Compare commits
	
		
			289 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a5259f56c5 | ||
|   | 3f75ed9c18 | ||
|   | 49ece51167 | ||
|   | e77c3eb20a | ||
|   | 59b2a5f8d0 | ||
|   | 28710d0bc7 | ||
|   | ad4d461606 | ||
|   | 67905089ba | ||
|   | f2483af561 | ||
|   | c28b87641e | ||
|   | f8e6a69d6e | ||
|   | 54216cec4b | ||
|   | 12989bbd99 | ||
|   | 38d09dba2e | ||
|   | fafd0c68e9 | ||
|   | 41195c8e48 | ||
|   | a97804548e | ||
|   | ba653c0841 | ||
|   | 5b191f78a0 | ||
|   | 83ef61287e | ||
|   | 3527e09bc5 | ||
|   | ddc5b3268f | ||
|   | 22307b1934 | ||
|   | bd97357f8d | ||
|   | 10dab1366e | ||
|   | 52fc94c1fe | ||
|   | c1c7961dd6 | ||
|   | d3eef051b1 | ||
|   | 57654df81e | ||
|   | 0f791d7a9a | ||
|   | 58779e0d65 | ||
|   | 4ac361b5fd | ||
|   | 1e2f27c061 | ||
|   | 0302e4da82 | ||
|   | dc8743e0c0 | ||
|   | cc5ce3d5ae | ||
|   | caaf6f3012 | ||
|   | c5de8fd1cc | ||
|   | c9f23869e3 | ||
|   | 61208c0e35 | ||
|   | dcffc74255 | ||
|   | 23e23be1a6 | ||
|   | 710427248a | ||
|   | a868042de2 | ||
|   | 15296cd8b4 | ||
|   | 717023245f | ||
|   | 320be5bffa | ||
|   | 778abea2d9 | ||
|   | 835a1ac3a6 | ||
|   | 20a7ef33f1 | ||
|   | e72612c7ff | ||
|   | 04e0f001b0 | ||
|   | 5db24aa901 | ||
|   | aec5e3d77b | ||
|   | 335ddf8db5 | ||
|   | 4abaf2b236 | ||
|   | 183d212431 | ||
|   | e99532fb89 | ||
|   | 4aa646f6b0 | ||
|   | 9dcd51fb80 | ||
|   | 6dee988b76 | ||
|   | 5af40db396 | ||
|   | b3553bee7a | ||
|   | ac19c94b9f | ||
|   | 845f7dc331 | ||
|   | 2adeae37e1 | ||
|   | 16eb12b2a0 | ||
|   | 8411f2aa32 | ||
|   | e8acc49cbd | ||
|   | 4bed073c65 | ||
|   | 272735fb26 | ||
|   | b75cf2c189 | ||
|   | 1aaa992250 | ||
|   | 6256c066f1 | ||
|   | 870b89a8f0 | ||
|   | 65ac96913c | ||
|   | 480945cb09 | ||
|   | bfc7130ed8 | ||
|   | a0938d9386 | ||
|   | 2338c69d40 | ||
|   | c714501a0e | ||
|   | a58a3e5000 | ||
|   | ba35212b67 | ||
|   | f3e0358de7 | ||
|   | 8064744d3a | ||
|   | d261949db2 | ||
|   | 877f0fe2e8 | ||
|   | 003d85772c | ||
|   | e7e10131de | ||
|   | 830361e48b | ||
|   | 1b1a9ce250 | ||
|   | 25ac4c708f | ||
|   | c268e90f49 | ||
|   | c17512b7ab | ||
|   | 1b837b3dc7 | ||
|   | 2ece724f75 | ||
|   | 276ac840aa | ||
|   | 1f91461853 | ||
|   | 1f9874102a | ||
|   | 822605c157 | ||
|   | e49266ae43 | ||
|   | 62e9de1a3b | ||
|   | 2ddc4f7ae9 | ||
|   | 2dd402675d | ||
|   | 25b1af1e11 | ||
|   | 75fb2b8156 | ||
|   | 2a403f8b85 | ||
|   | c3d45a9f06 | ||
|   | c07b85b625 | ||
|   | 511f653e6e | ||
|   | 5636eaca6d | ||
|   | 4b839b9958 | ||
|   | 3f79da84d5 | ||
|   | d540638223 | ||
|   | 4ec9b6dd4e | ||
|   | 3bc219167a | ||
|   | 8a55c97b4e | ||
|   | 9e34162a09 | ||
|   | 860a371eeb | ||
|   | 41a46526a1 | ||
|   | 46b798ac1b | ||
|   | 359d0f2910 | ||
|   | ad3cb0386b | ||
|   | 3a183cb218 | ||
|   | 2eecaccd1c | ||
|   | 5f30a98bc1 | ||
|   | b8a2fcbaff | ||
|   | 01496cd080 | ||
|   | 6a968ab82a | ||
|   | c0c4890887 | ||
|   | 171a53592d | ||
|   | 7811c330db | ||
|   | 9bcd131e66 | ||
|   | c791423dd5 | ||
|   | 80bdf38388 | ||
|   | 9d9cb32f4e | ||
|   | 87229bab13 | ||
|   | f065e9e4d5 | ||
|   | 3812693111 | ||
|   | dd3c572256 | ||
|   | c5dfe40326 | ||
|   | ef278301e3 | ||
|   | 2888fd64b0 | ||
|   | 27c0f37e49 | ||
|   | 0774f6a5e7 | ||
|   | 4036d4459b | ||
|   | ee643de5b6 | ||
|   | 8c7549a09e | ||
|   | 7a16146304 | ||
|   | 3d3809a21b | ||
|   | 29465397dd | ||
|   | d300bb1735 | ||
|   | 2e703472f1 | ||
|   | 8fede90b9e | ||
|   | d128f157c4 | ||
|   | 4fcedabfd0 | ||
|   | 246c8e4f74 | ||
|   | 4d2207aba7 | ||
|   | 17b8b86d68 | ||
|   | fdb57230a3 | ||
|   | 7469732bbc | ||
|   | d1dd6c3440 | ||
|   | 02612c0061 | ||
|   | a4db63a773 | ||
|   | 035c2b906a | ||
|   | 6ea8be5749 | ||
|   | 36024d5439 | ||
|   | 8d52c98373 | ||
|   | b4a4eb0057 | ||
|   | b469c8ddbd | ||
|   | eee0036c7f | ||
|   | 89c66b9430 | ||
|   | bd38319d83 | ||
|   | 33dffd5ea8 | ||
|   | 57176dadd4 | ||
|   | dd449a8705 | ||
|   | 587ad9f41d | ||
|   | a16ad8bf3b | ||
|   | 1e0490bd36 | ||
|   | 8afc641f0c | ||
|   | 2e4d58cb92 | ||
|   | 02d7e2db65 | ||
|   | f935c573e9 | ||
|   | 4a25e66c00 | ||
|   | 95f4e3448e | ||
|   | eacb1c1771 | ||
|   | 07fd825349 | ||
|   | be15cc8a36 | ||
|   | 2f68519b3c | ||
|   | efe641f202 | ||
|   | 9bd663046a | ||
|   | 11b07f01ba | ||
|   | 6c2f370e6b | ||
|   | 936bccccd2 | ||
|   | c30ffeb81e | ||
|   | e05a323afd | ||
|   | 80895deae2 | ||
|   | eddc691fc9 | ||
|   | deb2d7194d | ||
|   | fd8cfb11fb | ||
|   | 9407aa4600 | ||
|   | 263b8da37d | ||
|   | b95988b4e2 | ||
|   | 35025e164a | ||
|   | 32bbab8518 | ||
|   | 84c0b745af | ||
|   | 8b286fb009 | ||
|   | 386fa58b67 | ||
|   | c5cfbc2297 | ||
|   | cd0a2beb11 | ||
|   | 73f01ad8d8 | ||
|   | 930b639cc9 | ||
|   | 58483ea70c | ||
|   | 072cac0347 | ||
|   | 956d7cf3f3 | ||
|   | 7558a2162e | ||
|   | 62b165c0b4 | ||
|   | fe258e1b67 | ||
|   | dc37232100 | ||
|   | 163f55f9c2 | ||
|   | 2d16fd085e | ||
|   | e1a5f5bca5 | ||
|   | 6e772ee189 | ||
|   | 2b0f178ba3 | ||
|   | 79e6c9fa6c | ||
|   | 1426ddec5f | ||
|   | e9105003b0 | ||
|   | 587bb06558 | ||
|   | 53e9664cde | ||
|   | 482fbac68f | ||
|   | dcccd43427 | ||
|   | 397b8ff892 | ||
|   | 38a4cf315a | ||
|   | 5f8b24e32c | ||
|   | 678a7ceb4e | ||
|   | 077d494c7b | ||
|   | 09b243d8c2 | ||
|   | 991183e514 | ||
|   | 9bf10e4b58 | ||
|   | 884599d27d | ||
|   | f8a6e65bfd | ||
|   | 6df6c5d615 | ||
|   | 93114b7682 | ||
|   | 9987ac3f13 | ||
|   | 01a32b2154 | ||
|   | b3c3142bb2 | ||
|   | 77f1a959c3 | ||
|   | e3dda0e812 | ||
|   | 38103d36b4 | ||
|   | 7685fe1724 | ||
|   | 01afe03a3f | ||
|   | 7fbbf89c58 | ||
|   | 84d259d8b3 | ||
|   | 8b47670a74 | ||
|   | 7f5dc1d461 | ||
|   | 43e765f4f9 | ||
|   | adec73f542 | ||
|   | fee159541f | ||
|   | d81e6bf6ce | ||
|   | 70c93d970c | ||
|   | 4960273832 | ||
|   | 6c018ee6fe | ||
|   | 4ef32103ca | ||
|   | e4ec27c5e2 | ||
|   | 20c04f7977 | ||
|   | 571f50d734 | ||
|   | 780ea6f7c0 | ||
|   | 4279906f6e | ||
|   | 2e54b97fc2 | ||
|   | e1641b2c2e | ||
|   | e0e1e4be80 | ||
|   | d5845ce900 | ||
|   | 85f2cde4c3 | ||
|   | cef64e01b3 | ||
|   | 94ea775232 | ||
|   | 2e4b7fac11 | ||
|   | 2867ec459a | ||
|   | cd18d89894 | ||
|   | 449ed31e25 | ||
|   | 1f36904588 | ||
|   | f7495dd0c3 | ||
|   | a11f77835d | ||
|   | af1ad82c8e | ||
|   | 4976338677 | ||
|   | 99d130d1ed | ||
|   | 4fb0544b0e | ||
|   | 0b4ac61435 | ||
|   | 1d5cd1d7c4 | ||
|   | 14830d9f1c | 
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | 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. | ||||||
|  |  | ||||||
|  | Please answer the following questions.  | ||||||
|  |  | ||||||
|  | ### Which version of matterbridge are you using? | ||||||
|  | run ```matterbridge -version``` | ||||||
|  |  | ||||||
|  | ### If you're having problems with mattermost please specify mattermost version.  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Please describe the expected behavior. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Please describe the actual behavior.  | ||||||
|  | #### 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)) | ||||||
							
								
								
									
										49
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | language: go | ||||||
|  | go: | ||||||
|  |     #- 1.7.x | ||||||
|  |     - 1.8.x | ||||||
|  |       # - tip | ||||||
|  |  | ||||||
|  | # we have everything vendored | ||||||
|  | install: true | ||||||
|  |  | ||||||
|  | env: | ||||||
|  |     - GOOS=linux GOARCH=amd64 | ||||||
|  |       #    - GOOS=windows GOARCH=amd64 | ||||||
|  |       #- GOOS=linux GOARCH=arm | ||||||
|  |  | ||||||
|  | matrix: | ||||||
|  |   # It's ok if our code fails on unstable development versions of Go. | ||||||
|  |   allow_failures: | ||||||
|  |     - go: tip | ||||||
|  |   # Don't wait for tip tests to finish. Mark the test run green if the | ||||||
|  |   # tests pass on the stable versions of Go. | ||||||
|  |   fast_finish: true | ||||||
|  |  | ||||||
|  | notifications: | ||||||
|  |       email: false | ||||||
|  |  | ||||||
|  | before_script: | ||||||
|  |   - MY_VERSION=$(git describe --tags) | ||||||
|  |   - GO_FILES=$(find . -iname '*.go' | grep -v /vendor/)  # All the .go files, excluding vendor/ | ||||||
|  |   - PKGS=$(go list ./... | grep -v /vendor/)             # All the import paths, excluding vendor/ | ||||||
|  | #  - go get github.com/golang/lint/golint                 # Linter | ||||||
|  |   - go get honnef.co/go/tools/cmd/megacheck              # Badass static analyzer/linter | ||||||
|  |  | ||||||
|  | # Anything in before_script: that returns a nonzero exit code will | ||||||
|  | # 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 | ||||||
|  |   - 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 | ||||||
|  |   - megacheck $PKGS                   # "go vet on steroids" + linter | ||||||
|  |   - /bin/bash ci/bintray.sh | ||||||
|  |   #- golint -set_exit_status $PKGS     # one last linter | ||||||
|  |  | ||||||
|  | deploy: | ||||||
|  |   provider: bintray | ||||||
|  |   file: ci/deploy.json | ||||||
|  |   user: 42wim | ||||||
|  |   key: | ||||||
|  |      secure: "CeXXe6JOmt7HYR81MdWLua0ltQHhDdkIeRGBFbgd7hkb1wi8eF9DgpAcQrTso8NIlHNZmSAP46uhFgsRvkuezzX0ygalZ7DCJyAyn3sAMEh+UQSHV1WGThRehTtidqRGjetzsIGSwdrJOWil+XTfbO1Z8DGzfakhSuAZka8CM4BAoe3YeP9rYK8h+84x0GHfczvsLtXZ3mWLvQuwe4pK6+ItBCUg0ae7O7ZUpWHy0xQQkkWztY/6RAzXfaG7DuGjIw+20fhx3WOXRNpHCtZ6Bc3qERCpk0s1HhlQWlrN9wDaFTBWYwlvSnNgvxxMbNXJ6RrRJ0l0bA7FUswYwyroxhzrGLdzWDg8dHaQkypocngdalfhpsnoO9j3ApJhomUFJ3UoEq5nOGRUrKn8MPi+dP0zE4kNQ3e4VNa1ufNrvfpWolMg3xh8OXuhQdD5wIM5zFAbRJLqWSCVAjPq4DDPecmvXBOlIial7oa312lN5qnBnUjvAcxszZ+FUyDHT1Grxzna4tMwxY9obPzZUzm7359AOCCwIQFVB8GLqD2nwIstcXS0zGRz+fhviPipHuBa02q5bGUZwmkvrSNab0s8Jo7pCrel2Rz3nWPKaiCfq2WjbW1CLheSMkOQrjsdUd1hhbqNWFPUjJPInTc77NAKCfm5runv5uyowRLh4NNd0sI=" | ||||||
| @@ -6,6 +6,6 @@ RUN apk update && apk add go git gcc musl-dev ca-certificates \ | |||||||
|         && cd /go/src/github.com/42wim/matterbridge \ |         && cd /go/src/github.com/42wim/matterbridge \ | ||||||
|         && export GOPATH=/go \ |         && export GOPATH=/go \ | ||||||
|         && go get \ |         && go get \ | ||||||
|         && go build -o /bin/matterbridge \ |         && go build -x -ldflags "-X main.githash=$(git log --pretty=format:'%h' -n 1)" -o /bin/matterbridge \ | ||||||
|         && rm -rf /go \ |         && rm -rf /go \ | ||||||
|         && apk del --purge git go gcc musl-dev |         && apk del --purge git go gcc musl-dev | ||||||
|   | |||||||
							
								
								
									
										115
									
								
								README-0.6.md
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								README-0.6.md
									
									
									
									
									
								
							| @@ -1,115 +0,0 @@ | |||||||
| # matterbridge |  | ||||||
|  |  | ||||||
| Simple bridge between mattermost, IRC, XMPP, Gitter and Slack |  | ||||||
|  |  | ||||||
| * Relays public channel messages between mattermost, IRC, XMPP, Gitter and Slack. Pick and mix. |  | ||||||
| * Supports multiple channels. |  | ||||||
| * Matterbridge can also work with private groups on your mattermost. |  | ||||||
|  |  | ||||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for documentation and an example. |  | ||||||
|  |  | ||||||
| ## Changelog |  | ||||||
| Since v0.6.1 support for XMPP, Gitter and Slack is added. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md) |  | ||||||
|  |  | ||||||
| ## Requirements: |  | ||||||
| Accounts to one of the supported bridges |  | ||||||
| * [Mattermost] (https://github.com/mattermost/platform/) |  | ||||||
| * [IRC] (http://www.mirc.com/servers.html) |  | ||||||
| * [XMPP] (https://jabber.org) |  | ||||||
| * [Gitter] (https://gitter.im) |  | ||||||
| * [Slack] (https://www.slack.com) |  | ||||||
|  |  | ||||||
| ## binaries |  | ||||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) |  | ||||||
| * For use with mattermost 3.3.0+ [v0.6.1](https://github.com/42wim/matterircd/releases/tag/v0.6.1) |  | ||||||
| * For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Docker |  | ||||||
| Create your matterbridge.conf file locally eg in ```/tmp/matterbridge.conf``` |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| docker run -ti -v /tmp/matterbridge.conf:/matterbridge.conf 42wim/matterbridge:0.6.1 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Compatibility |  | ||||||
| ### Mattermost  |  | ||||||
| * Matterbridge v0.6.1 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0) |  | ||||||
| * Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #### Webhooks version |  | ||||||
| * Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance. |  | ||||||
|  |  | ||||||
| #### Plus (API) version |  | ||||||
| * A dedicated user(bot) on your mattermost instance. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## building |  | ||||||
| Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| cd $GOPATH |  | ||||||
| go get github.com/42wim/matterbridge |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| You should now have matterbridge binary in the bin directory: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| $ ls bin/ |  | ||||||
| matterbridge |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## running |  | ||||||
| 1) Copy the matterbridge.conf.sample to matterbridge.conf in the same directory as the matterbridge binary.   |  | ||||||
| 2) Edit matterbridge.conf with the settings for your environment. See below for more config information.   |  | ||||||
| 3) Now you can run matterbridge.  |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| Usage of ./matterbridge: |  | ||||||
|   -conf string |  | ||||||
|         config file (default "matterbridge.conf") |  | ||||||
|   -debug |  | ||||||
|         enable debug |  | ||||||
|   -plus |  | ||||||
|         running using API instead of webhooks (deprecated, set Plus flag in [general] config) |  | ||||||
|   -version |  | ||||||
|         show version |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## config |  | ||||||
| ### matterbridge |  | ||||||
| matterbridge looks for matterbridge.conf in current directory. (use -conf to specify another file) |  | ||||||
|  |  | ||||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for an example. |  | ||||||
|  |  | ||||||
| ### mattermost |  | ||||||
| #### webhooks version |  | ||||||
| You'll have to configure the incoming and outgoing webhooks.  |  | ||||||
|  |  | ||||||
| * incoming webhooks |  | ||||||
| Go to "account settings" - integrations - "incoming webhooks".   |  | ||||||
| Choose a channel at "Add a new incoming webhook", this will create a webhook URL right below.   |  | ||||||
| This URL should be set in the matterbridge.conf in the [mattermost] section (see above)   |  | ||||||
|  |  | ||||||
| * outgoing webhooks |  | ||||||
| Go to "account settings" - integrations - "outgoing webhooks".   |  | ||||||
| Choose a channel (the same as the one from incoming webhooks) and fill in the address and port of the server matterbridge will run on.   |  | ||||||
|  |  | ||||||
| e.g. http://192.168.1.1:9999 (192.168.1.1:9999 is the BindAddress specified in [mattermost] section of matterbridge.conf) |  | ||||||
|  |  | ||||||
| #### plus version |  | ||||||
| You'll have to create a new dedicated user on your mattermost instance. |  | ||||||
| Specify the login and password in [mattermost] section of matterbridge.conf |  | ||||||
|  |  | ||||||
| ## FAQ |  | ||||||
| Please look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for more information first.  |  | ||||||
| ### Mattermost doesn't show the IRC nicks |  | ||||||
| If you're running the webhooks version, this can be fixed by either: |  | ||||||
| * enabling "override usernames". See [mattermost documentation](http://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks) |  | ||||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf. |  | ||||||
|  |  | ||||||
| If you're running the plus version you'll need to: |  | ||||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf. |  | ||||||
|  |  | ||||||
| Also look at the ```RemoteNickFormat``` setting. |  | ||||||
							
								
								
									
										217
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,57 +1,63 @@ | |||||||
| # matterbridge | # matterbridge | ||||||
|  | Click on one of the badges below to join the chat    | ||||||
|  |  | ||||||
| :warning: Look at [README-0.6.md] (https://github.com/42wim/matterbridge/blob/master/README-0.6.md) for the documentation of the current stable.  | [](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)  | ||||||
| The information below is about the develop version. |  | ||||||
|  |  | ||||||
| Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord | [](https://github.com/42wim/matterbridge/releases/latest) [](https://bintray.com/42wim/nightly/Matterbridge/_latestVersion) | ||||||
|  |  | ||||||
| * Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack and Discord. Pick and mix. |  | ||||||
| * Supports multiple channels. |  | ||||||
| * Matterbridge can also work with private groups on your mattermost. | Simple bridge between Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat, Hipchat(via xmpp), Matrix and Steam. | ||||||
|  | Has a REST API. | ||||||
|  |  | ||||||
|  | # Table of Contents | ||||||
|  |  * [Features](#features) | ||||||
|  |  * [Requirements](#requirements) | ||||||
|  |  * [Screenshots](https://github.com/42wim/matterbridge/wiki/) | ||||||
|  |  * [Installing](#installing) | ||||||
|  |    * [Binaries](#binaries) | ||||||
|  |    * [Building](#building) | ||||||
|  |  * [Configuration](#configuration) | ||||||
|  |    * [Howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) | ||||||
|  |    * [Examples](#examples)  | ||||||
|  |  * [Running](#running) | ||||||
|  |    * [Docker](#docker) | ||||||
|  |  * [Changelog](#changelog) | ||||||
|  |  * [FAQ](#faq) | ||||||
|  |  * [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. | ||||||
|  | * Matterbridge can also work with private groups on your mattermost/slack. | ||||||
| * Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts. | * 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). | * The bridge is now a gateway which has support multiple in and out bridges. (and supports multiple gateways). | ||||||
|  | * REST API to read/post messages to bridges (WIP). | ||||||
|  |  | ||||||
| Look at [matterbridge.toml.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example. | # Requirements | ||||||
| Look at [matterbridge.toml.simple] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.simple) for a simple example. |  | ||||||
|  |  | ||||||
| ## Changelog |  | ||||||
| Since v0.7.0-dev the configuration has changed. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md) |  | ||||||
|  |  | ||||||
| ## Requirements |  | ||||||
| Accounts to one of the supported bridges | Accounts to one of the supported bridges | ||||||
| * [Mattermost] (https://github.com/mattermost/platform/) | * [Mattermost](https://github.com/mattermost/platform/) 3.5.x - 3.10.x, 4.0.x | ||||||
| * [IRC] (http://www.mirc.com/servers.html) | * [IRC](http://www.mirc.com/servers.html) | ||||||
| * [XMPP] (https://jabber.org) | * [XMPP](https://jabber.org) | ||||||
| * [Gitter] (https://gitter.im) | * [Gitter](https://gitter.im) | ||||||
| * [Slack] (https://slack.com) | * [Slack](https://slack.com) | ||||||
| * [Discord] (https://discordapp.com) | * [Discord](https://discordapp.com) | ||||||
|  | * [Telegram](https://telegram.org) | ||||||
|  | * [Hipchat](https://www.hipchat.com) | ||||||
|  | * [Rocket.chat](https://rocket.chat) | ||||||
|  | * [Matrix](https://matrix.org) | ||||||
|  | * [Steam](https://store.steampowered.com/) | ||||||
|  |  | ||||||
| ## Docker | # Screenshots | ||||||
| Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml``` | See https://github.com/42wim/matterbridge/wiki | ||||||
| ``` |  | ||||||
| docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## binaries | # Installing | ||||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) | ## Binaries | ||||||
| * For use with mattermost 3.3.0+ [v0.6.1](https://github.com/42wim/matterircd/releases/tag/v0.6.1) | * Latest stable release [v1.0.1](https://github.com/42wim/matterbridge/releases/latest) | ||||||
| * For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0) | * Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)   | ||||||
|  |  | ||||||
| ## Compatibility | ## Building | ||||||
| ### Mattermost  | Go 1.7+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) | ||||||
| * Matterbridge v0.6.1 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0) |  | ||||||
| * Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #### Webhooks version |  | ||||||
| * Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance. |  | ||||||
|  |  | ||||||
| #### API version |  | ||||||
| * A dedicated user(bot) on your mattermost instance. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## building |  | ||||||
| Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) |  | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| cd $GOPATH | cd $GOPATH | ||||||
| @@ -65,10 +71,73 @@ $ ls bin/ | |||||||
| matterbridge | matterbridge | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## running | # Configuration | ||||||
| 1) Copy the matterbridge.conf.sample to matterbridge.conf in the same directory as the matterbridge binary.   | ## Basic configuration | ||||||
| 2) Edit matterbridge.conf with the settings for your environment. See below for more config information.   | See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration. | ||||||
| 3) Now you can run matterbridge.  |  | ||||||
|  | ## Advanced configuration | ||||||
|  | * [matterbridge.toml.sample](https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example. | ||||||
|  |  | ||||||
|  | ## Examples  | ||||||
|  | ### Bridge mattermost (off-topic) - irc (#testing) | ||||||
|  | ``` | ||||||
|  | [irc] | ||||||
|  |     [irc.freenode] | ||||||
|  |     Server="irc.freenode.net:6667" | ||||||
|  |     Nick="yourbotname" | ||||||
|  |  | ||||||
|  | [mattermost] | ||||||
|  |     [mattermost.work] | ||||||
|  |     Server="yourmattermostserver.tld" | ||||||
|  |     Team="yourteam" | ||||||
|  |     Login="yourlogin" | ||||||
|  |     Password="yourpass" | ||||||
|  |     PrefixMessagesWithNick=true | ||||||
|  |     RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||||
|  |  | ||||||
|  | [[gateway]] | ||||||
|  | name="mygateway" | ||||||
|  | enable=true | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="irc.freenode" | ||||||
|  |     channel="#testing" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="mattermost.work" | ||||||
|  |     channel="off-topic" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Bridge slack (#general) - discord (general) | ||||||
|  | ``` | ||||||
|  | [slack] | ||||||
|  | [slack.test] | ||||||
|  | Token="yourslacktoken" | ||||||
|  | PrefixMessagesWithNick=true | ||||||
|  |  | ||||||
|  | [discord] | ||||||
|  | [discord.test] | ||||||
|  | Token="yourdiscordtoken" | ||||||
|  | Server="yourdiscordservername" | ||||||
|  |  | ||||||
|  | [general] | ||||||
|  | RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> " | ||||||
|  |  | ||||||
|  | [[gateway]] | ||||||
|  |     name = "mygateway" | ||||||
|  |     enable=true | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account = "discord.test" | ||||||
|  |     channel="general" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account ="slack.test" | ||||||
|  |     channel = "general" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | # Running | ||||||
|  |  | ||||||
|  | See [howto](https://github.com/42wim/matterbridge/wiki/How-to-create-your-config) for a step by step walkthrough for creating your configuration. | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| Usage of ./matterbridge: | Usage of ./matterbridge: | ||||||
| @@ -76,39 +145,39 @@ Usage of ./matterbridge: | |||||||
|         config file (default "matterbridge.toml") |         config file (default "matterbridge.toml") | ||||||
|   -debug |   -debug | ||||||
|         enable debug |         enable debug | ||||||
|  |   -gops | ||||||
|  |         enable gops agent | ||||||
|   -version |   -version | ||||||
|         show version |         show version | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## config | ## Docker | ||||||
| ### matterbridge | Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml``` | ||||||
| matterbridge looks for matterbridge.toml in current directory. (use -conf to specify another file) | ``` | ||||||
|  | docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge | ||||||
|  | ``` | ||||||
|  |  | ||||||
| Look at [matterbridge.toml.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for an example. | # Changelog | ||||||
|  | See [changelog.md](https://github.com/42wim/matterbridge/blob/master/changelog.md) | ||||||
|  |  | ||||||
| ### mattermost | # FAQ | ||||||
| #### webhooks version |  | ||||||
| You'll have to configure the incoming and outgoing webhooks.  |  | ||||||
|  |  | ||||||
| * incoming webhooks | See [FAQ](https://github.com/42wim/matterbridge/wiki/FAQ) | ||||||
| Go to "account settings" - integrations - "incoming webhooks".   |  | ||||||
| Choose a channel at "Add a new incoming webhook", this will create a webhook URL right below.   |  | ||||||
| This URL should be set in the matterbridge.conf in the [mattermost] section (see above)   |  | ||||||
|  |  | ||||||
| * outgoing webhooks | Want to tip ?  | ||||||
| Go to "account settings" - integrations - "outgoing webhooks".   | * eth: 0xb3f9b5387c66ad6be892bcb7bbc67862f3abc16f | ||||||
| Choose a channel (the same as the one from incoming webhooks) and fill in the address and port of the server matterbridge will run on.   | * btc: 1N7cKHj5SfqBHBzDJ6kad4BzeqUBBS2zhs | ||||||
|  |  | ||||||
| e.g. http://192.168.1.1:9999 (192.168.1.1:9999 is the BindAddress specified in [mattermost] section of matterbridge.conf) | # Thanks | ||||||
|  | Matterbridge wouldn't exist without these libraries: | ||||||
| ## FAQ | * discord - https://github.com/bwmarrin/discordgo | ||||||
| Please look at [matterbridge.toml.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for more information first.  | * echo - https://github.com/labstack/echo | ||||||
| ### Mattermost doesn't show the IRC nicks | * gitter - https://github.com/sromku/go-gitter | ||||||
| If you're running the webhooks version, this can be fixed by either: | * gops - https://github.com/google/gops | ||||||
| * enabling "override usernames". See [mattermost documentation](http://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks) | * irc - https://github.com/thoj/go-ircevent | ||||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.toml. | * mattermost - https://github.com/mattermost/platform | ||||||
|  | * matrix - https://github.com/matrix-org/gomatrix | ||||||
| If you're running the plus version you'll need to: | * slack - https://github.com/nlopes/slack | ||||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.toml. | * steam - https://github.com/Philipp15b/go-steam | ||||||
|  | * telegram - https://github.com/go-telegram-bot-api/telegram-bot-api | ||||||
| Also look at the ```RemoteNickFormat``` setting. | * xmpp - https://github.com/mattn/go-xmpp | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								bridge/api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								bridge/api/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | package api | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"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" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Api struct { | ||||||
|  | 	Config   *config.Protocol | ||||||
|  | 	Remote   chan config.Message | ||||||
|  | 	Account  string | ||||||
|  | 	Messages ring.Ring | ||||||
|  | 	sync.RWMutex | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ApiMessage struct { | ||||||
|  | 	Text     string `json:"text"` | ||||||
|  | 	Username string `json:"username"` | ||||||
|  | 	UserID   string `json:"userid"` | ||||||
|  | 	Avatar   string `json:"avatar"` | ||||||
|  | 	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{} | ||||||
|  | 	e := echo.New() | ||||||
|  | 	b.Messages = ring.Ring{} | ||||||
|  | 	b.Messages.SetCapacity(cfg.Buffer) | ||||||
|  | 	b.Config = &cfg | ||||||
|  | 	b.Account = account | ||||||
|  | 	b.Remote = c | ||||||
|  | 	if b.Config.Token != "" { | ||||||
|  | 		e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { | ||||||
|  | 			return key == b.Config.Token, nil | ||||||
|  | 		})) | ||||||
|  | 	} | ||||||
|  | 	e.GET("/api/messages", b.handleMessages) | ||||||
|  | 	e.POST("/api/message", b.handlePostMessage) | ||||||
|  | 	go func() { | ||||||
|  | 		flog.Fatal(e.Start(cfg.BindAddress)) | ||||||
|  | 	}() | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Api) Connect() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (b *Api) Disconnect() error { | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  | func (b *Api) JoinChannel(channel config.ChannelInfo) error { | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Api) Send(msg config.Message) error { | ||||||
|  | 	b.Lock() | ||||||
|  | 	defer b.Unlock() | ||||||
|  | 	b.Messages.Enqueue(&msg) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Api) handlePostMessage(c echo.Context) error { | ||||||
|  | 	message := &ApiMessage{} | ||||||
|  | 	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", | ||||||
|  | 	} | ||||||
|  | 	return c.JSON(http.StatusOK, message) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Api) handleMessages(c echo.Context) error { | ||||||
|  | 	b.Lock() | ||||||
|  | 	defer b.Unlock() | ||||||
|  | 	c.JSONPretty(http.StatusOK, b.Messages.Values(), " ") | ||||||
|  | 	b.Messages = ring.Ring{} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -1,45 +1,106 @@ | |||||||
| package bridge | package bridge | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/api" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/bridge/discord" | 	"github.com/42wim/matterbridge/bridge/discord" | ||||||
| 	"github.com/42wim/matterbridge/bridge/gitter" | 	"github.com/42wim/matterbridge/bridge/gitter" | ||||||
| 	"github.com/42wim/matterbridge/bridge/irc" | 	"github.com/42wim/matterbridge/bridge/irc" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/matrix" | ||||||
| 	"github.com/42wim/matterbridge/bridge/mattermost" | 	"github.com/42wim/matterbridge/bridge/mattermost" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/rocketchat" | ||||||
| 	"github.com/42wim/matterbridge/bridge/slack" | 	"github.com/42wim/matterbridge/bridge/slack" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/steam" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/telegram" | ||||||
| 	"github.com/42wim/matterbridge/bridge/xmpp" | 	"github.com/42wim/matterbridge/bridge/xmpp" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  |  | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Bridge interface { | type Bridger interface { | ||||||
| 	Send(msg config.Message) error | 	Send(msg config.Message) error | ||||||
| 	Name() string |  | ||||||
| 	Connect() error | 	Connect() error | ||||||
| 	FullOrigin() string | 	JoinChannel(channel config.ChannelInfo) error | ||||||
| 	Origin() string | 	Disconnect() error | ||||||
| 	Protocol() string |  | ||||||
| 	JoinChannel(channel string) error |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) Bridge { | type Bridge struct { | ||||||
|  | 	Config config.Protocol | ||||||
|  | 	Bridger | ||||||
|  | 	Name     string | ||||||
|  | 	Account  string | ||||||
|  | 	Protocol string | ||||||
|  | 	Channels map[string]config.ChannelInfo | ||||||
|  | 	Joined   map[string]bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge { | ||||||
|  | 	b := new(Bridge) | ||||||
|  | 	b.Channels = make(map[string]config.ChannelInfo) | ||||||
| 	accInfo := strings.Split(bridge.Account, ".") | 	accInfo := strings.Split(bridge.Account, ".") | ||||||
| 	protocol := accInfo[0] | 	protocol := accInfo[0] | ||||||
| 	name := accInfo[1] | 	name := accInfo[1] | ||||||
|  | 	b.Name = name | ||||||
|  | 	b.Protocol = protocol | ||||||
|  | 	b.Account = bridge.Account | ||||||
|  | 	b.Joined = make(map[string]bool) | ||||||
|  |  | ||||||
| 	// override config from environment | 	// override config from environment | ||||||
| 	config.OverrideCfgFromEnv(cfg, protocol, name) | 	config.OverrideCfgFromEnv(cfg, protocol, name) | ||||||
| 	switch protocol { | 	switch protocol { | ||||||
| 	case "mattermost": | 	case "mattermost": | ||||||
| 		return bmattermost.New(cfg.Mattermost[name], name, c) | 		b.Config = cfg.Mattermost[name] | ||||||
|  | 		b.Bridger = bmattermost.New(cfg.Mattermost[name], bridge.Account, c) | ||||||
| 	case "irc": | 	case "irc": | ||||||
| 		return birc.New(cfg.IRC[name], name, c) | 		b.Config = cfg.IRC[name] | ||||||
|  | 		b.Bridger = birc.New(cfg.IRC[name], bridge.Account, c) | ||||||
| 	case "gitter": | 	case "gitter": | ||||||
| 		return bgitter.New(cfg.Gitter[name], name, c) | 		b.Config = cfg.Gitter[name] | ||||||
|  | 		b.Bridger = bgitter.New(cfg.Gitter[name], bridge.Account, c) | ||||||
| 	case "slack": | 	case "slack": | ||||||
| 		return bslack.New(cfg.Slack[name], name, c) | 		b.Config = cfg.Slack[name] | ||||||
|  | 		b.Bridger = bslack.New(cfg.Slack[name], bridge.Account, c) | ||||||
| 	case "xmpp": | 	case "xmpp": | ||||||
| 		return bxmpp.New(cfg.Xmpp[name], name, c) | 		b.Config = cfg.Xmpp[name] | ||||||
|  | 		b.Bridger = bxmpp.New(cfg.Xmpp[name], bridge.Account, c) | ||||||
| 	case "discord": | 	case "discord": | ||||||
| 		return bdiscord.New(cfg.Discord[name], name, c) | 		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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) JoinChannels() error { | ||||||
|  | 	err := b.joinChannels(b.Channels, b.Joined) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 			err := b.JoinChannel(channel) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			exists[ID] = true | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,24 +6,49 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	EVENT_JOIN_LEAVE      = "join_leave" | ||||||
|  | 	EVENT_FAILURE         = "failure" | ||||||
|  | 	EVENT_REJOIN_CHANNELS = "rejoin_channels" | ||||||
|  | 	EVENT_USER_ACTION     = "user_action" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Message struct { | type Message struct { | ||||||
| 	Text       string | 	Text      string    `json:"text"` | ||||||
| 	Channel    string | 	Channel   string    `json:"channel"` | ||||||
| 	Username   string | 	Username  string    `json:"username"` | ||||||
| 	Origin     string | 	UserID    string    `json:"userid"` // userid on the bridge | ||||||
| 	FullOrigin string | 	Avatar    string    `json:"avatar"` | ||||||
| 	Protocol   string | 	Account   string    `json:"account"` | ||||||
| 	Avatar     string | 	Event     string    `json:"event"` | ||||||
|  | 	Protocol  string    `json:"protocol"` | ||||||
|  | 	Gateway   string    `json:"gateway"` | ||||||
|  | 	Timestamp time.Time `json:"timestamp"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ChannelInfo struct { | ||||||
|  | 	Name        string | ||||||
|  | 	Account     string | ||||||
|  | 	Direction   string | ||||||
|  | 	ID          string | ||||||
|  | 	SameChannel map[string]bool | ||||||
|  | 	Options     ChannelOptions | ||||||
| } | } | ||||||
|  |  | ||||||
| type Protocol struct { | type Protocol struct { | ||||||
| 	BindAddress            string // mattermost, slack | 	AuthCode               string // steam | ||||||
|  | 	BindAddress            string // mattermost, slack // DEPRECATED | ||||||
|  | 	Buffer                 int    // api | ||||||
|  | 	EditSuffix             string // mattermost, slack, discord, telegram, gitter | ||||||
|  | 	EditDisable            bool   // mattermost, slack, discord, telegram, gitter | ||||||
| 	IconURL                string // mattermost, slack | 	IconURL                string // mattermost, slack | ||||||
| 	IgnoreNicks            string // all protocols | 	IgnoreNicks            string // all protocols | ||||||
|  | 	IgnoreMessages         string // all protocols | ||||||
| 	Jid                    string // xmpp | 	Jid                    string // xmpp | ||||||
| 	Login                  string // mattermost | 	Login                  string // mattermost, matrix | ||||||
| 	Muc                    string // xmpp | 	Muc                    string // xmpp | ||||||
| 	Name                   string // all protocols | 	Name                   string // all protocols | ||||||
| 	Nick                   string // all protocols | 	Nick                   string // all protocols | ||||||
| @@ -31,27 +56,44 @@ type Protocol struct { | |||||||
| 	NickServNick           string // IRC | 	NickServNick           string // IRC | ||||||
| 	NickServPassword       string // IRC | 	NickServPassword       string // IRC | ||||||
| 	NicksPerRow            int    // mattermost, slack | 	NicksPerRow            int    // mattermost, slack | ||||||
|  | 	NoHomeServerSuffix     bool   // matrix | ||||||
| 	NoTLS                  bool   // mattermost | 	NoTLS                  bool   // mattermost | ||||||
| 	Password               string // IRC,mattermost,XMPP | 	Password               string // IRC,mattermost,XMPP,matrix | ||||||
| 	PrefixMessagesWithNick bool   // mattemost, slack | 	PrefixMessagesWithNick bool   // mattemost, slack | ||||||
| 	Protocol               string //all protocols | 	Protocol               string //all protocols | ||||||
| 	MessageQueue           int    // IRC, size of message queue for flood control | 	MessageQueue           int    // IRC, size of message queue for flood control | ||||||
| 	MessageDelay           int    // IRC, time in millisecond to wait between messages | 	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 | 	RemoteNickFormat       string // all protocols | ||||||
| 	Server                 string // IRC,mattermost,XMPP,discord | 	Server                 string // IRC,mattermost,XMPP,discord | ||||||
| 	ShowJoinPart           bool   // all protocols | 	ShowJoinPart           bool   // all protocols | ||||||
|  | 	ShowEmbeds             bool   // discord | ||||||
| 	SkipTLSVerify          bool   // IRC, mattermost | 	SkipTLSVerify          bool   // IRC, mattermost | ||||||
| 	Team                   string // mattermost | 	Team                   string // mattermost | ||||||
| 	Token                  string // gitter, slack, discord | 	Token                  string // gitter, slack, discord, api | ||||||
| 	URL                    string // mattermost, slack | 	URL                    string // mattermost, slack // DEPRECATED | ||||||
| 	UseAPI                 bool   // mattermost, slack | 	UseAPI                 bool   // mattermost, slack | ||||||
| 	UseSASL                bool   // IRC | 	UseSASL                bool   // IRC | ||||||
| 	UseTLS                 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 { | ||||||
|  | 	Key        string // irc | ||||||
|  | 	WebhookURL string // discord | ||||||
| } | } | ||||||
|  |  | ||||||
| type Bridge struct { | type Bridge struct { | ||||||
| 	Account string | 	Account     string | ||||||
| 	Channel string | 	Channel     string | ||||||
|  | 	Options     ChannelOptions | ||||||
|  | 	SameChannel bool | ||||||
| } | } | ||||||
|  |  | ||||||
| type Gateway struct { | type Gateway struct { | ||||||
| @@ -59,6 +101,7 @@ type Gateway struct { | |||||||
| 	Enable bool | 	Enable bool | ||||||
| 	In     []Bridge | 	In     []Bridge | ||||||
| 	Out    []Bridge | 	Out    []Bridge | ||||||
|  | 	InOut  []Bridge | ||||||
| } | } | ||||||
|  |  | ||||||
| type SameChannelGateway struct { | type SameChannelGateway struct { | ||||||
| @@ -69,12 +112,18 @@ type SameChannelGateway struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Config struct { | type Config struct { | ||||||
|  | 	Api                map[string]Protocol | ||||||
| 	IRC                map[string]Protocol | 	IRC                map[string]Protocol | ||||||
| 	Mattermost         map[string]Protocol | 	Mattermost         map[string]Protocol | ||||||
|  | 	Matrix             map[string]Protocol | ||||||
| 	Slack              map[string]Protocol | 	Slack              map[string]Protocol | ||||||
|  | 	Steam              map[string]Protocol | ||||||
| 	Gitter             map[string]Protocol | 	Gitter             map[string]Protocol | ||||||
| 	Xmpp               map[string]Protocol | 	Xmpp               map[string]Protocol | ||||||
| 	Discord            map[string]Protocol | 	Discord            map[string]Protocol | ||||||
|  | 	Telegram           map[string]Protocol | ||||||
|  | 	Rocketchat         map[string]Protocol | ||||||
|  | 	General            Protocol | ||||||
| 	Gateway            []Gateway | 	Gateway            []Gateway | ||||||
| 	SameChannelGateway []SameChannelGateway | 	SameChannelGateway []SameChannelGateway | ||||||
| } | } | ||||||
| @@ -84,6 +133,28 @@ func NewConfig(cfgfile string) *Config { | |||||||
| 	if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil { | 	if _, err := toml.DecodeFile(cfgfile, &cfg); err != nil { | ||||||
| 		log.Fatal(err) | 		log.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | 	fail := false | ||||||
|  | 	for k, v := range cfg.Mattermost { | ||||||
|  | 		res := Deprecated(v, "mattermost."+k) | ||||||
|  | 		if res { | ||||||
|  | 			fail = res | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for k, v := range cfg.Slack { | ||||||
|  | 		res := Deprecated(v, "slack."+k) | ||||||
|  | 		if res { | ||||||
|  | 			fail = res | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for k, v := range cfg.Rocketchat { | ||||||
|  | 		res := Deprecated(v, "rocketchat."+k) | ||||||
|  | 		if res { | ||||||
|  | 			fail = res | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if fail { | ||||||
|  | 		log.Fatalf("Fix your config. Please see changelog for more information") | ||||||
|  | 	} | ||||||
| 	return &cfg | 	return &cfg | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -126,16 +197,25 @@ func OverrideCfgFromEnv(cfg *Config, protocol string, account string) { | |||||||
|  |  | ||||||
| func GetIconURL(msg *Message, cfg *Protocol) string { | func GetIconURL(msg *Message, cfg *Protocol) string { | ||||||
| 	iconURL := cfg.IconURL | 	iconURL := cfg.IconURL | ||||||
|  | 	info := strings.Split(msg.Account, ".") | ||||||
|  | 	protocol := info[0] | ||||||
|  | 	name := info[1] | ||||||
| 	iconURL = strings.Replace(iconURL, "{NICK}", msg.Username, -1) | 	iconURL = strings.Replace(iconURL, "{NICK}", msg.Username, -1) | ||||||
| 	iconURL = strings.Replace(iconURL, "{BRIDGE}", msg.Origin, -1) | 	iconURL = strings.Replace(iconURL, "{BRIDGE}", name, -1) | ||||||
| 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", msg.Protocol, -1) | 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1) | ||||||
| 	return iconURL | 	return iconURL | ||||||
| } | } | ||||||
|  |  | ||||||
| func GetNick(msg *Message, cfg *Protocol) string { | func Deprecated(cfg Protocol, account string) bool { | ||||||
| 	nick := cfg.RemoteNickFormat | 	if cfg.BindAddress != "" { | ||||||
| 	nick = strings.Replace(nick, "{NICK}", msg.Username, -1) | 		log.Printf("ERROR: %s BindAddress is deprecated, you need to change it to WebhookBindAddress.", account) | ||||||
| 	nick = strings.Replace(nick, "{BRIDGE}", msg.Origin, -1) | 	} else if cfg.URL != "" { | ||||||
| 	nick = strings.Replace(nick, "{PROTOCOL}", msg.Protocol, -1) | 		log.Printf("ERROR: %s URL is deprecated, you need to change it to WebhookURL.", account) | ||||||
| 	return nick | 	} 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) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,18 +4,25 @@ import ( | |||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
| 	"github.com/bwmarrin/discordgo" | 	"github.com/bwmarrin/discordgo" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"sync" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type bdiscord struct { | type bdiscord struct { | ||||||
| 	c            *discordgo.Session | 	c              *discordgo.Session | ||||||
| 	Config       *config.Protocol | 	Config         *config.Protocol | ||||||
| 	Remote       chan config.Message | 	Remote         chan config.Message | ||||||
| 	protocol     string | 	Account        string | ||||||
| 	origin       string | 	Channels       []*discordgo.Channel | ||||||
| 	Channels     []*discordgo.Channel | 	Nick           string | ||||||
| 	Nick         string | 	UseChannelID   bool | ||||||
| 	UseChannelID bool | 	userMemberMap  map[string]*discordgo.Member | ||||||
|  | 	guildID        string | ||||||
|  | 	webhookID      string | ||||||
|  | 	webhookToken   string | ||||||
|  | 	channelInfoMap map[string]*config.ChannelInfo | ||||||
|  | 	sync.RWMutex | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog *log.Entry | var flog *log.Entry | ||||||
| @@ -25,18 +32,31 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *bdiscord { | func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord { | ||||||
| 	b := &bdiscord{} | 	b := &bdiscord{} | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	b.protocol = protocol | 	b.Account = account | ||||||
| 	b.origin = origin | 	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) | ||||||
|  | 	} | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *bdiscord) Connect() error { | func (b *bdiscord) Connect() error { | ||||||
| 	var err error | 	var err error | ||||||
| 	flog.Info("Connecting") | 	flog.Info("Connecting") | ||||||
|  | 	if b.Config.WebhookURL == "" { | ||||||
|  | 		flog.Info("Connecting using token") | ||||||
|  | 	} else { | ||||||
|  | 		flog.Info("Connecting using webhookurl (for posting) and token") | ||||||
|  | 	} | ||||||
|  | 	if !strings.HasPrefix(b.Config.Token, "Bot ") { | ||||||
|  | 		b.Config.Token = "Bot " + b.Config.Token | ||||||
|  | 	} | ||||||
| 	b.c, err = discordgo.New(b.Config.Token) | 	b.c, err = discordgo.New(b.Config.Token) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		flog.Debugf("%#v", err) | 		flog.Debugf("%#v", err) | ||||||
| @@ -44,12 +64,14 @@ func (b *bdiscord) Connect() error { | |||||||
| 	} | 	} | ||||||
| 	flog.Info("Connection succeeded") | 	flog.Info("Connection succeeded") | ||||||
| 	b.c.AddHandler(b.messageCreate) | 	b.c.AddHandler(b.messageCreate) | ||||||
|  | 	b.c.AddHandler(b.memberUpdate) | ||||||
|  | 	b.c.AddHandler(b.messageUpdate) | ||||||
| 	err = b.c.Open() | 	err = b.c.Open() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		flog.Debugf("%#v", err) | 		flog.Debugf("%#v", err) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	guilds, err := b.c.UserGuilds() | 	guilds, err := b.c.UserGuilds(100, "", "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		flog.Debugf("%#v", err) | 		flog.Debugf("%#v", err) | ||||||
| 		return err | 		return err | ||||||
| @@ -63,6 +85,7 @@ func (b *bdiscord) Connect() error { | |||||||
| 	for _, guild := range guilds { | 	for _, guild := range guilds { | ||||||
| 		if guild.Name == b.Config.Server { | 		if guild.Name == b.Config.Server { | ||||||
| 			b.Channels, err = b.c.GuildChannels(guild.ID) | 			b.Channels, err = b.c.GuildChannels(guild.ID) | ||||||
|  | 			b.guildID = guild.ID | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				flog.Debugf("%#v", err) | 				flog.Debugf("%#v", err) | ||||||
| 				return err | 				return err | ||||||
| @@ -72,30 +95,19 @@ func (b *bdiscord) Connect() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *bdiscord) FullOrigin() string { | func (b *bdiscord) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *bdiscord) JoinChannel(channel string) error { | func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	idcheck := strings.Split(channel, "ID:") | 	b.channelInfoMap[channel.ID] = &channel | ||||||
|  | 	idcheck := strings.Split(channel.Name, "ID:") | ||||||
| 	if len(idcheck) > 1 { | 	if len(idcheck) > 1 { | ||||||
| 		b.UseChannelID = true | 		b.UseChannelID = true | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *bdiscord) Name() string { |  | ||||||
| 	return b.protocol + "." + b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *bdiscord) Protocol() string { |  | ||||||
| 	return b.protocol |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *bdiscord) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *bdiscord) Send(msg config.Message) error { | func (b *bdiscord) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	channelID := b.getChannelID(msg.Channel) | 	channelID := b.getChannelID(msg.Channel) | ||||||
| @@ -103,31 +115,144 @@ func (b *bdiscord) Send(msg config.Message) error { | |||||||
| 		flog.Errorf("Could not find channelID for %v", msg.Channel) | 		flog.Errorf("Could not find channelID for %v", msg.Channel) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	nick := config.GetNick(&msg, b.Config) | 	if msg.Event == config.EVENT_USER_ACTION { | ||||||
| 	b.c.ChannelMessageSend(channelID, nick+msg.Text) | 		msg.Text = "_" + msg.Text + "_" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wID := b.webhookID | ||||||
|  | 	wToken := b.webhookToken | ||||||
|  | 	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)") | ||||||
|  | 		b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) | ||||||
|  | 	} else { | ||||||
|  | 		flog.Debugf("Broadcasting using Webhook") | ||||||
|  | 		b.c.WebhookExecute( | ||||||
|  | 			wID, | ||||||
|  | 			wToken, | ||||||
|  | 			true, | ||||||
|  | 			&discordgo.WebhookParams{ | ||||||
|  | 				Content:   msg.Text, | ||||||
|  | 				Username:  msg.Username, | ||||||
|  | 				AvatarURL: msg.Avatar, | ||||||
|  | 			}) | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) { | ||||||
|  | 	if b.Config.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.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) { | ||||||
| 	// not relay our own messages | 	// not relay our own messages | ||||||
| 	if m.Author.Username == b.Nick { | 	if m.Author.Username == b.Nick { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	// if using webhooks, do not relay if it's ours | ||||||
|  | 	if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(m.Attachments) > 0 { | 	if len(m.Attachments) > 0 { | ||||||
| 		for _, attach := range m.Attachments { | 		for _, attach := range m.Attachments { | ||||||
| 			m.Content = m.Content + "\n" + attach.URL | 			m.Content = m.Content + "\n" + attach.URL | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if m.Content == "" { |  | ||||||
|  | 	var text string | ||||||
|  | 	if m.Content != "" { | ||||||
|  | 		flog.Debugf("Receiving message %#v", m.Message) | ||||||
|  | 		if len(m.MentionRoles) > 0 { | ||||||
|  | 			m.Message.Content = b.replaceRoleMentions(m.Message.Content) | ||||||
|  | 		} | ||||||
|  | 		m.Message.Content = b.stripCustomoji(m.Message.Content) | ||||||
|  | 		m.Message.Content = b.replaceChannelMentions(m.Message.Content) | ||||||
|  | 		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} | ||||||
|  |  | ||||||
|  | 	rmsg.Channel = b.getChannelName(m.ChannelID) | ||||||
|  | 	if b.UseChannelID { | ||||||
|  | 		rmsg.Channel = "ID:" + m.ChannelID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !b.Config.UseUserName { | ||||||
|  | 		rmsg.Username = b.getNick(m.Author) | ||||||
|  | 	} else { | ||||||
|  | 		rmsg.Username = m.Author.Username | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if b.Config.ShowEmbeds && m.Message.Embeds != nil { | ||||||
|  | 		for _, embed := range m.Message.Embeds { | ||||||
|  | 			text = text + "embed: " + embed.Title + " - " + embed.Description + " - " + embed.URL + "\n" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// no empty messages | ||||||
|  | 	if text == "" { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.FullOrigin()) |  | ||||||
| 	channelName := b.getChannelName(m.ChannelID) | 	text, ok := b.replaceAction(text) | ||||||
| 	if b.UseChannelID { | 	if ok { | ||||||
| 		channelName = "ID:" + m.ChannelID | 		rmsg.Event = config.EVENT_USER_ACTION | ||||||
| 	} | 	} | ||||||
| 	b.Remote <- config.Message{Username: m.Author.Username, Text: m.ContentWithMentionsReplaced(), Channel: channelName, |  | ||||||
| 		Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg"} | 	rmsg.Text = text | ||||||
|  | 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||||
|  | 	b.Remote <- rmsg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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.userMemberMap[m.Member.User.ID] = m.Member | ||||||
|  | 	b.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *bdiscord) getNick(user *discordgo.User) string { | ||||||
|  | 	var err error | ||||||
|  | 	b.Lock() | ||||||
|  | 	defer b.Unlock() | ||||||
|  | 	if _, ok := b.userMemberMap[user.ID]; ok { | ||||||
|  | 		if b.userMemberMap[user.ID] != nil { | ||||||
|  | 			if b.userMemberMap[user.ID].Nick != "" { | ||||||
|  | 				// only return if nick is set | ||||||
|  | 				return b.userMemberMap[user.ID].Nick | ||||||
|  | 			} | ||||||
|  | 			// otherwise return username | ||||||
|  | 			return user.Username | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// if we didn't find nick, search for it | ||||||
|  | 	member, err := b.c.GuildMember(b.guildID, user.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return user.Username | ||||||
|  | 	} | ||||||
|  | 	b.userMemberMap[user.ID] = member | ||||||
|  | 	// only return if nick is set | ||||||
|  | 	if b.userMemberMap[user.ID].Nick != "" { | ||||||
|  | 		return b.userMemberMap[user.ID].Nick | ||||||
|  | 	} | ||||||
|  | 	return user.Username | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *bdiscord) getChannelID(name string) string { | func (b *bdiscord) getChannelID(name string) string { | ||||||
| @@ -151,3 +276,85 @@ func (b *bdiscord) getChannelName(id string) string { | |||||||
| 	} | 	} | ||||||
| 	return "" | 	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 { | ||||||
|  | 	var err error | ||||||
|  | 	re := regexp.MustCompile("<#[0-9]+>") | ||||||
|  | 	text = re.ReplaceAllStringFunc(text, func(m string) string { | ||||||
|  | 		channel := b.getChannelName(m[2 : len(m)-1]) | ||||||
|  | 		// if at first don't succeed, try again | ||||||
|  | 		if channel == "" { | ||||||
|  | 			b.Channels, err = b.c.GuildChannels(b.guildID) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return "#unknownchannel" | ||||||
|  | 			} | ||||||
|  | 			channel = b.getChannelName(m[2 : len(m)-1]) | ||||||
|  | 			return "#" + channel | ||||||
|  | 		} | ||||||
|  | 		return "#" + channel | ||||||
|  | 	}) | ||||||
|  | 	return text | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 { | ||||||
|  | 	// <: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) { | ||||||
|  | 	webhookURLSplit := strings.Split(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 != "" { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	for _, channel := range b.channelInfoMap { | ||||||
|  | 		if channel.Options.WebhookURL != "" { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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) | ||||||
|  | 		if wID == id { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, channel := range b.channelInfoMap { | ||||||
|  | 		if channel.Options.WebhookURL != "" { | ||||||
|  | 			wID, _ := b.splitURL(channel.Options.WebhookURL) | ||||||
|  | 			if wID == id { | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,20 +1,20 @@ | |||||||
| package bgitter | package bgitter | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/42wim/go-gitter" | 	"fmt" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/sromku/go-gitter" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Bgitter struct { | type Bgitter struct { | ||||||
| 	c        *gitter.Gitter | 	c       *gitter.Gitter | ||||||
| 	Config   *config.Protocol | 	Config  *config.Protocol | ||||||
| 	Remote   chan config.Message | 	Remote  chan config.Message | ||||||
| 	protocol string | 	Account string | ||||||
| 	origin   string | 	Users   []gitter.User | ||||||
| 	Users    []gitter.User | 	Rooms   []gitter.Room | ||||||
| 	Rooms    []gitter.Room |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog *log.Entry | var flog *log.Entry | ||||||
| @@ -24,12 +24,11 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bgitter { | func New(cfg config.Protocol, account string, c chan config.Message) *Bgitter { | ||||||
| 	b := &Bgitter{} | 	b := &Bgitter{} | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	b.protocol = protocol | 	b.Account = account | ||||||
| 	b.origin = origin |  | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -47,16 +46,21 @@ func (b *Bgitter) Connect() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bgitter) FullOrigin() string { | func (b *Bgitter) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin | 	return nil | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bgitter) JoinChannel(channel string) error { | func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	room := channel | 	roomID, err := b.c.GetRoomId(channel.Name) | ||||||
| 	roomID := b.getRoomID(room) | 	if err != nil { | ||||||
| 	if roomID == "" { | 		return fmt.Errorf("Could not find roomID for %v. Please create the room on gitter.im", channel.Name) | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
|  | 	room, err := b.c.GetRoom(roomID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.Rooms = append(b.Rooms, *room) | ||||||
| 	user, err := b.c.GetUser() | 	user, err := b.c.GetUser() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -71,36 +75,28 @@ func (b *Bgitter) JoinChannel(channel string) error { | |||||||
| 	go b.c.Listen(stream) | 	go b.c.Listen(stream) | ||||||
|  |  | ||||||
| 	go func(stream *gitter.Stream, room string) { | 	go func(stream *gitter.Stream, room string) { | ||||||
| 		for { | 		for event := range stream.Event { | ||||||
| 			event := <-stream.Event |  | ||||||
| 			switch ev := event.Data.(type) { | 			switch ev := event.Data.(type) { | ||||||
| 			case *gitter.MessageReceived: | 			case *gitter.MessageReceived: | ||||||
| 				// check for ZWSP to see if it's not an echo | 				// check for ZWSP to see if it's not an echo | ||||||
| 				if !strings.HasSuffix(ev.Message.Text, "") { | 				if !strings.HasSuffix(ev.Message.Text, "") { | ||||||
| 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.FullOrigin()) | 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) | ||||||
| 					b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, | 					rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, | ||||||
| 						Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(ev.Message.From.Username)} | 						Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID} | ||||||
|  | 					if strings.HasPrefix(ev.Message.Text, "@"+ev.Message.From.Username) { | ||||||
|  | 						rmsg.Event = config.EVENT_USER_ACTION | ||||||
|  | 						rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1) | ||||||
|  | 					} | ||||||
|  | 					b.Remote <- rmsg | ||||||
| 				} | 				} | ||||||
| 			case *gitter.GitterConnectionClosed: | 			case *gitter.GitterConnectionClosed: | ||||||
| 				flog.Errorf("connection with gitter closed for room %s", room) | 				flog.Errorf("connection with gitter closed for room %s", room) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}(stream, room) | 	}(stream, room.Name) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bgitter) Name() string { |  | ||||||
| 	return b.protocol + "." + b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bgitter) Protocol() string { |  | ||||||
| 	return b.protocol |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bgitter) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bgitter) Send(msg config.Message) error { | func (b *Bgitter) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	roomID := b.getRoomID(msg.Channel) | 	roomID := b.getRoomID(msg.Channel) | ||||||
| @@ -108,9 +104,8 @@ func (b *Bgitter) Send(msg config.Message) error { | |||||||
| 		flog.Errorf("Could not find roomID for %v", msg.Channel) | 		flog.Errorf("Could not find roomID for %v", msg.Channel) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	nick := config.GetNick(&msg, b.Config) |  | ||||||
| 	// add ZWSP because gitter echoes our own messages | 	// add ZWSP because gitter echoes our own messages | ||||||
| 	return b.c.SendMessage(roomID, nick+msg.Text+" ") | 	return b.c.SendMessage(roomID, msg.Username+msg.Text+" ") | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bgitter) getRoomID(channel string) string { | func (b *Bgitter) getRoomID(channel string) string { | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | /* | ||||||
| func tableformatter(nicks []string, nicksPerRow int, continued bool) string { | func tableformatter(nicks []string, nicksPerRow int, continued bool) string { | ||||||
| 	result := "|IRC users" | 	result := "|IRC users" | ||||||
| 	if continued { | 	if continued { | ||||||
| @@ -29,6 +30,7 @@ func tableformatter(nicks []string, nicksPerRow int, continued bool) string { | |||||||
| 	} | 	} | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
|  | */ | ||||||
|  |  | ||||||
| func plainformatter(nicks []string, nicksPerRow int) string { | func plainformatter(nicks []string, nicksPerRow int) string { | ||||||
| 	return strings.Join(nicks, ", ") + " currently on IRC" | 	return strings.Join(nicks, ", ") + " currently on IRC" | ||||||
|   | |||||||
| @@ -3,10 +3,15 @@ package birc | |||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"github.com/42wim/go-ircevent" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/paulrosania/go-charset/charset" | ||||||
|  | 	_ "github.com/paulrosania/go-charset/data" | ||||||
|  | 	"github.com/saintfish/chardet" | ||||||
| 	ircm "github.com/sorcix/irc" | 	ircm "github.com/sorcix/irc" | ||||||
| 	"github.com/thoj/go-ircevent" | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"sort" | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| @@ -15,15 +20,15 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type Birc struct { | type Birc struct { | ||||||
| 	i         *irc.Connection | 	i               *irc.Connection | ||||||
| 	Nick      string | 	Nick            string | ||||||
| 	names     map[string][]string | 	names           map[string][]string | ||||||
| 	Config    *config.Protocol | 	Config          *config.Protocol | ||||||
| 	origin    string | 	Remote          chan config.Message | ||||||
| 	protocol  string | 	connected       chan struct{} | ||||||
| 	Remote    chan config.Message | 	Local           chan config.Message // local queue for flood control | ||||||
| 	connected chan struct{} | 	Account         string | ||||||
| 	Local     chan config.Message // local queue for flood control | 	FirstConnection bool | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog *log.Entry | var flog *log.Entry | ||||||
| @@ -33,14 +38,13 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Birc { | func New(cfg config.Protocol, account string, c chan config.Message) *Birc { | ||||||
| 	b := &Birc{} | 	b := &Birc{} | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.Nick = b.Config.Nick | 	b.Nick = b.Config.Nick | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	b.names = make(map[string][]string) | 	b.names = make(map[string][]string) | ||||||
| 	b.origin = origin | 	b.Account = account | ||||||
| 	b.protocol = protocol |  | ||||||
| 	b.connected = make(chan struct{}) | 	b.connected = make(chan struct{}) | ||||||
| 	if b.Config.MessageDelay == 0 { | 	if b.Config.MessageDelay == 0 { | ||||||
| 		b.Config.MessageDelay = 1300 | 		b.Config.MessageDelay = 1300 | ||||||
| @@ -48,21 +52,25 @@ func New(cfg config.Protocol, origin string, c chan config.Message) *Birc { | |||||||
| 	if b.Config.MessageQueue == 0 { | 	if b.Config.MessageQueue == 0 { | ||||||
| 		b.Config.MessageQueue = 30 | 		b.Config.MessageQueue = 30 | ||||||
| 	} | 	} | ||||||
| 	b.Local = make(chan config.Message, b.Config.MessageQueue+10) | 	if b.Config.MessageLength == 0 { | ||||||
|  | 		b.Config.MessageLength = 400 | ||||||
|  | 	} | ||||||
|  | 	b.FirstConnection = true | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) Command(msg *config.Message) string { | func (b *Birc) Command(msg *config.Message) string { | ||||||
| 	switch msg.Text { | 	switch msg.Text { | ||||||
| 	case "!users": | 	case "!users": | ||||||
|  | 		b.i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) | ||||||
| 		b.i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) | 		b.i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) | ||||||
| 		b.i.SendRaw("NAMES " + msg.Channel) | 		b.i.SendRaw("NAMES " + msg.Channel) | ||||||
| 		b.i.ClearCallback(ircm.RPL_ENDOFNAMES) |  | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) Connect() error { | func (b *Birc) Connect() error { | ||||||
|  | 	b.Local = make(chan config.Message, b.Config.MessageQueue+10) | ||||||
| 	flog.Infof("Connecting %s", b.Config.Server) | 	flog.Infof("Connecting %s", b.Config.Server) | ||||||
| 	i := irc.IRC(b.Config.Nick, b.Config.Nick) | 	i := irc.IRC(b.Config.Nick, b.Config.Nick) | ||||||
| 	if log.GetLevel() == log.DebugLevel { | 	if log.GetLevel() == log.DebugLevel { | ||||||
| @@ -73,6 +81,8 @@ func (b *Birc) Connect() error { | |||||||
| 	i.SASLLogin = b.Config.NickServNick | 	i.SASLLogin = b.Config.NickServNick | ||||||
| 	i.SASLPassword = b.Config.NickServPassword | 	i.SASLPassword = b.Config.NickServPassword | ||||||
| 	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify} | 	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify} | ||||||
|  | 	i.KeepAlive = time.Minute | ||||||
|  | 	i.PingFreq = time.Minute | ||||||
| 	if b.Config.Password != "" { | 	if b.Config.Password != "" { | ||||||
| 		i.Password = b.Config.Password | 		i.Password = b.Config.Password | ||||||
| 	} | 	} | ||||||
| @@ -89,47 +99,48 @@ func (b *Birc) Connect() error { | |||||||
| 		return fmt.Errorf("connection timed out") | 		return fmt.Errorf("connection timed out") | ||||||
| 	} | 	} | ||||||
| 	i.Debug = false | 	i.Debug = false | ||||||
|  | 	// clear on reconnects | ||||||
|  | 	i.ClearCallback(ircm.RPL_WELCOME) | ||||||
|  | 	i.AddCallback(ircm.RPL_WELCOME, func(event *irc.Event) { | ||||||
|  | 		b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||||
|  | 		// set our correct nick on reconnect if necessary | ||||||
|  | 		b.Nick = event.Nick | ||||||
|  | 	}) | ||||||
|  | 	go i.Loop() | ||||||
| 	go b.doSend() | 	go b.doSend() | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) FullOrigin() string { | func (b *Birc) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin | 	//b.i.Disconnect() | ||||||
| } | 	close(b.Local) | ||||||
|  |  | ||||||
| func (b *Birc) JoinChannel(channel string) error { |  | ||||||
| 	b.i.Join(channel) |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) Name() string { | func (b *Birc) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	return b.protocol + "." + b.origin | 	if channel.Options.Key != "" { | ||||||
| } | 		flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name) | ||||||
|  | 		b.i.Join(channel.Name + " " + channel.Options.Key) | ||||||
| func (b *Birc) Protocol() string { | 	} else { | ||||||
| 	return b.protocol | 		b.i.Join(channel.Name) | ||||||
| } | 	} | ||||||
|  | 	return nil | ||||||
| func (b *Birc) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) Send(msg config.Message) error { | func (b *Birc) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	if msg.FullOrigin == b.FullOrigin() { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	if strings.HasPrefix(msg.Text, "!") { | 	if strings.HasPrefix(msg.Text, "!") { | ||||||
| 		b.Command(&msg) | 		b.Command(&msg) | ||||||
| 		return nil |  | ||||||
| 	} | 	} | ||||||
| 	nick := config.GetNick(&msg, b.Config) |  | ||||||
| 	for _, text := range strings.Split(msg.Text, "\n") { | 	for _, text := range strings.Split(msg.Text, "\n") { | ||||||
|  | 		if len(text) > b.Config.MessageLength { | ||||||
|  | 			text = text[:b.Config.MessageLength] + " <message clipped>" | ||||||
|  | 		} | ||||||
| 		if len(b.Local) < b.Config.MessageQueue { | 		if len(b.Local) < b.Config.MessageQueue { | ||||||
| 			if len(b.Local) == b.Config.MessageQueue-1 { | 			if len(b.Local) == b.Config.MessageQueue-1 { | ||||||
| 				text = text + " <message clipped>" | 				text = text + " <message clipped>" | ||||||
| 			} | 			} | ||||||
| 			b.Local <- config.Message{Text: text, Username: nick, Channel: msg.Channel} | 			b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel, Event: msg.Event} | ||||||
| 		} else { | 		} else { | ||||||
| 			flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | 			flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||||
| 		} | 		} | ||||||
| @@ -139,10 +150,14 @@ func (b *Birc) Send(msg config.Message) error { | |||||||
|  |  | ||||||
| func (b *Birc) doSend() { | func (b *Birc) doSend() { | ||||||
| 	rate := time.Millisecond * time.Duration(b.Config.MessageDelay) | 	rate := time.Millisecond * time.Duration(b.Config.MessageDelay) | ||||||
| 	throttle := time.Tick(rate) | 	throttle := time.NewTicker(rate) | ||||||
| 	for msg := range b.Local { | 	for msg := range b.Local { | ||||||
| 		<-throttle | 		<-throttle.C | ||||||
| 		b.i.Privmsg(msg.Channel, msg.Username+msg.Text) | 		if msg.Event == config.EVENT_USER_ACTION { | ||||||
|  | 			b.i.Action(msg.Channel, msg.Username+msg.Text) | ||||||
|  | 		} else { | ||||||
|  | 			b.i.Privmsg(msg.Channel, msg.Username+msg.Text) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -153,13 +168,15 @@ func (b *Birc) endNames(event *irc.Event) { | |||||||
| 	continued := false | 	continued := false | ||||||
| 	for len(b.names[channel]) > maxNamesPerPost { | 	for len(b.names[channel]) > maxNamesPerPost { | ||||||
| 		b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), | 		b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), | ||||||
| 			Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | 			Channel: channel, Account: b.Account} | ||||||
| 		b.names[channel] = b.names[channel][maxNamesPerPost:] | 		b.names[channel] = b.names[channel][maxNamesPerPost:] | ||||||
| 		continued = true | 		continued = true | ||||||
| 	} | 	} | ||||||
| 	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, | 	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued), | ||||||
| 		Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | 		Channel: channel, Account: b.Account} | ||||||
| 	b.names[channel] = nil | 	b.names[channel] = nil | ||||||
|  | 	b.i.ClearCallback(ircm.RPL_NAMREPLY) | ||||||
|  | 	b.i.ClearCallback(ircm.RPL_ENDOFNAMES) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) handleNewConnection(event *irc.Event) { | func (b *Birc) handleNewConnection(event *irc.Event) { | ||||||
| @@ -169,18 +186,43 @@ func (b *Birc) handleNewConnection(event *irc.Event) { | |||||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | ||||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | ||||||
| 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) | 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) | ||||||
| 	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) |  | ||||||
| 	i.AddCallback(ircm.NOTICE, b.handleNotice) | 	i.AddCallback(ircm.NOTICE, b.handleNotice) | ||||||
| 	//i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) | 	//i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) | ||||||
| 	i.AddCallback("PING", func(e *irc.Event) { | 	i.AddCallback("PING", func(e *irc.Event) { | ||||||
| 		i.SendRaw("PONG :" + e.Message()) | 		i.SendRaw("PONG :" + e.Message()) | ||||||
| 		flog.Debugf("PING/PONG") | 		flog.Debugf("PING/PONG") | ||||||
| 	}) | 	}) | ||||||
|  | 	i.AddCallback("JOIN", b.handleJoinPart) | ||||||
|  | 	i.AddCallback("PART", b.handleJoinPart) | ||||||
|  | 	i.AddCallback("QUIT", b.handleJoinPart) | ||||||
|  | 	i.AddCallback("KICK", b.handleJoinPart) | ||||||
| 	i.AddCallback("*", b.handleOther) | 	i.AddCallback("*", b.handleOther) | ||||||
| 	// we are now fully connected | 	// we are now fully connected | ||||||
| 	b.connected <- struct{}{} | 	b.connected <- struct{}{} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleJoinPart(event *irc.Event) { | ||||||
|  | 	channel := event.Arguments[0] | ||||||
|  | 	if event.Code == "KICK" { | ||||||
|  | 		flog.Infof("Got kicked from %s by %s", channel, event.Nick) | ||||||
|  | 		b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: channel, Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if event.Code == "QUIT" { | ||||||
|  | 		if event.Nick == b.Nick && strings.Contains(event.Raw, "Ping timeout") { | ||||||
|  | 			flog.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.Nick != b.Nick { | ||||||
|  | 		flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||||
|  | 		b.Remote <- config.Message{Username: "system", Text: event.Nick + " " + strings.ToLower(event.Code) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	flog.Debugf("handle %#v", event) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (b *Birc) handleNotice(event *irc.Event) { | func (b *Birc) handleNotice(event *irc.Event) { | ||||||
| 	if strings.Contains(event.Message(), "This nickname is registered") && event.Nick == b.Config.NickServNick { | 	if strings.Contains(event.Message(), "This nickname is registered") && event.Nick == b.Config.NickServNick { | ||||||
| 		b.i.Privmsg(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword) | 		b.i.Privmsg(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword) | ||||||
| @@ -198,6 +240,11 @@ func (b *Birc) handleOther(event *irc.Event) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) handlePrivMsg(event *irc.Event) { | func (b *Birc) handlePrivMsg(event *irc.Event) { | ||||||
|  | 	b.Nick = b.i.GetNick() | ||||||
|  | 	// freenode doesn't send 001 as first reply | ||||||
|  | 	if event.Code == "NOTICE" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	// don't forward queries to the bot | 	// don't forward queries to the bot | ||||||
| 	if event.Arguments[0] == b.Nick { | 	if event.Arguments[0] == b.Nick { | ||||||
| 		return | 		return | ||||||
| @@ -206,17 +253,42 @@ func (b *Birc) handlePrivMsg(event *irc.Event) { | |||||||
| 	if event.Nick == b.Nick { | 	if event.Nick == b.Nick { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	rmsg := config.Message{Username: event.Nick, Channel: event.Arguments[0], Account: b.Account, UserID: event.User + "@" + event.Host} | ||||||
| 	flog.Debugf("handlePrivMsg() %s %s %#v", event.Nick, event.Message(), event) | 	flog.Debugf("handlePrivMsg() %s %s %#v", event.Nick, event.Message(), event) | ||||||
| 	msg := "" | 	msg := "" | ||||||
| 	if event.Code == "CTCP_ACTION" { | 	if event.Code == "CTCP_ACTION" { | ||||||
| 		msg = event.Nick + " " | 		//	msg = event.Nick + " " | ||||||
|  | 		rmsg.Event = config.EVENT_USER_ACTION | ||||||
| 	} | 	} | ||||||
| 	msg += event.Message() | 	msg += event.Message() | ||||||
| 	// strip IRC colors | 	// strip IRC colors | ||||||
| 	re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`) | 	re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`) | ||||||
| 	msg = re.ReplaceAllString(msg, "") | 	msg = re.ReplaceAllString(msg, "") | ||||||
| 	flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.FullOrigin()) |  | ||||||
| 	b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | 	// detect what were sending so that we convert it to utf-8 | ||||||
|  | 	detector := chardet.NewTextDetector() | ||||||
|  | 	result, err := detector.DetectBest([]byte(msg)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Infof("detection failed for msg: %#v", msg) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence) | ||||||
|  | 	var r io.Reader | ||||||
|  | 	r, err = charset.NewReader(result.Charset, strings.NewReader(msg)) | ||||||
|  | 	// if we're not sure, just pick ISO-8859-1 | ||||||
|  | 	if result.Confidence < 80 { | ||||||
|  | 		r, err = charset.NewReader("ISO-8859-1", strings.NewReader(msg)) | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Errorf("charset to utf-8 conversion failed: %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	output, _ := ioutil.ReadAll(r) | ||||||
|  | 	msg = string(output) | ||||||
|  |  | ||||||
|  | 	flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.Account) | ||||||
|  | 	rmsg.Text = msg | ||||||
|  | 	b.Remote <- rmsg | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Birc) handleTopicWhoTime(event *irc.Event) { | func (b *Birc) handleTopicWhoTime(event *irc.Event) { | ||||||
|   | |||||||
							
								
								
									
										133
									
								
								bridge/matrix/matrix.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								bridge/matrix/matrix.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | package bmatrix | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"regexp" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	matrix "github.com/matrix-org/gomatrix" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Bmatrix struct { | ||||||
|  | 	mc      *matrix.Client | ||||||
|  | 	Config  *config.Protocol | ||||||
|  | 	Remote  chan config.Message | ||||||
|  | 	Account string | ||||||
|  | 	UserID  string | ||||||
|  | 	RoomMap map[string]string | ||||||
|  | 	sync.RWMutex | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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{} | ||||||
|  | 	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, "", "") | ||||||
|  | 	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, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Debugf("%#v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.mc.SetCredentials(resp.UserID, resp.AccessToken) | ||||||
|  | 	b.UserID = resp.UserID | ||||||
|  | 	flog.Info("Connection succeeded") | ||||||
|  | 	go b.handlematrix() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmatrix) Disconnect() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error { | ||||||
|  | 	resp, err := b.mc.JoinRoom(channel.Name, "", nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.Lock() | ||||||
|  | 	b.RoomMap[resp.RoomID] = channel.Name | ||||||
|  | 	b.Unlock() | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmatrix) Send(msg config.Message) error { | ||||||
|  | 	flog.Debugf("Receiving %#v", msg) | ||||||
|  | 	channel := b.getRoomID(msg.Channel) | ||||||
|  | 	flog.Debugf("Sending to channel %s", channel) | ||||||
|  | 	if msg.Event == config.EVENT_USER_ACTION { | ||||||
|  | 		b.mc.SendMessageEvent(channel, "m.room.message", | ||||||
|  | 			matrix.TextMessage{"m.emote", msg.Username + msg.Text}) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	b.mc.SendText(channel, msg.Username+msg.Text) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmatrix) getRoomID(channel string) string { | ||||||
|  | 	b.RLock() | ||||||
|  | 	defer b.RUnlock() | ||||||
|  | 	for ID, name := range b.RoomMap { | ||||||
|  | 		if name == channel { | ||||||
|  | 			return ID | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	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.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) | ||||||
|  | 	}) | ||||||
|  | 	go func() { | ||||||
|  | 		for { | ||||||
|  | 			if err := b.mc.Sync(); err != nil { | ||||||
|  | 				flog.Println("Sync() returned ", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -1,10 +1,12 @@ | |||||||
| package bmattermost | package bmattermost | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/matterclient" | 	"github.com/42wim/matterbridge/matterclient" | ||||||
| 	"github.com/42wim/matterbridge/matterhook" | 	"github.com/42wim/matterbridge/matterhook" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type MMhook struct { | type MMhook struct { | ||||||
| @@ -12,26 +14,24 @@ type MMhook struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type MMapi struct { | type MMapi struct { | ||||||
| 	mc            *matterclient.MMClient | 	mc    *matterclient.MMClient | ||||||
| 	mmMap         map[string]string | 	mmMap map[string]string | ||||||
| 	mmIgnoreNicks []string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type MMMessage struct { | type MMMessage struct { | ||||||
| 	Text     string | 	Text     string | ||||||
| 	Channel  string | 	Channel  string | ||||||
| 	Username string | 	Username string | ||||||
|  | 	UserID   string | ||||||
| } | } | ||||||
|  |  | ||||||
| type Bmattermost struct { | type Bmattermost struct { | ||||||
| 	MMhook | 	MMhook | ||||||
| 	MMapi | 	MMapi | ||||||
| 	Config   *config.Protocol | 	Config  *config.Protocol | ||||||
| 	Remote   chan config.Message | 	Remote  chan config.Message | ||||||
| 	name     string | 	TeamId  string | ||||||
| 	origin   string | 	Account string | ||||||
| 	protocol string |  | ||||||
| 	TeamId   string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog *log.Entry | var flog *log.Entry | ||||||
| @@ -41,13 +41,11 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost { | func New(cfg config.Protocol, account string, c chan config.Message) *Bmattermost { | ||||||
| 	b := &Bmattermost{} | 	b := &Bmattermost{} | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.origin = origin |  | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	b.protocol = "mattermost" | 	b.Account = account | ||||||
| 	b.name = cfg.Name |  | ||||||
| 	b.mmMap = make(map[string]string) | 	b.mmMap = make(map[string]string) | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
| @@ -57,69 +55,82 @@ func (b *Bmattermost) Command(cmd string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) Connect() error { | func (b *Bmattermost) Connect() error { | ||||||
| 	if !b.Config.UseAPI { | 	if b.Config.WebhookBindAddress != "" { | ||||||
| 		flog.Info("Connecting webhooks") | 		if b.Config.WebhookURL != "" { | ||||||
| 		b.mh = matterhook.New(b.Config.URL, | 			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.Login != "" { | ||||||
|  | 			flog.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}) | ||||||
|  | 		} | ||||||
|  | 		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, | 			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||||
| 				BindAddress: b.Config.BindAddress}) | 				DisableServer: true}) | ||||||
| 	} else { | 		if b.Config.Login != "" { | ||||||
| 		b.mc = matterclient.New(b.Config.Login, b.Config.Password, | 			flog.Info("Connecting using login/password (receiving)") | ||||||
| 			b.Config.Team, b.Config.Server) | 			err := b.apiLogin() | ||||||
| 		b.mc.SkipTLSVerify = b.Config.SkipTLSVerify | 			if err != nil { | ||||||
| 		b.mc.NoTLS = b.Config.NoTLS | 				return err | ||||||
| 		flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server) | 			} | ||||||
| 		err := b.mc.Login() | 			go b.handleMatter() | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} else if b.Config.Login != "" { | ||||||
|  | 		flog.Info("Connecting using login/password (sending and receiving)") | ||||||
|  | 		err := b.apiLogin() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		flog.Info("Connection succeeded") | 		go b.handleMatter() | ||||||
| 		b.TeamId = b.mc.GetTeamId() | 	} | ||||||
| 		go b.mc.WsReceiver() | 	if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" { | ||||||
|  | 		return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server/Team configured.") | ||||||
| 	} | 	} | ||||||
| 	go b.handleMatter() |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) FullOrigin() string { | func (b *Bmattermost) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) JoinChannel(channel string) error { | func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	// we can only join channels using the API | 	// we can only join channels using the API | ||||||
| 	if b.Config.UseAPI { | 	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { | ||||||
| 		return b.mc.JoinChannel(b.mc.GetChannelId(channel, "")) | 		return b.mc.JoinChannel(b.mc.GetChannelId(channel.Name, "")) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) Name() string { |  | ||||||
| 	return b.protocol + "." + b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bmattermost) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bmattermost) Protocol() string { |  | ||||||
| 	return b.protocol |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bmattermost) Send(msg config.Message) error { | func (b *Bmattermost) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	nick := config.GetNick(&msg, b.Config) | 	if msg.Event == config.EVENT_USER_ACTION { | ||||||
|  | 		msg.Text = "*" + msg.Text + "*" | ||||||
|  | 	} | ||||||
|  | 	nick := msg.Username | ||||||
| 	message := msg.Text | 	message := msg.Text | ||||||
| 	channel := msg.Channel | 	channel := msg.Channel | ||||||
|  |  | ||||||
| 	if b.Config.PrefixMessagesWithNick { | 	if b.Config.PrefixMessagesWithNick { | ||||||
| 		/*if IsMarkup(message) { | 		message = nick + message | ||||||
| 			message = nick + "\n\n" + message |  | ||||||
| 		} else { |  | ||||||
| 		*/ |  | ||||||
| 		message = nick + " " + message |  | ||||||
| 		//} |  | ||||||
| 	} | 	} | ||||||
| 	if !b.Config.UseAPI { | 	if b.Config.WebhookURL != "" { | ||||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||||
|  | 		matterMessage.IconURL = msg.Avatar | ||||||
| 		matterMessage.Channel = channel | 		matterMessage.Channel = channel | ||||||
| 		matterMessage.UserName = nick | 		matterMessage.UserName = nick | ||||||
| 		matterMessage.Type = "" | 		matterMessage.Type = "" | ||||||
| @@ -136,31 +147,58 @@ func (b *Bmattermost) Send(msg config.Message) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) handleMatter() { | func (b *Bmattermost) handleMatter() { | ||||||
| 	flog.Debugf("Choosing API based Mattermost connection: %t", b.Config.UseAPI) |  | ||||||
| 	mchan := make(chan *MMMessage) | 	mchan := make(chan *MMMessage) | ||||||
| 	if b.Config.UseAPI { | 	if b.Config.WebhookBindAddress != "" { | ||||||
| 		go b.handleMatterClient(mchan) | 		flog.Debugf("Choosing webhooks based receiving") | ||||||
| 	} else { |  | ||||||
| 		go b.handleMatterHook(mchan) | 		go b.handleMatterHook(mchan) | ||||||
|  | 	} else { | ||||||
|  | 		flog.Debugf("Choosing login/password based receiving") | ||||||
|  | 		go b.handleMatterClient(mchan) | ||||||
| 	} | 	} | ||||||
| 	for message := range mchan { | 	for message := range mchan { | ||||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin()) | 		rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID} | ||||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | 		text, ok := b.replaceAction(message.Text) | ||||||
|  | 		if ok { | ||||||
|  | 			rmsg.Event = config.EVENT_USER_ACTION | ||||||
|  | 		} | ||||||
|  | 		rmsg.Text = text | ||||||
|  | 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||||
|  | 		b.Remote <- rmsg | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||||
| 	for message := range b.mc.MessageChan { | 	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 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		// do not post our own messages back to irc | 		// do not post our own messages back to irc | ||||||
| 		// only listen to message from our team | 		// only listen to message from our team | ||||||
| 		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.TeamId == b.TeamId { | 		if (message.Raw.Event == "posted" || message.Raw.Event == "post_edited") && | ||||||
|  | 			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) | 			flog.Debugf("Receiving from matterclient %#v", message) | ||||||
| 			m := &MMMessage{} | 			m := &MMMessage{} | ||||||
|  | 			m.UserID = message.UserID | ||||||
| 			m.Username = message.Username | 			m.Username = message.Username | ||||||
| 			m.Channel = message.Channel | 			m.Channel = message.Channel | ||||||
| 			m.Text = message.Text | 			m.Text = message.Text | ||||||
| 			if len(message.Post.Filenames) > 0 { | 			if message.Raw.Event == "post_edited" && !b.Config.EditDisable { | ||||||
| 				for _, link := range b.mc.GetPublicLinks(message.Post.Filenames) { | 				m.Text = message.Text + b.Config.EditSuffix | ||||||
|  | 			} | ||||||
|  | 			if len(message.Post.FileIds) > 0 { | ||||||
|  | 				for _, link := range b.mc.GetFileLinks(message.Post.FileIds) { | ||||||
| 					m.Text = m.Text + "\n" + link | 					m.Text = m.Text + "\n" + link | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @@ -174,9 +212,34 @@ func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) { | |||||||
| 		message := b.mh.Receive() | 		message := b.mh.Receive() | ||||||
| 		flog.Debugf("Receiving from matterhook %#v", message) | 		flog.Debugf("Receiving from matterhook %#v", message) | ||||||
| 		m := &MMMessage{} | 		m := &MMMessage{} | ||||||
|  | 		m.UserID = message.UserID | ||||||
| 		m.Username = message.UserName | 		m.Username = message.UserName | ||||||
| 		m.Text = message.Text | 		m.Text = message.Text | ||||||
| 		m.Channel = message.ChannelName | 		m.Channel = message.ChannelName | ||||||
| 		mchan <- m | 		mchan <- m | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) apiLogin() error { | ||||||
|  | 	b.mc = matterclient.New(b.Config.Login, b.Config.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) | ||||||
|  | 	err := b.mc.Login() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	flog.Info("Connection succeeded") | ||||||
|  | 	b.TeamId = b.mc.GetTeamId() | ||||||
|  | 	go b.mc.WsReceiver() | ||||||
|  | 	go b.mc.StatusLoop() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) replaceAction(text string) (string, bool) { | ||||||
|  | 	if strings.HasPrefix(text, "*") && strings.HasSuffix(text, "*") { | ||||||
|  | 		return strings.Replace(text, "*", "", -1), true | ||||||
|  | 	} | ||||||
|  | 	return text, false | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								bridge/rocketchat/rocketchat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								bridge/rocketchat/rocketchat.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | package brocketchat | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	"github.com/42wim/matterbridge/hook/rockethook" | ||||||
|  | 	"github.com/42wim/matterbridge/matterhook" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type MMhook struct { | ||||||
|  | 	mh *matterhook.Client | ||||||
|  | 	rh *rockethook.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Brocketchat struct { | ||||||
|  | 	MMhook | ||||||
|  | 	Config  *config.Protocol | ||||||
|  | 	Remote  chan config.Message | ||||||
|  | 	Account string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 (b *Brocketchat) Command(cmd string) string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Brocketchat) Connect() error { | ||||||
|  | 	flog.Info("Connecting webhooks") | ||||||
|  | 	b.mh = matterhook.New(b.Config.WebhookURL, | ||||||
|  | 		matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||||
|  | 			DisableServer: true}) | ||||||
|  | 	b.rh = rockethook.New(b.Config.WebhookURL, rockethook.Config{BindAddress: b.Config.WebhookBindAddress}) | ||||||
|  | 	go b.handleRocketHook() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Brocketchat) Disconnect() error { | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Brocketchat) Send(msg config.Message) error { | ||||||
|  | 	flog.Debugf("Receiving %#v", msg) | ||||||
|  | 	matterMessage := matterhook.OMessage{IconURL: b.Config.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) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Brocketchat) handleRocketHook() { | ||||||
|  | 	for { | ||||||
|  | 		message := b.rh.Receive() | ||||||
|  | 		flog.Debugf("Receiving from rockethook %#v", message) | ||||||
|  | 		// do not loop | ||||||
|  | 		if message.UserName == b.Config.Nick { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||||
|  | 		b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account, UserID: message.UserID} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,11 +1,14 @@ | |||||||
| package bslack | package bslack | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/matterhook" | 	"github.com/42wim/matterbridge/matterhook" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
| 	"github.com/nlopes/slack" | 	"github.com/nlopes/slack" | ||||||
|  | 	"html" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @@ -14,6 +17,7 @@ type MMMessage struct { | |||||||
| 	Text     string | 	Text     string | ||||||
| 	Channel  string | 	Channel  string | ||||||
| 	Username string | 	Username string | ||||||
|  | 	UserID   string | ||||||
| 	Raw      *slack.MessageEvent | 	Raw      *slack.MessageEvent | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -25,8 +29,7 @@ type Bslack struct { | |||||||
| 	Plus     bool | 	Plus     bool | ||||||
| 	Remote   chan config.Message | 	Remote   chan config.Message | ||||||
| 	Users    []slack.User | 	Users    []slack.User | ||||||
| 	protocol string | 	Account  string | ||||||
| 	origin   string |  | ||||||
| 	si       *slack.Info | 	si       *slack.Info | ||||||
| 	channels []slack.Channel | 	channels []slack.Channel | ||||||
| } | } | ||||||
| @@ -38,12 +41,11 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bslack { | func New(cfg config.Protocol, account string, c chan config.Message) *Bslack { | ||||||
| 	b := &Bslack{} | 	b := &Bslack{} | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	b.protocol = protocol | 	b.Account = account | ||||||
| 	b.origin = origin |  | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -52,59 +54,89 @@ func (b *Bslack) Command(cmd string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bslack) Connect() error { | func (b *Bslack) Connect() error { | ||||||
| 	flog.Info("Connecting") | 	if b.Config.WebhookBindAddress != "" { | ||||||
| 	if !b.Config.UseAPI { | 		if b.Config.WebhookURL != "" { | ||||||
| 		b.mh = matterhook.New(b.Config.URL, | 			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") | ||||||
| 			matterhook.Config{BindAddress: b.Config.BindAddress}) | 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||||
| 	} else { | 				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.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}) | ||||||
|  | 		} else { | ||||||
|  | 			flog.Info("Connecting using webhookbindaddress (receiving)") | ||||||
|  | 			b.mh = matterhook.New(b.Config.WebhookURL, | ||||||
|  | 				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||||
|  | 					BindAddress: b.Config.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, | ||||||
|  | 				DisableServer: true}) | ||||||
|  | 		if b.Config.Token != "" { | ||||||
|  | 			flog.Info("Connecting using token (receiving)") | ||||||
|  | 			b.sc = slack.New(b.Config.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) | 		b.sc = slack.New(b.Config.Token) | ||||||
| 		b.rtm = b.sc.NewRTM() | 		b.rtm = b.sc.NewRTM() | ||||||
| 		go b.rtm.ManageConnection() | 		go b.rtm.ManageConnection() | ||||||
|  | 		go b.handleSlack() | ||||||
|  | 	} | ||||||
|  | 	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.") | ||||||
| 	} | 	} | ||||||
| 	flog.Info("Connection succeeded") |  | ||||||
| 	go b.handleSlack() |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bslack) FullOrigin() string { | func (b *Bslack) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin | 	return nil | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bslack) JoinChannel(channel string) error { | func (b *Bslack) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	// we can only join channels using the API | 	// we can only join channels using the API | ||||||
| 	if b.Config.UseAPI { | 	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" { | ||||||
| 		_, err := b.sc.JoinChannel(channel) | 		if strings.HasPrefix(b.Config.Token, "xoxb") { | ||||||
|  | 			// TODO check if bot has already joined channel | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		_, err := b.sc.JoinChannel(channel.Name) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			if err.Error() != "name_taken" { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bslack) Name() string { |  | ||||||
| 	return b.protocol + "." + b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bslack) Protocol() string { |  | ||||||
| 	return b.protocol |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bslack) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bslack) Send(msg config.Message) error { | func (b *Bslack) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	if msg.FullOrigin == b.FullOrigin() { | 	if msg.Event == config.EVENT_USER_ACTION { | ||||||
| 		return nil | 		msg.Text = "_" + msg.Text + "_" | ||||||
| 	} | 	} | ||||||
| 	nick := config.GetNick(&msg, b.Config) | 	nick := msg.Username | ||||||
| 	message := msg.Text | 	message := msg.Text | ||||||
| 	channel := msg.Channel | 	channel := msg.Channel | ||||||
| 	if b.Config.PrefixMessagesWithNick { | 	if b.Config.PrefixMessagesWithNick { | ||||||
| 		message = nick + " " + message | 		message = nick + " " + message | ||||||
| 	} | 	} | ||||||
| 	if !b.Config.UseAPI { | 	if b.Config.WebhookURL != "" { | ||||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | 		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||||
| 		matterMessage.Channel = channel | 		matterMessage.Channel = channel | ||||||
| 		matterMessage.UserName = nick | 		matterMessage.UserName = nick | ||||||
| @@ -122,7 +154,7 @@ func (b *Bslack) Send(msg config.Message) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	np := slack.NewPostMessageParameters() | 	np := slack.NewPostMessageParameters() | ||||||
| 	if b.Config.PrefixMessagesWithNick == true { | 	if b.Config.PrefixMessagesWithNick { | ||||||
| 		np.AsUser = true | 		np.AsUser = true | ||||||
| 	} | 	} | ||||||
| 	np.Username = nick | 	np.Username = nick | ||||||
| @@ -130,6 +162,7 @@ func (b *Bslack) Send(msg config.Message) error { | |||||||
| 	if msg.Avatar != "" { | 	if msg.Avatar != "" { | ||||||
| 		np.IconURL = msg.Avatar | 		np.IconURL = msg.Avatar | ||||||
| 	} | 	} | ||||||
|  | 	np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"}) | ||||||
| 	b.sc.PostMessage(schannel.ID, message, np) | 	b.sc.PostMessage(schannel.ID, message, np) | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| @@ -154,35 +187,57 @@ func (b *Bslack) getAvatar(user string) string { | |||||||
|  |  | ||||||
| func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) { | func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) { | ||||||
| 	if b.channels == nil { | 	if b.channels == nil { | ||||||
| 		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.FullOrigin(), name) | 		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name) | ||||||
| 	} | 	} | ||||||
| 	for _, channel := range b.channels { | 	for _, channel := range b.channels { | ||||||
| 		if channel.Name == name { | 		if channel.Name == name { | ||||||
| 			return &channel, nil | 			return &channel, nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil, fmt.Errorf("%s: channel %s not found", b.FullOrigin(), name) | 	return nil, fmt.Errorf("%s: channel %s not found", b.Account, name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) { | ||||||
|  | 	if b.channels == nil { | ||||||
|  | 		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, ID) | ||||||
|  | 	} | ||||||
|  | 	for _, channel := range b.channels { | ||||||
|  | 		if channel.ID == ID { | ||||||
|  | 			return &channel, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("%s: channel %s not found", b.Account, ID) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bslack) handleSlack() { | func (b *Bslack) handleSlack() { | ||||||
| 	flog.Debugf("Choosing API based slack connection: %t", b.Config.UseAPI) |  | ||||||
| 	mchan := make(chan *MMMessage) | 	mchan := make(chan *MMMessage) | ||||||
| 	if b.Config.UseAPI { | 	if b.Config.WebhookBindAddress != "" { | ||||||
| 		go b.handleSlackClient(mchan) | 		flog.Debugf("Choosing webhooks based receiving") | ||||||
| 	} else { |  | ||||||
| 		go b.handleMatterHook(mchan) | 		go b.handleMatterHook(mchan) | ||||||
|  | 	} else { | ||||||
|  | 		flog.Debugf("Choosing token based receiving") | ||||||
|  | 		go b.handleSlackClient(mchan) | ||||||
| 	} | 	} | ||||||
| 	time.Sleep(time.Second) | 	time.Sleep(time.Second) | ||||||
| 	flog.Debug("Start listening for Slack messages") | 	flog.Debug("Start listening for Slack messages") | ||||||
| 	for message := range mchan { | 	for message := range mchan { | ||||||
| 		// do not send messages from ourself | 		// do not send messages from ourself | ||||||
| 		if message.Username == b.si.User.Name { | 		if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if message.Text == "" || message.Username == "" { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		texts := strings.Split(message.Text, "\n") | 		texts := strings.Split(message.Text, "\n") | ||||||
| 		for _, text := range texts { | 		for _, text := range texts { | ||||||
| 			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin()) | 			text = b.replaceURL(text) | ||||||
| 			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(message.Username)} | 			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} | ||||||
|  | 			if message.Raw.SubType == "me_message" { | ||||||
|  | 				msg.Event = config.EVENT_USER_ACTION | ||||||
|  | 			} | ||||||
|  | 			b.Remote <- msg | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -195,29 +250,74 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | |||||||
| 			// ignore first message | 			// ignore first message | ||||||
| 			if count > 0 { | 			if count > 0 { | ||||||
| 				flog.Debugf("Receiving from slackclient %#v", ev) | 				flog.Debugf("Receiving from slackclient %#v", ev) | ||||||
| 				//ev.ReplyTo | 				if len(ev.Attachments) > 0 { | ||||||
| 				channel, err := b.rtm.GetChannelInfo(ev.Channel) | 					// skip messages we made ourselves | ||||||
| 				if err != nil { | 					if ev.Attachments[0].CallbackID == "matterbridge" { | ||||||
| 					continue | 						continue | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 				user, err := b.rtm.GetUserInfo(ev.User) | 				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 | ||||||
|  | 				} | ||||||
|  | 				// use our own func because rtm.GetChannelInfo doesn't work for private channels | ||||||
|  | 				channel, err := b.getChannelByID(ev.Channel) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 				m := &MMMessage{} | 				m := &MMMessage{} | ||||||
| 				m.Username = user.Name | 				if ev.BotID == "" { | ||||||
|  | 					user, err := b.rtm.GetUserInfo(ev.User) | ||||||
|  | 					if err != nil { | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					m.UserID = user.ID | ||||||
|  | 					m.Username = user.Name | ||||||
|  | 				} | ||||||
| 				m.Channel = channel.Name | 				m.Channel = channel.Name | ||||||
| 				m.Text = ev.Text | 				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.Raw = ev | ||||||
|  | 				m.Text = b.replaceMention(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 | ||||||
|  | 						m.UserID = bot.ID | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 				mchan <- m | 				mchan <- m | ||||||
| 			} | 			} | ||||||
| 			count++ | 			count++ | ||||||
| 		case *slack.OutgoingErrorEvent: | 		case *slack.OutgoingErrorEvent: | ||||||
| 			flog.Debugf("%#v", ev.Error()) | 			flog.Debugf("%#v", ev.Error()) | ||||||
|  | 		case *slack.ChannelJoinedEvent: | ||||||
|  | 			b.Users, _ = b.sc.GetUsers() | ||||||
| 		case *slack.ConnectedEvent: | 		case *slack.ConnectedEvent: | ||||||
| 			b.channels = ev.Info.Channels | 			b.channels = ev.Info.Channels | ||||||
| 			b.si = ev.Info | 			b.si = ev.Info | ||||||
| 			b.Users, _ = b.sc.GetUsers() | 			b.Users, _ = b.sc.GetUsers() | ||||||
|  | 			// add private channels | ||||||
|  | 			groups, _ := b.sc.GetGroups(true) | ||||||
|  | 			for _, g := range groups { | ||||||
|  | 				channel := new(slack.Channel) | ||||||
|  | 				channel.ID = g.ID | ||||||
|  | 				channel.Name = g.Name | ||||||
|  | 				b.channels = append(b.channels, *channel) | ||||||
|  | 			} | ||||||
| 		case *slack.InvalidAuthEvent: | 		case *slack.InvalidAuthEvent: | ||||||
| 			flog.Fatalf("Invalid Token %#v", ev) | 			flog.Fatalf("Invalid Token %#v", ev) | ||||||
| 		default: | 		default: | ||||||
| @@ -232,7 +332,37 @@ func (b *Bslack) handleMatterHook(mchan chan *MMMessage) { | |||||||
| 		m := &MMMessage{} | 		m := &MMMessage{} | ||||||
| 		m.Username = message.UserName | 		m.Username = message.UserName | ||||||
| 		m.Text = message.Text | 		m.Text = message.Text | ||||||
|  | 		m.Text = b.replaceMention(m.Text) | ||||||
| 		m.Channel = message.ChannelName | 		m.Channel = message.ChannelName | ||||||
|  | 		if m.Username == "slackbot" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
| 		mchan <- m | 		mchan <- m | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *Bslack) userName(id string) string { | ||||||
|  | 	for _, u := range b.Users { | ||||||
|  | 		if u.ID == id { | ||||||
|  | 			return u.Name | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bslack) replaceMention(text string) string { | ||||||
|  | 	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1) | ||||||
|  | 	for _, r := range results { | ||||||
|  | 		text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1) | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | 	return text | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  | 	} | ||||||
|  | 	return text | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										160
									
								
								bridge/steam/steam.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								bridge/steam/steam.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | |||||||
|  | package bsteam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"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" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | 	b.userMap = make(map[steamid.SteamId]string) | ||||||
|  | 	b.connected = make(chan struct{}) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) Connect() error { | ||||||
|  | 	flog.Info("Connecting") | ||||||
|  | 	b.c = steam.NewClient() | ||||||
|  | 	go b.handleEvents() | ||||||
|  | 	go b.c.Connect() | ||||||
|  | 	select { | ||||||
|  | 	case <-b.connected: | ||||||
|  | 		flog.Info("Connection succeeded") | ||||||
|  | 	case <-time.After(time.Second * 30): | ||||||
|  | 		return fmt.Errorf("connection timed out") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) Disconnect() error { | ||||||
|  | 	b.c.Disconnect() | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error { | ||||||
|  | 	id, err := steamid.NewId(channel.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.c.Social.JoinChat(id) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) Send(msg config.Message) error { | ||||||
|  | 	id, err := steamid.NewId(msg.Channel) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) getNick(id steamid.SteamId) string { | ||||||
|  | 	b.RLock() | ||||||
|  | 	defer b.RUnlock() | ||||||
|  | 	if name, ok := b.userMap[id]; ok { | ||||||
|  | 		return name | ||||||
|  | 	} | ||||||
|  | 	return "unknown" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bsteam) handleEvents() { | ||||||
|  | 	myLoginInfo := new(steam.LogOnDetails) | ||||||
|  | 	myLoginInfo.Username = b.Config.Login | ||||||
|  | 	myLoginInfo.Password = b.Config.Password | ||||||
|  | 	myLoginInfo.AuthCode = b.Config.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) | ||||||
|  | 		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) | ||||||
|  | 			// for some reason we have to remove 0x18000000000000 | ||||||
|  | 			channel := int64(e.ChatRoomId) - 0x18000000000000 | ||||||
|  | 			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.Lock() | ||||||
|  | 			b.userMap[e.FriendId] = e.Name | ||||||
|  | 			b.Unlock() | ||||||
|  | 		case *steam.ConnectedEvent: | ||||||
|  | 			b.c.Auth.LogOn(myLoginInfo) | ||||||
|  | 		case *steam.MachineAuthUpdateEvent: | ||||||
|  | 			/* | ||||||
|  | 				flog.Info("authupdate", e) | ||||||
|  | 				flog.Info("hash", e.Hash) | ||||||
|  | 				ioutil.WriteFile("sentry", e.Hash, 0666) | ||||||
|  | 			*/ | ||||||
|  | 		case *steam.LogOnFailedEvent: | ||||||
|  | 			flog.Info("Logon failed", e) | ||||||
|  | 			switch e.Result { | ||||||
|  | 			case steamlang.EResult_AccountLogonDeniedNeedTwoFactorCode: | ||||||
|  | 				{ | ||||||
|  | 					flog.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:") | ||||||
|  | 					var code string | ||||||
|  | 					fmt.Scanf("%s", &code) | ||||||
|  | 					myLoginInfo.AuthCode = code | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				log.Errorf("LogOnFailedEvent: %#v ", e.Result) | ||||||
|  | 				// TODO: Handle EResult_InvalidLoginAuthCode | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		case *steam.LoggedOnEvent: | ||||||
|  | 			flog.Debugf("LoggedOnEvent: %#v", e) | ||||||
|  | 			b.connected <- struct{}{} | ||||||
|  | 			flog.Debugf("setting online") | ||||||
|  | 			b.c.Social.SetPersonaState(steamlang.EPersonaState_Online) | ||||||
|  | 		case *steam.DisconnectedEvent: | ||||||
|  | 			flog.Info("Disconnected") | ||||||
|  | 			flog.Info("Attempting to reconnect...") | ||||||
|  | 			b.c.Connect() | ||||||
|  | 		case steam.FatalErrorEvent: | ||||||
|  | 			flog.Error(e) | ||||||
|  | 		case error: | ||||||
|  | 			flog.Error(e) | ||||||
|  | 		default: | ||||||
|  | 			flog.Debugf("unknown event %#v", e) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								bridge/telegram/html.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								bridge/telegram/html.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | package btelegram | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"github.com/russross/blackfriday" | ||||||
|  | 	"html" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type customHtml struct { | ||||||
|  | 	blackfriday.Renderer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) { | ||||||
|  | 	marker := out.Len() | ||||||
|  |  | ||||||
|  | 	if !text() { | ||||||
|  | 		out.Truncate(marker) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	out.WriteString("\n") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) { | ||||||
|  | 	options.Paragraph(out, text) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (options *customHtml) HRule(out *bytes.Buffer) { | ||||||
|  | 	out.WriteByte('\n') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) { | ||||||
|  | 	options.Paragraph(out, text) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||||
|  | 	out.WriteString("- ") | ||||||
|  | 	out.Write(text) | ||||||
|  | 	out.WriteByte('\n') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func makeHTML(input string) string { | ||||||
|  | 	return string(blackfriday.Markdown([]byte(input), | ||||||
|  | 		&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")}, | ||||||
|  | 		blackfriday.EXTENSION_NO_INTRA_EMPHASIS| | ||||||
|  | 			blackfriday.EXTENSION_FENCED_CODE| | ||||||
|  | 			blackfriday.EXTENSION_AUTOLINK| | ||||||
|  | 			blackfriday.EXTENSION_SPACE_HEADERS| | ||||||
|  | 			blackfriday.EXTENSION_HEADER_IDS| | ||||||
|  | 			blackfriday.EXTENSION_BACKSLASH_LINE_BREAK| | ||||||
|  | 			blackfriday.EXTENSION_DEFINITION_LISTS)) | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								bridge/telegram/telegram.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								bridge/telegram/telegram.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | |||||||
|  | package btelegram | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 (b *Btelegram) Connect() error { | ||||||
|  | 	var err error | ||||||
|  | 	flog.Info("Connecting") | ||||||
|  | 	b.c, err = tgbotapi.NewBotAPI(b.Config.Token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Debugf("%#v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Debugf("%#v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	flog.Info("Connection succeeded") | ||||||
|  | 	go b.handleRecv(updates) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Btelegram) Disconnect() error { | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Btelegram) Send(msg config.Message) error { | ||||||
|  | 	flog.Debugf("Receiving %#v", msg) | ||||||
|  | 	chatid, err := strconv.ParseInt(msg.Channel, 10, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if b.Config.MessageFormat == "HTML" { | ||||||
|  | 		msg.Text = makeHTML(msg.Text) | ||||||
|  | 	} | ||||||
|  | 	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text) | ||||||
|  | 	if b.Config.MessageFormat == "HTML" { | ||||||
|  | 		m.ParseMode = tgbotapi.ModeHTML | ||||||
|  | 	} | ||||||
|  | 	_, err = b.c.Send(m) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 := "" | ||||||
|  | 		// handle channels | ||||||
|  | 		if update.ChannelPost != nil { | ||||||
|  | 			message = update.ChannelPost | ||||||
|  | 		} | ||||||
|  | 		if update.EditedChannelPost != nil && !b.Config.EditDisable { | ||||||
|  | 			message = update.EditedChannelPost | ||||||
|  | 			message.Text = message.Text + b.Config.EditSuffix | ||||||
|  | 		} | ||||||
|  | 		// handle groups | ||||||
|  | 		if update.Message != nil { | ||||||
|  | 			message = update.Message | ||||||
|  | 		} | ||||||
|  | 		if update.EditedMessage != nil && !b.Config.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) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if username == "" { | ||||||
|  | 			username = "unknown" | ||||||
|  | 		} | ||||||
|  | 		if message.Sticker != nil && b.Config.UseInsecureURL { | ||||||
|  | 			text = text + " " + b.getFileDirectURL(message.Sticker.FileID) | ||||||
|  | 		} | ||||||
|  | 		if message.Video != nil && b.Config.UseInsecureURL { | ||||||
|  | 			text = text + " " + b.getFileDirectURL(message.Video.FileID) | ||||||
|  | 		} | ||||||
|  | 		if message.Photo != nil && b.Config.UseInsecureURL { | ||||||
|  | 			photos := *message.Photo | ||||||
|  | 			// last photo is the biggest | ||||||
|  | 			text = text + " " + b.getFileDirectURL(photos[len(photos)-1].FileID) | ||||||
|  | 		} | ||||||
|  | 		if message.Document != nil && b.Config.UseInsecureURL { | ||||||
|  | 			text = text + " " + message.Document.FileName + " : " + b.getFileDirectURL(message.Document.FileID) | ||||||
|  | 		} | ||||||
|  | 		if text != "" { | ||||||
|  | 			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account) | ||||||
|  | 			b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID)} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Btelegram) getFileDirectURL(id string) string { | ||||||
|  | 	res, err := b.c.GetFileDirectURL(id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return res | ||||||
|  | } | ||||||
| @@ -1,8 +1,10 @@ | |||||||
| package bxmpp | package bxmpp | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/jpillora/backoff" | ||||||
| 	"github.com/mattn/go-xmpp" | 	"github.com/mattn/go-xmpp" | ||||||
|  |  | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -10,12 +12,11 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type Bxmpp struct { | type Bxmpp struct { | ||||||
| 	xc       *xmpp.Client | 	xc      *xmpp.Client | ||||||
| 	xmppMap  map[string]string | 	xmppMap map[string]string | ||||||
| 	Config   *config.Protocol | 	Config  *config.Protocol | ||||||
| 	origin   string | 	Remote  chan config.Message | ||||||
| 	protocol string | 	Account string | ||||||
| 	Remote   chan config.Message |  | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog *log.Entry | var flog *log.Entry | ||||||
| @@ -25,12 +26,11 @@ func init() { | |||||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bxmpp { | func New(cfg config.Protocol, account string, c chan config.Message) *Bxmpp { | ||||||
| 	b := &Bxmpp{} | 	b := &Bxmpp{} | ||||||
| 	b.xmppMap = make(map[string]string) | 	b.xmppMap = make(map[string]string) | ||||||
| 	b.Config = &cfg | 	b.Config = &cfg | ||||||
| 	b.protocol = protocol | 	b.Account = account | ||||||
| 	b.origin = origin |  | ||||||
| 	b.Remote = c | 	b.Remote = c | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
| @@ -44,45 +44,59 @@ func (b *Bxmpp) Connect() error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	flog.Info("Connection succeeded") | 	flog.Info("Connection succeeded") | ||||||
| 	go b.handleXmpp() | 	go func() { | ||||||
|  | 		initial := true | ||||||
|  | 		bf := &backoff.Backoff{ | ||||||
|  | 			Min:    time.Second, | ||||||
|  | 			Max:    5 * time.Minute, | ||||||
|  | 			Jitter: true, | ||||||
|  | 		} | ||||||
|  | 		for { | ||||||
|  | 			if initial { | ||||||
|  | 				b.handleXmpp() | ||||||
|  | 				initial = false | ||||||
|  | 			} | ||||||
|  | 			d := bf.Duration() | ||||||
|  | 			flog.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() | ||||||
|  | 				bf.Reset() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bxmpp) FullOrigin() string { | func (b *Bxmpp) Disconnect() error { | ||||||
| 	return b.protocol + "." + b.origin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bxmpp) JoinChannel(channel string) error { |  | ||||||
| 	b.xc.JoinMUCNoHistory(channel+"@"+b.Config.Muc, b.Config.Nick) |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bxmpp) Name() string { | func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error { | ||||||
| 	return b.protocol + "." + b.origin | 	b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick) | ||||||
| } | 	return nil | ||||||
|  |  | ||||||
| func (b *Bxmpp) Protocol() string { |  | ||||||
| 	return b.protocol |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bxmpp) Origin() string { |  | ||||||
| 	return b.origin |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bxmpp) Send(msg config.Message) error { | func (b *Bxmpp) Send(msg config.Message) error { | ||||||
| 	flog.Debugf("Receiving %#v", msg) | 	flog.Debugf("Receiving %#v", msg) | ||||||
| 	nick := config.GetNick(&msg, b.Config) | 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) | ||||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: nick + msg.Text}) |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||||
|  | 	tc := new(tls.Config) | ||||||
|  | 	tc.InsecureSkipVerify = b.Config.SkipTLSVerify | ||||||
|  | 	tc.ServerName = strings.Split(b.Config.Server, ":")[0] | ||||||
| 	options := xmpp.Options{ | 	options := xmpp.Options{ | ||||||
| 		Host:     b.Config.Server, | 		Host:      b.Config.Server, | ||||||
| 		User:     b.Config.Jid, | 		User:      b.Config.Jid, | ||||||
| 		Password: b.Config.Password, | 		Password:  b.Config.Password, | ||||||
| 		NoTLS:    true, | 		NoTLS:     true, | ||||||
| 		StartTLS: true, | 		StartTLS:  true, | ||||||
|  | 		TLSConfig: tc, | ||||||
|  |  | ||||||
| 		//StartTLS:      false, | 		//StartTLS:      false, | ||||||
| 		Debug:                        true, | 		Debug:                        true, | ||||||
| 		Session:                      true, | 		Session:                      true, | ||||||
| @@ -97,19 +111,32 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | |||||||
| 	return b.xc, err | 	return b.xc, err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bxmpp) xmppKeepAlive() { | func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||||
|  | 	done := make(chan bool) | ||||||
| 	go func() { | 	go func() { | ||||||
| 		ticker := time.NewTicker(90 * time.Second) | 		ticker := time.NewTicker(90 * time.Second) | ||||||
|  | 		defer ticker.Stop() | ||||||
| 		for { | 		for { | ||||||
| 			select { | 			select { | ||||||
| 			case <-ticker.C: | 			case <-ticker.C: | ||||||
| 				b.xc.Send(xmpp.Chat{}) | 				flog.Debugf("PING") | ||||||
|  | 				err := b.xc.PingC2S("", "") | ||||||
|  | 				if err != nil { | ||||||
|  | 					flog.Debugf("PING failed %#v", err) | ||||||
|  | 				} | ||||||
|  | 			case <-done: | ||||||
|  | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  | 	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 { | 	for { | ||||||
| 		m, err := b.xc.Recv() | 		m, err := b.xc.Recv() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -120,16 +147,21 @@ func (b *Bxmpp) handleXmpp() error { | |||||||
| 			var channel, nick string | 			var channel, nick string | ||||||
| 			if v.Type == "groupchat" { | 			if v.Type == "groupchat" { | ||||||
| 				s := strings.Split(v.Remote, "@") | 				s := strings.Split(v.Remote, "@") | ||||||
| 				if len(s) == 2 { | 				if len(s) >= 2 { | ||||||
| 					channel = s[0] | 					channel = s[0] | ||||||
| 				} | 				} | ||||||
| 				s = strings.Split(s[1], "/") | 				s = strings.Split(s[1], "/") | ||||||
| 				if len(s) == 2 { | 				if len(s) == 2 { | ||||||
| 					nick = s[1] | 					nick = s[1] | ||||||
| 				} | 				} | ||||||
| 				if nick != b.Config.Nick { | 				if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" { | ||||||
| 					flog.Debugf("Sending message from %s on %s to gateway", nick, b.FullOrigin()) | 					rmsg := config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account, UserID: v.Remote} | ||||||
| 					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | 					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 | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		case xmpp.Presence: | 		case xmpp.Presence: | ||||||
| @@ -137,3 +169,10 @@ func (b *Bxmpp) handleXmpp() error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) replaceAction(text string) (string, bool) { | ||||||
|  | 	if strings.HasPrefix(text, "/me ") { | ||||||
|  | 		return strings.Replace(text, "/me ", "", -1), true | ||||||
|  | 	} | ||||||
|  | 	return text, false | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										319
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										319
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,4 +1,320 @@ | |||||||
| # v0.7-dev | # v1.0.1 | ||||||
|  | ## New features | ||||||
|  | * mattermost: add support for mattermost 4.1.x | ||||||
|  | * discord: allow a webhookURL per channel #239 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  |  | ||||||
|  | # v1.0.0 | ||||||
|  | ## New features | ||||||
|  | * general: Add action support for slack,mattermost,irc,gitter,matrix,xmpp,discord. #199 | ||||||
|  | * discord: Shows the username instead of the server nickname #234 | ||||||
|  |  | ||||||
|  | # v1.0.0-rc1 | ||||||
|  | ## New features | ||||||
|  | * general: Add action support for slack,mattermost,irc,gitter,matrix,xmpp,discord. #199 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * general: Handle same account in multiple gateways better | ||||||
|  | * mattermost: ignore edited messages with reactions | ||||||
|  | * mattermost: Fix double posting of edited messages by using lru cache | ||||||
|  | * irc: update vendor | ||||||
|  |  | ||||||
|  | # v0.16.3 | ||||||
|  | ## Bugfix | ||||||
|  | * general: Fix in/out logic. Closes #224  | ||||||
|  | * general: Fix message modification | ||||||
|  | * slack: Disable message from other bots when using webhooks (slack) | ||||||
|  | * mattermost: Return better error messages on mattermost connect | ||||||
|  |  | ||||||
|  | # v0.16.2 | ||||||
|  | ## New features | ||||||
|  | * general: binary builds against latest commit are now available on https://bintray.com/42wim/nightly/Matterbridge/_latestVersion | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * slack: fix loop introduced by relaying message of other bots #219 | ||||||
|  | * slack: Suppress parent message when child message is received #218 | ||||||
|  | * mattermost: fix regression when using webhookurl and webhookbindaddress #221 | ||||||
|  |  | ||||||
|  | # v0.16.1 | ||||||
|  | ## New features | ||||||
|  | * slack: also relay messages of other bots #213 | ||||||
|  | * mattermost: show also links if public links have not been enabled. | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * mattermost, slack: fix connecting logic #216 | ||||||
|  |  | ||||||
|  | # v0.16.0 | ||||||
|  | ## Breaking Changes | ||||||
|  | * URL,UseAPI,BindAddress is deprecated. Your config has to be updated. | ||||||
|  |   * URL => WebhookURL | ||||||
|  |   * BindAddress => WebhookBindAddress | ||||||
|  |   * UseAPI => removed  | ||||||
|  |   This change allows you to specify a WebhookURL and a token (slack,discord), so that | ||||||
|  |   messages will be sent with the webhook, but received via the token (API) | ||||||
|  |   If you have not specified WebhookURL and WebhookBindAddress the API (login or token)  | ||||||
|  |   will be used automatically. (no need for UseAPI) | ||||||
|  |  | ||||||
|  | ## New features | ||||||
|  | * mattermost: add support for mattermost 4.0 | ||||||
|  | * steam: New protocol support added (http://store.steampowered.com/) | ||||||
|  | * discord: Support for embedded messages (sent by other bots) | ||||||
|  |   Shows title, description and URL of embedded messages (sent by other bots) | ||||||
|  |   To enable add ```ShowEmbeds=true``` to your discord config  | ||||||
|  | * discord: ```WebhookURL``` posting support added (thanks @saury07) #204 | ||||||
|  |   Discord API does not allow to change the name of the user posting, but webhooks does. | ||||||
|  |  | ||||||
|  | ## Changes | ||||||
|  | * general: all :emoji: will be converted to unicode, providing consistent emojis across all bridges | ||||||
|  | * telegram: Add ```UseInsecureURL``` option for telegram (default false) | ||||||
|  |   WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs | ||||||
|  |   Those URLs will contain your bot-token. This may not be what you want. | ||||||
|  |   For now there is no secure way to relay GIF/stickers/documents without seeing your token. | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * irc: detect charset and try to convert it to utf-8 before sending it to other bridges. #209 #210 | ||||||
|  | * slack: Remove label from URLs (slack). #205 | ||||||
|  | * slack: Relay <>& correctly to other bridges #215 | ||||||
|  | * steam: Fix channel id bug in steam (channels are off by 0x18000000000000) | ||||||
|  | * general: various improvements | ||||||
|  | * general: samechannelgateway now relays messages correct again #207 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # v0.16.0-rc2 | ||||||
|  | ## Breaking Changes | ||||||
|  | * URL,UseAPI,BindAddress is deprecated. Your config has to be updated. | ||||||
|  |   * URL => WebhookURL | ||||||
|  |   * BindAddress => WebhookBindAddress | ||||||
|  |   * UseAPI => removed  | ||||||
|  |   This change allows you to specify a WebhookURL and a token (slack,discord), so that | ||||||
|  |   messages will be sent with the webhook, but received via the token (API) | ||||||
|  |   If you have not specified WebhookURL and WebhookBindAddress the API (login or token)  | ||||||
|  |   will be used automatically. (no need for UseAPI) | ||||||
|  |  | ||||||
|  | ## Bugfix since rc1 | ||||||
|  | * steam: Fix channel id bug in steam (channels are off by 0x18000000000000) | ||||||
|  | * telegram: Add UseInsecureURL option for telegram (default false) | ||||||
|  |   WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs | ||||||
|  |   Those URLs will contain your bot-token. This may not be what you want. | ||||||
|  |   For now there is no secure way to relay GIF/stickers/documents without seeing your token. | ||||||
|  | * irc: detect charset and try to convert it to utf-8 before sending it to other bridges. #209 #210 | ||||||
|  | * general: various improvements | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # v0.16.0-rc1 | ||||||
|  | ## Breaking Changes | ||||||
|  | * URL,UseAPI,BindAddress is deprecated. Your config has to be updated. | ||||||
|  |   * URL => WebhookURL | ||||||
|  |   * BindAddress => WebhookBindAddress | ||||||
|  |   * UseAPI => removed  | ||||||
|  |   This change allows you to specify a WebhookURL and a token (slack,discord), so that | ||||||
|  |   messages will be sent with the webhook, but received via the token (API) | ||||||
|  |   If you have not specified WebhookURL and WebhookBindAddress the API (login or token)  | ||||||
|  |   will be used automatically. (no need for UseAPI) | ||||||
|  |  | ||||||
|  | ## New features | ||||||
|  | * steam: New protocol support added (http://store.steampowered.com/) | ||||||
|  | * discord: WebhookURL posting support added (thanks @saury07) #204 | ||||||
|  |   Discord API does not allow to change the name of the user posting, but webhooks does. | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * general: samechannelgateway now relays messages correct again #207 | ||||||
|  | * slack: Remove label from URLs (slack). #205 | ||||||
|  |  | ||||||
|  | # v0.15.0 | ||||||
|  | ## New features | ||||||
|  | * general: add option IgnoreMessages for all protocols (see mattebridge.toml.sample) | ||||||
|  |   Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  |   e.g. IgnoreMessages="^~~ badword" | ||||||
|  | * telegram: add support for sticker/video/photo/document #184 | ||||||
|  |  | ||||||
|  | ## Changes | ||||||
|  | * api: add userid to each message #200 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * discord: fix crash in memberupdate #198 | ||||||
|  | * mattermost: Fix incorrect behaviour of EditDisable (mattermost). Fixes #197  | ||||||
|  | * irc: Do not relay join/part of ourselves (irc). Closes #190  | ||||||
|  | * irc: make reconnections more robust. #153 | ||||||
|  | * gitter: update library, fixes possible crash | ||||||
|  |  | ||||||
|  | # v0.14.0 | ||||||
|  | ## New features | ||||||
|  | * api: add token authentication | ||||||
|  | * mattermost: add support for mattermost 3.10.0 | ||||||
|  |  | ||||||
|  | ## Changes | ||||||
|  | * api: gateway name is added in JSON messages | ||||||
|  | * api: lowercase JSON keys | ||||||
|  | * api: channel name isn't needed in config #195 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * discord: Add hashtag to channelname (when translating from id) (discord) | ||||||
|  | * mattermost: Fix a panic. #186 | ||||||
|  | * mattermost: use teamid cache if possible. Fixes a panic | ||||||
|  | * api: post valid json. #185 | ||||||
|  | * api: allow reuse of api in different gateways. #189 | ||||||
|  | * general: Fix utf-8 issues for {NOPINGNICK}. #193 | ||||||
|  |  | ||||||
|  | # v0.13.0 | ||||||
|  | ## New features | ||||||
|  | * irc: Limit message length. ```MessageLength=400``` | ||||||
|  |   Maximum length of message sent to irc server. If it exceeds <message clipped> will be add to the message. | ||||||
|  | * irc: Add NOPINGNICK option.  | ||||||
|  |   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 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * slack: Fix sending to different channels on same account (slack). Closes #177 | ||||||
|  | * telegram: Fix incorrect usernames being sent. Closes #181 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # v0.12.1 | ||||||
|  | ## New features | ||||||
|  | * telegram: Add UseFirstName option (telegram). Closes #144 | ||||||
|  | * matrix: Add NoHomeServerSuffix. Option to disable homeserver on username (matrix). Closes #160. | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * xmpp: Add Compatibility for Cisco Jabber (xmpp) (#166) | ||||||
|  | * irc: Fix JoinChannel argument to use IRC channel key (#172) | ||||||
|  | * discord: Fix possible crash on nil (discord) | ||||||
|  | * discord: Replace long ids in channel metions (discord). Fixes #174 | ||||||
|  |  | ||||||
|  | # v0.12.0 | ||||||
|  | ## Changes | ||||||
|  | * general: edited messages are now being sent by default on discord/mattermost/telegram/slack. See "New Features" | ||||||
|  |  | ||||||
|  | ## New features | ||||||
|  | * general: add support for edited messages.  | ||||||
|  |   Add new keyword EditDisable (false/true), default false. Which means by default edited messages will be sent to other bridges. | ||||||
|  |   Add new keyword EditSuffix , default "". You can change this eg to "(edited)", this will be appended to every edit message. | ||||||
|  | * mattermost: support mattermost v3.9.x | ||||||
|  | * general: Add support for HTTP{S}_PROXY env variables (#162) | ||||||
|  | * discord: Strip custom emoji metadata (discord). Closes #148 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * slack: Ignore error on private channel join (slack) Fixes #150  | ||||||
|  | * mattermost: fix crash on reconnects when server is down. Closes #163 | ||||||
|  | * irc: Relay messages starting with ! (irc). Closes #164 | ||||||
|  |  | ||||||
|  | # v0.11.0 | ||||||
|  | ## New features | ||||||
|  | * general: reusing the same account on multiple gateways now also reuses the connection. | ||||||
|  |   This is particuarly useful for irc. See #87 | ||||||
|  | * general: the Name is now REQUIRED and needs to be UNIQUE for each gateway configuration | ||||||
|  | * telegram:  Support edited messages (telegram). See #141 | ||||||
|  | * mattermost: Add support for showing/hiding join/leave messages from mattermost. Closes #147 | ||||||
|  | * mattermost: Reconnect on session removal/timeout (mattermost) | ||||||
|  | * mattermost: Support mattermost v3.8.x | ||||||
|  | * irc:  Rejoin channel when kicked (irc). | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * mattermost: Remove space after nick (mattermost). Closes #142 | ||||||
|  | * mattermost: Modify iconurl correctly (mattermost). | ||||||
|  | * irc: Fix join/leave regression (irc) | ||||||
|  |  | ||||||
|  | # v0.10.3 | ||||||
|  | ## Bugfix | ||||||
|  | * slack: Allow bot tokens for now without warning (slack). Closes #140 (fixes user_is_bot message on channel join) | ||||||
|  |  | ||||||
|  | # v0.10.2 | ||||||
|  | ## New features | ||||||
|  | * general: gops agent added. Allows for more debugging. See #134 | ||||||
|  | * general: toml inline table support added for config file | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * all: vendored libs updated | ||||||
|  |  | ||||||
|  | ## Changes | ||||||
|  | * general: add more informative messages on startup | ||||||
|  |  | ||||||
|  | # v0.10.1 | ||||||
|  | ## Bugfix | ||||||
|  | * gitter: Fix sending messages on new channel join. | ||||||
|  |  | ||||||
|  | # v0.10.0 | ||||||
|  | ## New features | ||||||
|  | * matrix: New protocol support added (https://matrix.org) | ||||||
|  | * mattermost: works with mattermost release v3.7.0 | ||||||
|  | * discord: Replace role ids in mentions to role names (discord). Closes #133 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * mattermost: Add ReadTimeout to close lingering connections (mattermost). See #125 | ||||||
|  | * gitter: Join rooms not already joined by the bot (gitter). See #135 | ||||||
|  | * general: Fail when bridge is unable to join a channel (general) | ||||||
|  |  | ||||||
|  | ## Changes | ||||||
|  | * telegram: Do not use HTML parsemode by default. Set ```MessageFormat="HTML"``` to use it. Closes #126 | ||||||
|  |  | ||||||
|  | # v0.9.3 | ||||||
|  | ## New features | ||||||
|  | * API: rest interface to read / post messages (see API section in matterbridge.toml.sample) | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * slack: fix receiving messages from private channels #118 | ||||||
|  | * slack: fix echo when using webhooks #119 | ||||||
|  | * mattermost: reconnecting should work better now | ||||||
|  | * irc: keeps reconnecting (every 60 seconds) now after ping timeout/disconnects. | ||||||
|  |  | ||||||
|  | # v0.9.2 | ||||||
|  | ## New features | ||||||
|  | * slack: support private channels #118 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * general: make ignorenicks work again #115 | ||||||
|  | * telegram: fix receiving from channels and groups #112 | ||||||
|  | * telegram: use html for username | ||||||
|  | * telegram: use ```unknown``` as username when username is not visible. | ||||||
|  | * irc: update vendor (fixes some crashes) #117 | ||||||
|  | * xmpp: fix tls by setting ServerName #114 | ||||||
|  |  | ||||||
|  | # v0.9.1 | ||||||
|  | ## New features | ||||||
|  | * Rocket.Chat: New protocol support added (https://rocket.chat) | ||||||
|  | * irc: add channel key support #27 (see matterbrige.toml.sample for example) | ||||||
|  | * xmpp: add SkipTLSVerify #106 | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * general: Exit when a bridge fails to start | ||||||
|  | * mattermost: Check errors only on first connect. Keep retrying after first connection succeeds. #95 | ||||||
|  | * telegram: fix missing username #102 | ||||||
|  | * slack: do not use API functions in webhook (slack) #110 | ||||||
|  |  | ||||||
|  | # v0.9.0 | ||||||
|  | ## New features | ||||||
|  | * Telegram: New protocol support added (https://telegram.org) | ||||||
|  | * Hipchat: Add sample config to connect to hipchat via xmpp | ||||||
|  | * discord: add "Bot " tag to discord tokens automatically | ||||||
|  | * slack: Add support for dynamic Iconurl #43 | ||||||
|  | * general: Add ```gateway.inout``` config option for bidirectional bridges #85 | ||||||
|  | * general: Add ```[general]``` section so that ```RemoteNickFormat``` can be set globally | ||||||
|  |  | ||||||
|  | ## Bugfix | ||||||
|  | * general: when using samechannelgateway NickFormat get doubled by the NICK #77 | ||||||
|  | * general: fix ShowJoinPart for messages from irc bridge #72 | ||||||
|  | * gitter: fix high cpu usage #89 | ||||||
|  | * irc: fix !users command #78 | ||||||
|  | * xmpp: fix keepalive | ||||||
|  | * xmpp: do not relay delayed/empty messages | ||||||
|  | * slack: Replace id-mentions to usernames #86  | ||||||
|  | * mattermost: fix public links not working (API changes) | ||||||
|  |  | ||||||
|  | # v0.8.1 | ||||||
|  | ## Bugfix | ||||||
|  | * general: when using samechannelgateway NickFormat get doubled by the NICK #77 | ||||||
|  | * irc: fix !users command #78 | ||||||
|  |  | ||||||
|  | # v0.8.0 | ||||||
|  | Release because of breaking mattermost API changes | ||||||
|  | ## New features | ||||||
|  | * Supports mattermost v3.5.0 | ||||||
|  |  | ||||||
|  | # v0.7.1 | ||||||
|  | ## Bugfix | ||||||
|  | * general: when using samechannelgateway NickFormat get doubled by the NICK #77 | ||||||
|  | * irc: fix !users command #78 | ||||||
|  |  | ||||||
|  | # v0.7.0 | ||||||
| ## Breaking config changes from 0.6 to 0.7 | ## Breaking config changes from 0.6 to 0.7 | ||||||
| Matterbridge now uses TOML configuration (https://github.com/toml-lang/toml) | Matterbridge now uses TOML configuration (https://github.com/toml-lang/toml) | ||||||
| See matterbridge.toml.sample for an example | See matterbridge.toml.sample for an example | ||||||
| @@ -32,6 +348,7 @@ See matterbridge.toml.sample for an example | |||||||
| # v0.6.1 | # v0.6.1 | ||||||
| ## New features | ## New features | ||||||
| * Slack support added.  See matterbridge.conf.sample for more information | * Slack support added.  See matterbridge.conf.sample for more information | ||||||
|  |  | ||||||
| ## Bugfix | ## Bugfix | ||||||
| * Fix 100% CPU bug on incorrect closed connections | * Fix 100% CPU bug on incorrect closed connections | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								ci/bintray.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								ci/bintray.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | go version |grep go1.8 || 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-win64.exe | ||||||
|  | GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux64 | ||||||
|  | GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm | ||||||
|  | cd ci | ||||||
|  | cat > deploy.json <<EOF | ||||||
|  | { | ||||||
|  |     "package": { | ||||||
|  |         "name": "Matterbridge", | ||||||
|  |         "repo": "nightly", | ||||||
|  |         "subject": "42wim" | ||||||
|  |     }, | ||||||
|  |     "version": { | ||||||
|  |         "name": "$VERSION" | ||||||
|  |     }, | ||||||
|  |     "files": | ||||||
|  |         [ | ||||||
|  |         {"includePattern": "ci/binaries/(.*)", "uploadPattern":"\$1"} | ||||||
|  |         ], | ||||||
|  |     "publish": true | ||||||
|  | } | ||||||
|  | EOF | ||||||
|  |  | ||||||
| @@ -5,119 +5,163 @@ import ( | |||||||
| 	"github.com/42wim/matterbridge/bridge" | 	"github.com/42wim/matterbridge/bridge" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
| 	"reflect" | 	//	"github.com/davecgh/go-spew/spew" | ||||||
|  | 	"github.com/peterhellberg/emojilib" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Gateway struct { | type Gateway struct { | ||||||
| 	*config.Config | 	*config.Config | ||||||
| 	MyConfig    *config.Gateway | 	Router         *Router | ||||||
| 	Bridges     []bridge.Bridge | 	MyConfig       *config.Gateway | ||||||
| 	ChannelsOut map[string][]string | 	Bridges        map[string]*bridge.Bridge | ||||||
| 	ChannelsIn  map[string][]string | 	Channels       map[string]*config.ChannelInfo | ||||||
| 	ignoreNicks map[string][]string | 	ChannelOptions map[string]config.ChannelOptions | ||||||
| 	Name        string | 	Message        chan config.Message | ||||||
|  | 	Name           string | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg *config.Config, gateway *config.Gateway) error { | func New(cfg config.Gateway, r *Router) *Gateway { | ||||||
| 	c := make(chan config.Message) | 	gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message, | ||||||
| 	gw := &Gateway{} | 		Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config} | ||||||
| 	gw.Name = gateway.Name | 	gw.AddConfig(&cfg) | ||||||
| 	gw.Config = cfg | 	return gw | ||||||
| 	gw.MyConfig = gateway | } | ||||||
| 	exists := make(map[string]bool) |  | ||||||
| 	for _, br := range append(gateway.In, gateway.Out...) { | func (gw *Gateway) AddBridge(cfg *config.Bridge) error { | ||||||
| 		if exists[br.Account] { | 	br := gw.Router.getBridge(cfg.Account) | ||||||
| 			continue | 	if br == nil { | ||||||
| 		} | 		br = bridge.New(gw.Config, cfg, gw.Message) | ||||||
| 		log.Infof("Starting bridge: %s channel: %s", br.Account, br.Channel) |  | ||||||
| 		gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c)) |  | ||||||
| 		exists[br.Account] = true |  | ||||||
| 	} | 	} | ||||||
| 	gw.mapChannels() | 	gw.mapChannelsToBridge(br) | ||||||
| 	//TODO fix mapIgnores | 	gw.Bridges[cfg.Account] = br | ||||||
| 	//gw.mapIgnores() |  | ||||||
| 	exists = make(map[string]bool) |  | ||||||
| 	for _, br := range gw.Bridges { |  | ||||||
| 		err := br.Connect() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err) |  | ||||||
| 		} |  | ||||||
| 		for _, channel := range append(gw.ChannelsOut[br.FullOrigin()], gw.ChannelsIn[br.FullOrigin()]...) { |  | ||||||
| 			if exists[br.FullOrigin()+channel] { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			log.Infof("%s: joining %s", br.FullOrigin(), channel) |  | ||||||
| 			br.JoinChannel(channel) |  | ||||||
| 			exists[br.FullOrigin()+channel] = true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	gw.handleReceive(c) |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) handleReceive(c chan config.Message) { | func (gw *Gateway) AddConfig(cfg *config.Gateway) error { | ||||||
| 	for { | 	gw.Name = cfg.Name | ||||||
| 		select { | 	gw.MyConfig = cfg | ||||||
| 		case msg := <-c: | 	gw.mapChannels() | ||||||
| 			for _, br := range gw.Bridges { | 	for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) { | ||||||
| 				gw.handleMessage(msg, br) | 		err := gw.AddBridge(&br) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (gw *Gateway) mapChannelsToBridge(br *bridge.Bridge) { | ||||||
|  | 	for ID, channel := range gw.Channels { | ||||||
|  | 		if br.Account == channel.Account { | ||||||
|  | 			br.Channels[ID] = *channel | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (gw *Gateway) reconnectBridge(br *bridge.Bridge) { | ||||||
|  | 	br.Disconnect() | ||||||
|  | 	time.Sleep(time.Second * 5) | ||||||
|  | RECONNECT: | ||||||
|  | 	log.Infof("Reconnecting %s", br.Account) | ||||||
|  | 	err := br.Connect() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err) | ||||||
|  | 		time.Sleep(time.Second * 60) | ||||||
|  | 		goto RECONNECT | ||||||
|  | 	} | ||||||
|  | 	br.Joined = make(map[string]bool) | ||||||
|  | 	br.JoinChannels() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) { | ||||||
|  | 	for _, br := range cfg { | ||||||
|  | 		if isApi(br.Account) { | ||||||
|  | 			br.Channel = "api" | ||||||
|  | 		} | ||||||
|  | 		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, | ||||||
|  | 				SameChannel: make(map[string]bool)} | ||||||
|  | 			channel.SameChannel[gw.Name] = br.SameChannel | ||||||
|  | 			gw.Channels[channel.ID] = channel | ||||||
|  | 		} else { | ||||||
|  | 			// if we already have a key and it's not our current direction it means we have a bidirectional inout | ||||||
|  | 			if gw.Channels[ID].Direction != direction { | ||||||
|  | 				gw.Channels[ID].Direction = "inout" | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		gw.Channels[ID].SameChannel[gw.Name] = br.SameChannel | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) mapChannels() error { | func (gw *Gateway) mapChannels() error { | ||||||
| 	m := make(map[string][]string) | 	gw.mapChannelConfig(gw.MyConfig.In, "in") | ||||||
| 	for _, br := range gw.MyConfig.Out { | 	gw.mapChannelConfig(gw.MyConfig.Out, "out") | ||||||
| 		m[br.Account] = append(m[br.Account], br.Channel) | 	gw.mapChannelConfig(gw.MyConfig.InOut, "inout") | ||||||
| 	} |  | ||||||
| 	gw.ChannelsOut = m |  | ||||||
| 	m = nil |  | ||||||
| 	m = make(map[string][]string) |  | ||||||
| 	for _, br := range gw.MyConfig.In { |  | ||||||
| 		m[br.Account] = append(m[br.Account], br.Channel) |  | ||||||
| 	} |  | ||||||
| 	gw.ChannelsIn = m |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) mapIgnores() { | func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []config.ChannelInfo { | ||||||
| 	m := make(map[string][]string) | 	var channels []config.ChannelInfo | ||||||
| 	for _, br := range gw.MyConfig.In { | 	// if source channel is in only, do nothing | ||||||
| 		accInfo := strings.Split(br.Account, ".") | 	for _, channel := range gw.Channels { | ||||||
| 		m[br.Account] = strings.Fields(gw.Config.IRC[accInfo[1]].IgnoreNicks) | 		// lookup the channel from the message | ||||||
| 	} | 		if channel.ID == getChannelID(*msg) { | ||||||
| 	gw.ignoreNicks = m | 			// we only have destinations if the original message is from an "in" (sending) channel | ||||||
| } | 			if !strings.Contains(channel.Direction, "in") { | ||||||
|  | 				return channels | ||||||
| func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { | 			} | ||||||
| 	channels := gw.ChannelsIn[msg.FullOrigin] | 			continue | ||||||
| 	for _, channel := range channels { |  | ||||||
| 		if channel == msg.Channel { |  | ||||||
| 			return gw.ChannelsOut[dest] |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return []string{} | 	for _, channel := range gw.Channels { | ||||||
|  | 		if _, ok := gw.Channels[getChannelID(*msg)]; !ok { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// do samechannelgateway logic | ||||||
|  | 		if channel.SameChannel[msg.Gateway] { | ||||||
|  | 			if msg.Channel == channel.Name && msg.Account != dest.Account { | ||||||
|  | 				channels = append(channels, *channel) | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if strings.Contains(channel.Direction, "out") && channel.Account == dest.Account && gw.validGatewayDest(msg, channel) { | ||||||
|  | 			channels = append(channels, *channel) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return channels | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { | func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) { | ||||||
| 	if gw.ignoreMessage(&msg) { | 	// only relay join/part when configged | ||||||
|  | 	if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// broadcast to every out channel (irc QUIT) | ||||||
|  | 	if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE { | ||||||
|  | 		log.Debug("empty channel") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	originchannel := msg.Channel | 	originchannel := msg.Channel | ||||||
| 	channels := gw.getDestChannel(&msg, dest.FullOrigin()) | 	origmsg := msg | ||||||
|  | 	channels := gw.getDestChannel(&msg, *dest) | ||||||
| 	for _, channel := range channels { | 	for _, channel := range channels { | ||||||
| 		// do not send the message to the bridge we come from if also the channel is the same | 		// do not send to ourself | ||||||
| 		if msg.FullOrigin == dest.FullOrigin() && channel == originchannel { | 		if channel.ID == getChannelID(origmsg) { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		msg.Channel = channel | 		log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel.Name) | ||||||
| 		if msg.Channel == "" { | 		msg.Channel = channel.Name | ||||||
| 			log.Debug("empty channel") | 		msg.Avatar = gw.modifyAvatar(origmsg, dest) | ||||||
| 			return | 		msg.Username = gw.modifyUsername(origmsg, dest) | ||||||
|  | 		// for api we need originchannel as channel | ||||||
|  | 		if dest.Protocol == "api" { | ||||||
|  | 			msg.Channel = originchannel | ||||||
| 		} | 		} | ||||||
| 		log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, originchannel, dest.FullOrigin(), channel) |  | ||||||
| 		err := dest.Send(msg) | 		err := dest.Send(msg) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Println(err) | 			fmt.Println(err) | ||||||
| @@ -126,26 +170,88 @@ func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||||
| 	// should we discard messages ? | 	// if we don't have the bridge, ignore it | ||||||
| 	for _, entry := range gw.ignoreNicks[msg.FullOrigin] { | 	if _, ok := gw.Bridges[msg.Account]; !ok { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	if msg.Text == "" { | ||||||
|  | 		log.Debugf("ignoring empty message %#v from %s", msg, msg.Account) | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) { | ||||||
| 		if msg.Username == entry { | 		if msg.Username == entry { | ||||||
|  | 			log.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	// TODO do not compile regexps everytime | ||||||
|  | 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreMessages) { | ||||||
|  | 		if entry != "" { | ||||||
|  | 			re, err := regexp.Compile(entry) | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.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) | ||||||
|  | 				return true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *Gateway) modifyMessage(msg *config.Message, dest bridge.Bridge) { | func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string { | ||||||
| 	val := reflect.ValueOf(gw.Config).Elem() | 	br := gw.Bridges[msg.Account] | ||||||
| 	for i := 0; i < val.NumField(); i++ { | 	msg.Protocol = br.Protocol | ||||||
| 		typeField := val.Type().Field(i) | 	nick := gw.Config.General.RemoteNickFormat | ||||||
| 		// look for the protocol map (both lowercase) | 	if nick == "" { | ||||||
| 		if strings.ToLower(typeField.Name) == dest.Protocol() { | 		nick = dest.Config.RemoteNickFormat | ||||||
| 			// get the Protocol struct from the map |  | ||||||
| 			protoCfg := val.Field(i).MapIndex(reflect.ValueOf(dest.Origin())) |  | ||||||
| 			//config.SetNickFormat(msg, protoCfg.Interface().(config.Protocol)) |  | ||||||
| 			val.Field(i).SetMapIndex(reflect.ValueOf(dest.Origin()), protoCfg) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 	if len(msg.Username) > 0 { | ||||||
|  | 		// fix utf-8 issue #193 | ||||||
|  | 		i := 0 | ||||||
|  | 		for index := range msg.Username { | ||||||
|  | 			if i == 1 { | ||||||
|  | 				i = index | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 		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) | ||||||
|  | 	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 = strings.Replace(iconurl, "{NICK}", msg.Username, -1) | ||||||
|  | 	if msg.Avatar == "" { | ||||||
|  | 		msg.Avatar = iconurl | ||||||
|  | 	} | ||||||
|  | 	return msg.Avatar | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (gw *Gateway) modifyMessage(msg *config.Message) { | ||||||
|  | 	// replace :emoji: to unicode | ||||||
|  | 	msg.Text = emojilib.Replace(msg.Text) | ||||||
|  | 	msg.Gateway = gw.Name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getChannelID(msg config.Message) string { | ||||||
|  | 	return msg.Channel + msg.Account | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (gw *Gateway) validGatewayDest(msg *config.Message, channel *config.ChannelInfo) bool { | ||||||
|  | 	return msg.Gateway == gw.Name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isApi(account string) bool { | ||||||
|  | 	return strings.HasPrefix(account, "api.") | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										288
									
								
								gateway/gateway_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								gateway/gateway_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,288 @@ | |||||||
|  | package gateway | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	"github.com/BurntSushi/toml" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var testconfig = ` | ||||||
|  | [irc.freenode] | ||||||
|  | [mattermost.test] | ||||||
|  | [gitter.42wim] | ||||||
|  | [discord.test] | ||||||
|  | [slack.test] | ||||||
|  |  | ||||||
|  | [[gateway]] | ||||||
|  |     name = "bridge1" | ||||||
|  |     enable=true | ||||||
|  |      | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account = "irc.freenode" | ||||||
|  |     channel = "#wimtesting" | ||||||
|  |      | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="gitter.42wim" | ||||||
|  |     channel="42wim/testroom" | ||||||
|  |     #channel="matterbridge/Lobby" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account = "discord.test" | ||||||
|  |     channel = "general" | ||||||
|  |      | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="slack.test" | ||||||
|  |     channel="testing" | ||||||
|  | 	` | ||||||
|  |  | ||||||
|  | var testconfig2 = ` | ||||||
|  | [irc.freenode] | ||||||
|  | [mattermost.test] | ||||||
|  | [gitter.42wim] | ||||||
|  | [discord.test] | ||||||
|  | [slack.test] | ||||||
|  |  | ||||||
|  | [[gateway]] | ||||||
|  |     name = "bridge1" | ||||||
|  |     enable=true | ||||||
|  |      | ||||||
|  |     [[gateway.in]] | ||||||
|  |     account = "irc.freenode" | ||||||
|  |     channel = "#wimtesting" | ||||||
|  |      | ||||||
|  |     [[gateway.in]] | ||||||
|  |     account="gitter.42wim" | ||||||
|  |     channel="42wim/testroom" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account = "discord.test" | ||||||
|  |     channel = "general" | ||||||
|  |      | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="slack.test" | ||||||
|  |     channel="testing" | ||||||
|  | [[gateway]] | ||||||
|  |     name = "bridge2" | ||||||
|  |     enable=true | ||||||
|  |      | ||||||
|  |     [[gateway.in]] | ||||||
|  |     account = "irc.freenode" | ||||||
|  |     channel = "#wimtesting2" | ||||||
|  |      | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="gitter.42wim" | ||||||
|  |     channel="42wim/testroom" | ||||||
|  |  | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account = "discord.test" | ||||||
|  |     channel = "general2" | ||||||
|  | 	` | ||||||
|  | var testconfig3 = ` | ||||||
|  | [irc.zzz] | ||||||
|  | [telegram.zzz] | ||||||
|  | [slack.zzz] | ||||||
|  | [[gateway]] | ||||||
|  | name="bridge" | ||||||
|  | enable=true | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="irc.zzz" | ||||||
|  |     channel="#main"		 | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="telegram.zzz" | ||||||
|  |     channel="-1111111111111" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="slack.zzz" | ||||||
|  |     channel="irc"	 | ||||||
|  | 	 | ||||||
|  | [[gateway]] | ||||||
|  | name="announcements" | ||||||
|  | enable=true | ||||||
|  | 	 | ||||||
|  |     [[gateway.in]] | ||||||
|  |     account="telegram.zzz" | ||||||
|  |     channel="-2222222222222"	 | ||||||
|  | 	 | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="irc.zzz" | ||||||
|  |     channel="#main"		 | ||||||
|  | 	 | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="irc.zzz" | ||||||
|  |     channel="#main-help"	 | ||||||
|  |  | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="telegram.zzz" | ||||||
|  |     channel="--333333333333"	 | ||||||
|  |  | ||||||
|  |     [[gateway.out]] | ||||||
|  |     account="slack.zzz" | ||||||
|  |     channel="general"		 | ||||||
|  | 	 | ||||||
|  | [[gateway]] | ||||||
|  | name="bridge2" | ||||||
|  | enable=true | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="irc.zzz" | ||||||
|  |     channel="#main-help"	 | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="telegram.zzz" | ||||||
|  |     channel="--444444444444"	 | ||||||
|  |  | ||||||
|  | 	 | ||||||
|  | [[gateway]] | ||||||
|  | name="bridge3" | ||||||
|  | enable=true | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="irc.zzz" | ||||||
|  |     channel="#main-telegram"	 | ||||||
|  |  | ||||||
|  |     [[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) | ||||||
|  | 	} | ||||||
|  | 	r, err := NewRouter(cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 	} | ||||||
|  | 	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) | ||||||
|  | 	} | ||||||
|  | 	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)) | ||||||
|  |  | ||||||
|  | 	r = maketestRouter(testconfig2) | ||||||
|  | 	assert.Equal(t, 2, len(r.Gateways)) | ||||||
|  | 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Bridges)) | ||||||
|  | 	assert.Equal(t, 3, len(r.Gateways["bridge2"].Bridges)) | ||||||
|  | 	assert.Equal(t, 4, len(r.Gateways["bridge1"].Channels)) | ||||||
|  | 	assert.Equal(t, 3, len(r.Gateways["bridge2"].Channels)) | ||||||
|  | 	assert.Equal(t, &config.ChannelInfo{Name: "42wim/testroom", Direction: "out", | ||||||
|  | 		ID: "42wim/testroomgitter.42wim", Account: "gitter.42wim", | ||||||
|  | 		SameChannel: map[string]bool{"bridge2": false}}, | ||||||
|  | 		r.Gateways["bridge2"].Channels["42wim/testroomgitter.42wim"]) | ||||||
|  | 	assert.Equal(t, &config.ChannelInfo{Name: "42wim/testroom", Direction: "in", | ||||||
|  | 		ID: "42wim/testroomgitter.42wim", Account: "gitter.42wim", | ||||||
|  | 		SameChannel: map[string]bool{"bridge1": false}}, | ||||||
|  | 		r.Gateways["bridge1"].Channels["42wim/testroomgitter.42wim"]) | ||||||
|  | 	assert.Equal(t, &config.ChannelInfo{Name: "general", Direction: "inout", | ||||||
|  | 		ID: "generaldiscord.test", Account: "discord.test", | ||||||
|  | 		SameChannel: map[string]bool{"bridge1": false}}, | ||||||
|  | 		r.Gateways["bridge1"].Channels["generaldiscord.test"]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGetDestChannel(t *testing.T) { | ||||||
|  | 	r := maketestRouter(testconfig2) | ||||||
|  | 	msg := &config.Message{Text: "test", Channel: "general", Account: "discord.test", Gateway: "bridge1", Protocol: "discord", Username: "test"} | ||||||
|  | 	for _, br := range r.Gateways["bridge1"].Bridges { | ||||||
|  | 		switch br.Account { | ||||||
|  | 		case "discord.test": | ||||||
|  | 			assert.Equal(t, []config.ChannelInfo{{Name: "general", Account: "discord.test", Direction: "inout", ID: "generaldiscord.test", SameChannel: map[string]bool{"bridge1": false}, Options: config.ChannelOptions{Key: ""}}}, | ||||||
|  | 				r.Gateways["bridge1"].getDestChannel(msg, *br)) | ||||||
|  | 		case "slack.test": | ||||||
|  | 			assert.Equal(t, []config.ChannelInfo{{Name: "testing", Account: "slack.test", Direction: "out", ID: "testingslack.test", SameChannel: map[string]bool{"bridge1": false}, Options: config.ChannelOptions{Key: ""}}}, | ||||||
|  | 				r.Gateways["bridge1"].getDestChannel(msg, *br)) | ||||||
|  | 		case "gitter.42wim": | ||||||
|  | 			assert.Equal(t, []config.ChannelInfo(nil), r.Gateways["bridge1"].getDestChannel(msg, *br)) | ||||||
|  | 		case "irc.freenode": | ||||||
|  | 			assert.Equal(t, []config.ChannelInfo(nil), r.Gateways["bridge1"].getDestChannel(msg, *br)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGetDestChannelAdvanced(t *testing.T) { | ||||||
|  | 	r := maketestRouter(testconfig3) | ||||||
|  | 	var msgs []*config.Message | ||||||
|  | 	i := 0 | ||||||
|  | 	for _, gw := range r.Gateways { | ||||||
|  | 		for _, channel := range gw.Channels { | ||||||
|  | 			msgs = append(msgs, &config.Message{Text: "text" + strconv.Itoa(i), Channel: channel.Name, Account: channel.Account, Gateway: gw.Name, Username: "user" + strconv.Itoa(i)}) | ||||||
|  | 			i++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	hits := make(map[string]int) | ||||||
|  | 	for _, gw := range r.Gateways { | ||||||
|  | 		for _, br := range gw.Bridges { | ||||||
|  | 			for _, msg := range msgs { | ||||||
|  | 				channels := gw.getDestChannel(msg, *br) | ||||||
|  | 				if gw.Name != msg.Gateway { | ||||||
|  | 					assert.Equal(t, []config.ChannelInfo(nil), channels) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				switch gw.Name { | ||||||
|  | 				case "bridge": | ||||||
|  | 					if (msg.Channel == "#main" || msg.Channel == "-1111111111111" || msg.Channel == "irc") && (msg.Account == "irc.zzz" || msg.Account == "telegram.zzz" || msg.Account == "slack.zzz") { | ||||||
|  | 						hits[gw.Name]++ | ||||||
|  | 						switch br.Account { | ||||||
|  | 						case "irc.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "#main", Account: "irc.zzz", Direction: "inout", ID: "#mainirc.zzz", SameChannel: map[string]bool{"bridge": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						case "telegram.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "-1111111111111", Account: "telegram.zzz", Direction: "inout", ID: "-1111111111111telegram.zzz", SameChannel: map[string]bool{"bridge": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						case "slack.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "irc", Account: "slack.zzz", Direction: "inout", ID: "ircslack.zzz", SameChannel: map[string]bool{"bridge": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				case "bridge2": | ||||||
|  | 					if (msg.Channel == "#main-help" || msg.Channel == "--444444444444") && (msg.Account == "irc.zzz" || msg.Account == "telegram.zzz") { | ||||||
|  | 						hits[gw.Name]++ | ||||||
|  | 						switch br.Account { | ||||||
|  | 						case "irc.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "#main-help", Account: "irc.zzz", Direction: "inout", ID: "#main-helpirc.zzz", SameChannel: map[string]bool{"bridge2": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						case "telegram.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "--444444444444", Account: "telegram.zzz", Direction: "inout", ID: "--444444444444telegram.zzz", SameChannel: map[string]bool{"bridge2": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				case "bridge3": | ||||||
|  | 					if (msg.Channel == "#main-telegram" || msg.Channel == "--333333333333") && (msg.Account == "irc.zzz" || msg.Account == "telegram.zzz") { | ||||||
|  | 						hits[gw.Name]++ | ||||||
|  | 						switch br.Account { | ||||||
|  | 						case "irc.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "#main-telegram", Account: "irc.zzz", Direction: "inout", ID: "#main-telegramirc.zzz", SameChannel: map[string]bool{"bridge3": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						case "telegram.zzz": | ||||||
|  | 							assert.Equal(t, []config.ChannelInfo{{Name: "--333333333333", Account: "telegram.zzz", Direction: "inout", ID: "--333333333333telegram.zzz", SameChannel: map[string]bool{"bridge3": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				case "announcements": | ||||||
|  | 					if msg.Channel != "-2222222222222" && msg.Account != "telegram" { | ||||||
|  | 						assert.Equal(t, []config.ChannelInfo(nil), channels) | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 					hits[gw.Name]++ | ||||||
|  | 					switch br.Account { | ||||||
|  | 					case "irc.zzz": | ||||||
|  | 						assert.Equal(t, []config.ChannelInfo{{Name: "#main", Account: "irc.zzz", Direction: "out", ID: "#mainirc.zzz", SameChannel: map[string]bool{"announcements": false}, Options: config.ChannelOptions{Key: ""}}, {Name: "#main-help", Account: "irc.zzz", Direction: "out", ID: "#main-helpirc.zzz", SameChannel: map[string]bool{"announcements": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 					case "slack.zzz": | ||||||
|  | 						assert.Equal(t, []config.ChannelInfo{{Name: "general", Account: "slack.zzz", Direction: "out", ID: "generalslack.zzz", SameChannel: map[string]bool{"announcements": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 					case "telegram.zzz": | ||||||
|  | 						assert.Equal(t, []config.ChannelInfo{{Name: "--333333333333", Account: "telegram.zzz", Direction: "out", ID: "--333333333333telegram.zzz", SameChannel: map[string]bool{"announcements": false}, Options: config.ChannelOptions{Key: ""}}}, channels) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	assert.Equal(t, map[string]int{"bridge3": 4, "bridge": 9, "announcements": 3, "bridge2": 4}, hits) | ||||||
|  | } | ||||||
							
								
								
									
										106
									
								
								gateway/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								gateway/router.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | package gateway | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"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" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Router struct { | ||||||
|  | 	Gateways map[string]*Gateway | ||||||
|  | 	Message  chan config.Message | ||||||
|  | 	*config.Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewRouter(cfg *config.Config) (*Router, error) { | ||||||
|  | 	r := &Router{} | ||||||
|  | 	r.Config = cfg | ||||||
|  | 	r.Message = make(chan config.Message) | ||||||
|  | 	r.Gateways = make(map[string]*Gateway) | ||||||
|  | 	sgw := samechannelgateway.New(cfg) | ||||||
|  | 	gwconfigs := sgw.GetConfig() | ||||||
|  |  | ||||||
|  | 	for _, entry := range append(gwconfigs, cfg.Gateway...) { | ||||||
|  | 		if !entry.Enable { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if entry.Name == "" { | ||||||
|  | 			return nil, fmt.Errorf("%s", "Gateway without name found") | ||||||
|  | 		} | ||||||
|  | 		if _, ok := r.Gateways[entry.Name]; ok { | ||||||
|  | 			return nil, fmt.Errorf("Gateway with name %s already exists", entry.Name) | ||||||
|  | 		} | ||||||
|  | 		r.Gateways[entry.Name] = New(entry, r) | ||||||
|  | 	} | ||||||
|  | 	return r, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Router) Start() error { | ||||||
|  | 	m := make(map[string]*bridge.Bridge) | ||||||
|  | 	for _, gw := range r.Gateways { | ||||||
|  | 		for _, br := range gw.Bridges { | ||||||
|  | 			m[br.Account] = br | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	for _, br := range m { | ||||||
|  | 		log.Infof("Starting bridge: %s ", br.Account) | ||||||
|  | 		err := br.Connect() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err) | ||||||
|  | 		} | ||||||
|  | 		err = br.JoinChannels() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("Bridge %s failed to join channel: %v", br.Account, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	go r.handleReceive() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Router) getBridge(account string) *bridge.Bridge { | ||||||
|  | 	for _, gw := range r.Gateways { | ||||||
|  | 		if br, ok := gw.Bridges[account]; ok { | ||||||
|  | 			return br | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *Router) handleReceive() { | ||||||
|  | 	for msg := range r.Message { | ||||||
|  | 		if msg.Event == config.EVENT_FAILURE { | ||||||
|  | 		Loop: | ||||||
|  | 			for _, gw := range r.Gateways { | ||||||
|  | 				for _, br := range gw.Bridges { | ||||||
|  | 					if msg.Account == br.Account { | ||||||
|  | 						go gw.reconnectBridge(br) | ||||||
|  | 						break Loop | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if msg.Event == config.EVENT_REJOIN_CHANNELS { | ||||||
|  | 			for _, gw := range r.Gateways { | ||||||
|  | 				for _, br := range gw.Bridges { | ||||||
|  | 					if msg.Account == br.Account { | ||||||
|  | 						br.Joined = make(map[string]bool) | ||||||
|  | 						br.JoinChannels() | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		for _, gw := range r.Gateways { | ||||||
|  | 			if !gw.ignoreMessage(&msg) { | ||||||
|  | 				msg.Timestamp = time.Now() | ||||||
|  | 				gw.modifyMessage(&msg) | ||||||
|  | 				for _, br := range gw.Bridges { | ||||||
|  | 					gw.handleMessage(msg, br) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,103 +1,28 @@ | |||||||
| package samechannelgateway | package samechannelgateway | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/42wim/matterbridge/bridge" |  | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" |  | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type SameChannelGateway struct { | type SameChannelGateway struct { | ||||||
| 	*config.Config | 	*config.Config | ||||||
| 	MyConfig    *config.SameChannelGateway |  | ||||||
| 	Bridges     []bridge.Bridge |  | ||||||
| 	Channels    []string |  | ||||||
| 	ignoreNicks map[string][]string |  | ||||||
| 	Name        string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(cfg *config.Config, gateway *config.SameChannelGateway) error { | func New(cfg *config.Config) *SameChannelGateway { | ||||||
| 	c := make(chan config.Message) | 	return &SameChannelGateway{Config: cfg} | ||||||
| 	gw := &SameChannelGateway{} |  | ||||||
| 	gw.Name = gateway.Name |  | ||||||
| 	gw.Config = cfg |  | ||||||
| 	gw.MyConfig = gateway |  | ||||||
| 	gw.Channels = gateway.Channels |  | ||||||
| 	for _, account := range gateway.Accounts { |  | ||||||
| 		br := config.Bridge{Account: account} |  | ||||||
| 		log.Infof("Starting bridge: %s", account) |  | ||||||
| 		gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c)) |  | ||||||
| 	} |  | ||||||
| 	for _, br := range gw.Bridges { |  | ||||||
| 		err := br.Connect() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err) |  | ||||||
| 		} |  | ||||||
| 		for _, channel := range gw.Channels { |  | ||||||
| 			log.Infof("%s: joining %s", br.FullOrigin(), channel) |  | ||||||
| 			br.JoinChannel(channel) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	gw.handleReceive(c) |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (gw *SameChannelGateway) handleReceive(c chan config.Message) { | func (sgw *SameChannelGateway) GetConfig() []config.Gateway { | ||||||
| 	for { | 	var gwconfigs []config.Gateway | ||||||
| 		select { | 	cfg := sgw.Config | ||||||
| 		case msg := <-c: | 	for _, gw := range cfg.SameChannelGateway { | ||||||
| 			for _, br := range gw.Bridges { | 		gwconfig := config.Gateway{Name: gw.Name, Enable: gw.Enable} | ||||||
| 				gw.handleMessage(msg, br) | 		for _, account := range gw.Accounts { | ||||||
|  | 			for _, channel := range gw.Channels { | ||||||
|  | 				gwconfig.InOut = append(gwconfig.InOut, config.Bridge{Account: account, Channel: channel, SameChannel: true}) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		gwconfigs = append(gwconfigs, gwconfig) | ||||||
| 	} | 	} | ||||||
| } | 	return gwconfigs | ||||||
|  |  | ||||||
| func (gw *SameChannelGateway) handleMessage(msg config.Message, dest bridge.Bridge) { |  | ||||||
| 	// is this a configured channel |  | ||||||
| 	if !gw.validChannel(msg.Channel) { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	// do not send the message to the bridge we come from if also the channel is the same |  | ||||||
| 	if msg.FullOrigin == dest.FullOrigin() { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	gw.modifyMessage(&msg, dest) |  | ||||||
| 	log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, msg.Channel, dest.FullOrigin(), msg.Channel) |  | ||||||
| 	err := dest.Send(msg) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func setNickFormat(msg *config.Message, format string) { |  | ||||||
| 	if format == "" { |  | ||||||
| 		msg.Username = msg.Protocol + "." + msg.Origin + "-" + msg.Username + ": " |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1) |  | ||||||
| 	msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1) |  | ||||||
| 	msg.Username = strings.Replace(msg.Username, "{PROTOCOL}", msg.Protocol, -1) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (gw *SameChannelGateway) modifyMessage(msg *config.Message, dest bridge.Bridge) { |  | ||||||
| 	switch dest.Protocol() { |  | ||||||
| 	case "irc": |  | ||||||
| 		setNickFormat(msg, gw.Config.IRC[dest.Origin()].RemoteNickFormat) |  | ||||||
| 	case "mattermost": |  | ||||||
| 		setNickFormat(msg, gw.Config.Mattermost[dest.Origin()].RemoteNickFormat) |  | ||||||
| 	case "slack": |  | ||||||
| 		setNickFormat(msg, gw.Config.Slack[dest.Origin()].RemoteNickFormat) |  | ||||||
| 	case "discord": |  | ||||||
| 		setNickFormat(msg, gw.Config.Discord[dest.Origin()].RemoteNickFormat) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (gw *SameChannelGateway) validChannel(channel string) bool { |  | ||||||
| 	for _, c := range gw.Channels { |  | ||||||
| 		if c == channel { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								gateway/samechannel/samechannel_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								gateway/samechannel/samechannel_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | package samechannelgateway | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	"github.com/BurntSushi/toml" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var testconfig = ` | ||||||
|  | [mattermost.test] | ||||||
|  | [slack.test] | ||||||
|  |  | ||||||
|  | [[samechannelgateway]] | ||||||
|  |    enable = true | ||||||
|  |    name = "blah" | ||||||
|  |       accounts = [ "mattermost.test","slack.test" ] | ||||||
|  |       channels = [ "testing","testing2","testing10"] | ||||||
|  | ` | ||||||
|  |  | ||||||
|  | func TestGetConfig(t *testing.T) { | ||||||
|  | 	var cfg *config.Config | ||||||
|  | 	if _, err := toml.Decode(testconfig, &cfg); err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 	} | ||||||
|  | 	sgw := New(cfg) | ||||||
|  | 	configs := sgw.GetConfig() | ||||||
|  | 	assert.Equal(t, []config.Gateway{{Name: "blah", Enable: true, In: []config.Bridge(nil), Out: []config.Bridge(nil), InOut: []config.Bridge{{Account: "mattermost.test", Channel: "testing", Options: config.ChannelOptions{Key: ""}, SameChannel: true}, {Account: "mattermost.test", Channel: "testing2", Options: config.ChannelOptions{Key: ""}, SameChannel: true}, {Account: "mattermost.test", Channel: "testing10", Options: config.ChannelOptions{Key: ""}, SameChannel: true}, {Account: "slack.test", Channel: "testing", Options: config.ChannelOptions{Key: ""}, SameChannel: true}, {Account: "slack.test", Channel: "testing2", Options: config.ChannelOptions{Key: ""}, SameChannel: true}, {Account: "slack.test", Channel: "testing10", Options: config.ChannelOptions{Key: ""}, SameChannel: true}}}}, configs) | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								hook/rockethook/rockethook.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								hook/rockethook/rockethook.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | package rockethook | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Message for rocketchat outgoing webhook. | ||||||
|  | type Message struct { | ||||||
|  | 	Token       string `json:"token"` | ||||||
|  | 	ChannelID   string `json:"channel_id"` | ||||||
|  | 	ChannelName string `json:"channel_name"` | ||||||
|  | 	Timestamp   string `json:"timestamp"` | ||||||
|  | 	UserID      string `json:"user_id"` | ||||||
|  | 	UserName    string `json:"user_name"` | ||||||
|  | 	Text        string `json:"text"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Client for Rocketchat. | ||||||
|  | type Client struct { | ||||||
|  | 	In         chan Message | ||||||
|  | 	httpclient *http.Client | ||||||
|  | 	Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Config for client. | ||||||
|  | type Config struct { | ||||||
|  | 	BindAddress        string // Address to listen on | ||||||
|  | 	Token              string // Only allow this token from Rocketchat. (Allow everything when empty) | ||||||
|  | 	InsecureSkipVerify bool   // disable certificate checking | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // New Rocketchat client. | ||||||
|  | func New(url string, config Config) *Client { | ||||||
|  | 	c := &Client{In: make(chan Message), Config: config} | ||||||
|  | 	tr := &http.Transport{ | ||||||
|  | 		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, | ||||||
|  | 	} | ||||||
|  | 	c.httpclient = &http.Client{Transport: tr} | ||||||
|  | 	_, _, err := net.SplitHostPort(c.BindAddress) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("incorrect bindaddress %s", c.BindAddress) | ||||||
|  | 	} | ||||||
|  | 	go c.StartServer() | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // StartServer starts a webserver listening for incoming mattermost POSTS. | ||||||
|  | func (c *Client) StartServer() { | ||||||
|  | 	mux := http.NewServeMux() | ||||||
|  | 	mux.Handle("/", c) | ||||||
|  | 	log.Printf("Listening on http://%v...\n", c.BindAddress) | ||||||
|  | 	if err := http.ListenAndServe(c.BindAddress, mux); err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ServeHTTP implementation. | ||||||
|  | func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 	if r.Method != "POST" { | ||||||
|  | 		log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr) | ||||||
|  | 		http.NotFound(w, r) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	msg := Message{} | ||||||
|  | 	body, err := ioutil.ReadAll(r.Body) | ||||||
|  | 	log.Println(string(body)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 		http.NotFound(w, r) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer r.Body.Close() | ||||||
|  | 	err = json.Unmarshal(body, &msg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 		http.NotFound(w, r) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if msg.Token == "" { | ||||||
|  | 		log.Println("no token from " + r.RemoteAddr) | ||||||
|  | 		http.NotFound(w, r) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	msg.ChannelName = "#" + msg.ChannelName | ||||||
|  | 	if c.Token != "" { | ||||||
|  | 		if msg.Token != c.Token { | ||||||
|  | 			log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) | ||||||
|  | 			http.NotFound(w, r) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	c.In <- msg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Receive returns an incoming message from mattermost outgoing webhooks URL. | ||||||
|  | func (c *Client) Receive() Message { | ||||||
|  | 	var msg Message | ||||||
|  | 	for msg = range c.In { | ||||||
|  | 		return msg | ||||||
|  | 	} | ||||||
|  | 	return msg | ||||||
|  | } | ||||||
| @@ -1,287 +0,0 @@ | |||||||
| #This is configuration for matterbridge. |  | ||||||
| ################################################################### |  | ||||||
| #IRC section |  | ||||||
| ################################################################### |  | ||||||
| [IRC] |  | ||||||
| #Enable enables this bridge |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| Enable=true |  | ||||||
| #irc server to connect to.  |  | ||||||
| #REQUIRED |  | ||||||
| Server="irc.freenode.net:6667" |  | ||||||
|  |  | ||||||
| #Enable to use TLS connection to your irc server.  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| UseTLS=false |  | ||||||
|  |  | ||||||
| #Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts) |  | ||||||
| #It uses NickServNick and NickServPassword as login and password |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| UseSASL=false |  | ||||||
|  |  | ||||||
| #Enable to not verify the certificate on your irc server. i |  | ||||||
| #e.g. when using selfsigned certificates |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| SkipTLSVerify=true |  | ||||||
|  |  | ||||||
| #Your nick on irc.  |  | ||||||
| #REQUIRED |  | ||||||
| Nick="matterbot" |  | ||||||
|  |  | ||||||
| #If you registered your bot with a service like Nickserv on freenode.  |  | ||||||
| #Also being used when UseSASL=true |  | ||||||
| #OPTIONAL |  | ||||||
| NickServNick="nickserv" |  | ||||||
| NickServPassword="secret" |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
| #OPTIONAL (default {BRIDGE}-{NICK}) |  | ||||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}> " |  | ||||||
|  |  | ||||||
| #Nicks you want to ignore.  |  | ||||||
| #Messages from those users will not be sent to other bridges. |  | ||||||
| #OPTIONAL |  | ||||||
| IgnoreNicks="ircspammer1 ircspammer2" |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #XMPP section |  | ||||||
| ################################################################### |  | ||||||
| [XMPP] |  | ||||||
| #Enable enables this bridge |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| Enable=true |  | ||||||
|  |  | ||||||
| #xmpp server to connect to.  |  | ||||||
| #REQUIRED |  | ||||||
| Server="jabber.example.com:5222" |  | ||||||
|  |  | ||||||
| #Jid |  | ||||||
| #REQUIRED |  | ||||||
| Jid="user@example.com" |  | ||||||
|  |  | ||||||
| #Password |  | ||||||
| #REQUIRED |  | ||||||
| Password="yourpass" |  | ||||||
|  |  | ||||||
| #MUC |  | ||||||
| #REQUIRED |  | ||||||
| Muc="conference.jabber.example.com" |  | ||||||
|  |  | ||||||
| #Your nick in the rooms |  | ||||||
| #REQUIRED |  | ||||||
| Nick="xmppbot" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #mattermost section |  | ||||||
| ################################################################### |  | ||||||
|  |  | ||||||
| [mattermost] |  | ||||||
| #Enable enables this bridge |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| Enable=true |  | ||||||
|  |  | ||||||
| #### Settings for webhook matterbridge. |  | ||||||
| #### These settings will not be used when using -plus switch which doesn't use  |  | ||||||
| #### webhooks. |  | ||||||
|  |  | ||||||
| #Url is your incoming webhook url as specified in mattermost.  |  | ||||||
| #See account settings - integrations - incoming webhooks on mattermost. |  | ||||||
| #REQUIRED |  | ||||||
| URL="https://yourdomain/hooks/yourhookkey" |  | ||||||
|  |  | ||||||
| #Address to listen on for outgoing webhook requests from mattermost. |  | ||||||
| #See account settings - integrations - outgoing webhooks on mattermost. |  | ||||||
| #This setting will not be used when using -plus switch which doesn't use  |  | ||||||
| #webhooks |  | ||||||
| #REQUIRED |  | ||||||
| BindAddress="0.0.0.0:9999" |  | ||||||
|  |  | ||||||
| #Icon that will be showed in mattermost.  |  | ||||||
| #OPTIONAL |  | ||||||
| IconURL="http://youricon.png" |  | ||||||
|  |  | ||||||
| #### Settings for matterbridge -plus |  | ||||||
| #### Thse settings will only be used when using the -plus switch. |  | ||||||
|  |  | ||||||
| #The mattermost hostname.  |  | ||||||
| #REQUIRED |  | ||||||
| Server="yourmattermostserver.domain" |  | ||||||
|  |  | ||||||
| #Your team on mattermost.  |  | ||||||
| #REQUIRED |  | ||||||
| Team="yourteam" |  | ||||||
|  |  | ||||||
| #login/pass of your bot.  |  | ||||||
| #Use a dedicated user for this and not your own!  |  | ||||||
| #REQUIRED |  | ||||||
| Login="yourlogin" |  | ||||||
| Password="yourpass" |  | ||||||
|  |  | ||||||
| #Enable this to make a http connection (instead of https) to your mattermost.  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| NoTLS=false |  | ||||||
|  |  | ||||||
| #### Shared settings for matterbridge and -plus |  | ||||||
|  |  | ||||||
| #Enable to not verify the certificate on your mattermost server.  |  | ||||||
| #e.g. when using selfsigned certificates |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| SkipTLSVerify=true |  | ||||||
|  |  | ||||||
| #Enable to show IRC joins/parts in mattermost.  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| ShowJoinPart=false |  | ||||||
|  |  | ||||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  |  | ||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  |  | ||||||
| #mattermost server. If you set PrefixMessagesWithNick to true, each message  |  | ||||||
| #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  |  | ||||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| PrefixMessagesWithNick=false |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
| #OPTIONAL (default {BRIDGE}-{NICK}) |  | ||||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}> " |  | ||||||
|  |  | ||||||
| #how to format the list of IRC nicks when displayed in mattermost.  |  | ||||||
| #Possible options are "table" and "plain" |  | ||||||
| #OPTIONAL (default plain) |  | ||||||
| NickFormatter=plain |  | ||||||
| #How many nicks to list per row for formatters that support this.  |  | ||||||
| #OPTIONAL (default 4) |  | ||||||
| NicksPerRow=4 |  | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages from those users will not be bridged. |  | ||||||
| #OPTIONAL  |  | ||||||
| IgnoreNicks="mmbot spammer2" |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #Gitter section |  | ||||||
| #Best to make a dedicated gitter account for the bot. |  | ||||||
| ################################################################### |  | ||||||
| [Gitter] |  | ||||||
| #Enable enables this bridge |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| Enable=true |  | ||||||
|  |  | ||||||
| #Token to connect with Gitter API |  | ||||||
| #You can get your token by going to https://developer.gitter.im/docs/welcome and SIGN IN |  | ||||||
| #REQUIRED |  | ||||||
| Token="Yourtokenhere" |  | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages of those users will not be bridged. |  | ||||||
| #OPTIONAL  |  | ||||||
| IgnoreNicks="spammer1 spammer2" |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
| #OPTIONAL (default {BRIDGE}-{NICK}) |  | ||||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}> " |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #slack section |  | ||||||
| ################################################################### |  | ||||||
|  |  | ||||||
| [slack] |  | ||||||
| #Enable enables this bridge |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| Enable=true |  | ||||||
|  |  | ||||||
| #### Settings for webhook matterbridge. |  | ||||||
| #### These settings will not be used when useAPI is enabled |  | ||||||
|  |  | ||||||
| #Url is your incoming webhook url as specified in slack |  | ||||||
| #See account settings - integrations - incoming webhooks on slack |  | ||||||
| #REQUIRED (unless useAPI=true) |  | ||||||
| URL="https://hooks.slack.com/services/yourhook" |  | ||||||
|  |  | ||||||
| #Address to listen on for outgoing webhook requests from slack |  | ||||||
| #See account settings - integrations - outgoing webhooks on slack |  | ||||||
| #This setting will not be used when useAPI is eanbled |  | ||||||
| #webhooks |  | ||||||
| #REQUIRED (unless useAPI=true) |  | ||||||
| BindAddress="0.0.0.0:9999" |  | ||||||
|  |  | ||||||
| #Icon that will be showed in slack |  | ||||||
| #OPTIONAL |  | ||||||
| IconURL="http://youricon.png" |  | ||||||
|  |  | ||||||
| #### Settings for using slack API |  | ||||||
| #OPTIONAL |  | ||||||
| useAPI=false |  | ||||||
|  |  | ||||||
| #Token to connect with the Slack API |  | ||||||
| #REQUIRED (when useAPI=true) |  | ||||||
| Token="yourslacktoken" |  | ||||||
|  |  | ||||||
| #### Shared settings for webhooks and API |  | ||||||
|  |  | ||||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  |  | ||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  |  | ||||||
| #slack server. If you set PrefixMessagesWithNick to true, each message  |  | ||||||
| #from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,  |  | ||||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| PrefixMessagesWithNick=false |  | ||||||
|  |  | ||||||
| #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 |  | ||||||
| #OPTIONAL (default {BRIDGE}-{NICK}) |  | ||||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  |  | ||||||
|  |  | ||||||
| #how to format the list of IRC nicks when displayed in slack |  | ||||||
| #Possible options are "table" and "plain" |  | ||||||
| #OPTIONAL (default plain) |  | ||||||
| NickFormatter=plain |  | ||||||
| #How many nicks to list per row for formatters that support this.  |  | ||||||
| #OPTIONAL (default 4) |  | ||||||
| NicksPerRow=4 |  | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages from those users will not be bridged. |  | ||||||
| #OPTIONAL  |  | ||||||
| IgnoreNicks="mmbot spammer2" |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #multiple channel config |  | ||||||
| ################################################################### |  | ||||||
| #You can specify multiple channels.  |  | ||||||
| #The name is just an identifier for you. |  | ||||||
| #REQUIRED (at least 1 channel) |  | ||||||
| [Channel "channel1"]  |  | ||||||
| #Choose the IRC channel to send messages to. |  | ||||||
| IRC="#off-topic" |  | ||||||
| #Choose the mattermost channel to messages to. |  | ||||||
| mattermost="off-topic" |  | ||||||
| #Choose the xmpp channel to send messages to. |  | ||||||
| xmpp="off-topic" |  | ||||||
| #Choose the Gitter channel to send messages to. |  | ||||||
| #Gitter channels are named "user/repo" |  | ||||||
| gitter="42wim/matterbridge" |  | ||||||
| #Choose the slack channel to send messages to. |  | ||||||
| slack="general" |  | ||||||
|  |  | ||||||
| [Channel "testchannel"] |  | ||||||
| IRC="#testing" |  | ||||||
| mattermost="testing" |  | ||||||
| xmpp="testing" |  | ||||||
| gitter="user/repo" |  | ||||||
| slack="testing" |  | ||||||
|  |  | ||||||
| ################################################################### |  | ||||||
| #general |  | ||||||
| ################################################################### |  | ||||||
| [general] |  | ||||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  |  | ||||||
| #OPTIONAL |  | ||||||
| GiphyApiKey="dc6zaTOxFJmzC" |  | ||||||
|  |  | ||||||
| #Enabling plus means you'll use the API version instead of the webhooks one |  | ||||||
| Plus=false |  | ||||||
| @@ -5,11 +5,15 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/gateway" | 	"github.com/42wim/matterbridge/gateway" | ||||||
| 	"github.com/42wim/matterbridge/gateway/samechannel" |  | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/google/gops/agent" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var version = "0.7.0-dev" | var ( | ||||||
|  | 	version = "1.0.1" | ||||||
|  | 	githash string | ||||||
|  | ) | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||||
| @@ -19,42 +23,33 @@ func main() { | |||||||
| 	flagConfig := flag.String("conf", "matterbridge.toml", "config file") | 	flagConfig := flag.String("conf", "matterbridge.toml", "config file") | ||||||
| 	flagDebug := flag.Bool("debug", false, "enable debug") | 	flagDebug := flag.Bool("debug", false, "enable debug") | ||||||
| 	flagVersion := flag.Bool("version", false, "show version") | 	flagVersion := flag.Bool("version", false, "show version") | ||||||
|  | 	flagGops := flag.Bool("gops", false, "enable gops agent") | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
|  | 	if *flagGops { | ||||||
|  | 		agent.Listen(&agent.Options{}) | ||||||
|  | 		defer agent.Close() | ||||||
|  | 	} | ||||||
| 	if *flagVersion { | 	if *flagVersion { | ||||||
| 		fmt.Println("version:", version) | 		fmt.Printf("version: %s %s\n", version, githash) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	flag.Parse() |  | ||||||
| 	if *flagDebug { | 	if *flagDebug { | ||||||
| 		log.Info("enabling debug") | 		log.Info("Enabling debug") | ||||||
| 		log.SetLevel(log.DebugLevel) | 		log.SetLevel(log.DebugLevel) | ||||||
| 	} | 	} | ||||||
| 	fmt.Println("running version", version) | 	log.Printf("Running version %s %s", version, githash) | ||||||
|  | 	if strings.Contains(version, "-dev") { | ||||||
|  | 		log.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.") | ||||||
|  | 	} | ||||||
| 	cfg := config.NewConfig(*flagConfig) | 	cfg := config.NewConfig(*flagConfig) | ||||||
| 	for _, gw := range cfg.SameChannelGateway { | 	r, err := gateway.NewRouter(cfg) | ||||||
| 		if !gw.Enable { | 	if err != nil { | ||||||
| 			continue | 		log.Fatalf("Starting gateway failed: %s", err) | ||||||
| 		} |  | ||||||
| 		fmt.Printf("starting samechannel gateway %#v\n", gw.Name) |  | ||||||
| 		go func(gw config.SameChannelGateway) { |  | ||||||
| 			err := samechannelgateway.New(cfg, &gw) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Debugf("starting gateway failed %#v", err) |  | ||||||
| 			} |  | ||||||
| 		}(gw) |  | ||||||
| 	} | 	} | ||||||
|  | 	err = r.Start() | ||||||
| 	for _, gw := range cfg.Gateway { | 	if err != nil { | ||||||
| 		if !gw.Enable { | 		log.Fatalf("Starting gateway failed: %s", err) | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		fmt.Printf("starting gateway %#v\n", gw.Name) |  | ||||||
| 		go func(gw config.Gateway) { |  | ||||||
| 			err := gateway.New(cfg, &gw) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Debugf("starting gateway failed %#v", err) |  | ||||||
| 			} |  | ||||||
| 		}(gw) |  | ||||||
| 	} | 	} | ||||||
|  | 	log.Printf("Gateway(s) started succesfully. Now relaying messages") | ||||||
| 	select {} | 	select {} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #This is configuration for matterbridge. | #This is configuration for matterbridge. | ||||||
|  | #WARNING: as this file contains credentials, be sure to set correct file permissions | ||||||
| ################################################################### | ################################################################### | ||||||
| #IRC section | #IRC section | ||||||
| ################################################################### | ################################################################### | ||||||
| @@ -13,6 +14,10 @@ | |||||||
| #REQUIRED | #REQUIRED | ||||||
| Server="irc.freenode.net:6667" | Server="irc.freenode.net:6667" | ||||||
|  |  | ||||||
|  | #Password for irc server (if necessary) | ||||||
|  | #OPTIONAL (default "") | ||||||
|  | Password="" | ||||||
|  |  | ||||||
| #Enable to use TLS connection to your irc server.  | #Enable to use TLS connection to your irc server.  | ||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| UseTLS=false | UseTLS=false | ||||||
| @@ -37,18 +42,6 @@ Nick="matterbot" | |||||||
| NickServNick="nickserv" | NickServNick="nickserv" | ||||||
| NickServPassword="secret" | NickServPassword="secret" | ||||||
|  |  | ||||||
| #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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge |  | ||||||
| #OPTIONAL (default empty) |  | ||||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " |  | ||||||
|  |  | ||||||
| #Nicks you want to ignore.  |  | ||||||
| #Messages from those users will not be sent to other bridges. |  | ||||||
| #OPTIONAL |  | ||||||
| IgnoreNicks="ircspammer1 ircspammer2" |  | ||||||
|  |  | ||||||
| #Flood control | #Flood control | ||||||
| #Delay in milliseconds between each message send to the IRC server | #Delay in milliseconds between each message send to the IRC server | ||||||
| #OPTIONAL (default 1300) | #OPTIONAL (default 1300) | ||||||
| @@ -56,10 +49,39 @@ MessageDelay=1300 | |||||||
|  |  | ||||||
| #Maximum amount of messages to hold in queue. If queue is full  | #Maximum amount of messages to hold in queue. If queue is full  | ||||||
| #messages will be dropped.  | #messages will be dropped.  | ||||||
| #<clipped> will be add to the message that fills the queue. | #<message clipped> will be add to the message that fills the queue. | ||||||
| #OPTIONAL (default 30) | #OPTIONAL (default 30) | ||||||
| MessageQueue=30 | MessageQueue=30 | ||||||
|  |  | ||||||
|  | #Maximum length of message sent to irc server. If it exceeds | ||||||
|  | #<message clipped> will be add to the message. | ||||||
|  | #OPTIONAL (default 400) | ||||||
|  | MessageLength=400 | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #XMPP section | #XMPP section | ||||||
| ################################################################### | ################################################################### | ||||||
| @@ -89,6 +111,84 @@ Muc="conference.jabber.example.com" | |||||||
| #REQUIRED | #REQUIRED | ||||||
| Nick="xmppbot" | Nick="xmppbot" | ||||||
|  |  | ||||||
|  | #Enable to not verify the certificate on your xmpp server. | ||||||
|  | #e.g. when using selfsigned certificates | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | SkipTLSVerify=true | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #hipchat section | ||||||
|  | ################################################################### | ||||||
|  | #Go to https://www.hipchat.com/account/xmpp this will show you the necessary data | ||||||
|  | #to fill in the section below | ||||||
|  | [xmpp.hipchat] | ||||||
|  | #xmpp server to connect to.  | ||||||
|  | #REQUIRED | ||||||
|  | Server="chat.hipchat.com:5222" | ||||||
|  |  | ||||||
|  | #Jabber ID | ||||||
|  | #REQUIRED | ||||||
|  | Jid="12345_12345@chat.hipchat.com" | ||||||
|  |  | ||||||
|  | #Password (your hipchat password) | ||||||
|  | #REQUIRED | ||||||
|  | Password="yourpass" | ||||||
|  |  | ||||||
|  | #Conference (MUC) domain | ||||||
|  | #REQUIRED | ||||||
|  | Muc="conf.hipchat.com" | ||||||
|  |  | ||||||
|  | #Room nickname | ||||||
|  | #REQUIRED | ||||||
|  | Nick="yourlogin" | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="spammer1 spammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #mattermost section | #mattermost section | ||||||
| @@ -99,43 +199,17 @@ Nick="xmppbot" | |||||||
| #REQUIRED | #REQUIRED | ||||||
|  |  | ||||||
| [mattermost.work] | [mattermost.work] | ||||||
| #### Settings for webhook matterbridge. | #The mattermost hostname. (do not prefix it with http or https) | ||||||
| #### These settings will not be used when useAPI is enabled | #REQUIRED (when not using webhooks) | ||||||
|  | Server="yourmattermostserver.domain"  | ||||||
| #Url is your incoming webhook url as specified in mattermost.  |  | ||||||
| #See account settings - integrations - incoming webhooks on mattermost. |  | ||||||
| #REQUIRED (unless useAPI=true) |  | ||||||
| URL="https://yourdomain/hooks/yourhookkey" |  | ||||||
|  |  | ||||||
| #Address to listen on for outgoing webhook requests from mattermost. |  | ||||||
| #See account settings - integrations - outgoing webhooks on mattermost. |  | ||||||
| #This setting will not be used when using -plus switch which doesn't use  |  | ||||||
| #webhooks |  | ||||||
| #REQUIRED (unless useAPI=true) |  | ||||||
| BindAddress="0.0.0.0:9999" |  | ||||||
|  |  | ||||||
| #Icon that will be showed in mattermost.  |  | ||||||
| #OPTIONAL |  | ||||||
| IconURL="http://youricon.png" |  | ||||||
|  |  | ||||||
| #### Settings for matterbridge -plus |  | ||||||
| #### Thse settings will only be used when using the -plus switch. |  | ||||||
|  |  | ||||||
| #### Settings for using matterbridge API |  | ||||||
| #OPTIONAL |  | ||||||
| useAPI=false |  | ||||||
|  |  | ||||||
| #The mattermost hostname.  |  | ||||||
| #REQUIRED (when useAPI=true) |  | ||||||
| Server="yourmattermostserver.domain" |  | ||||||
|  |  | ||||||
| #Your team on mattermost.  | #Your team on mattermost.  | ||||||
| #REQUIRED (when useAPI=true) | #REQUIRED (when not using webhooks) | ||||||
| Team="yourteam" | Team="yourteam" | ||||||
|  |  | ||||||
| #login/pass of your bot.  | #login/pass of your bot.  | ||||||
| #Use a dedicated user for this and not your own!  | #Use a dedicated user for this and not your own!  | ||||||
| #REQUIRED (when useAPI=true) | #REQUIRED (when not using webhooks) | ||||||
| Login="yourlogin" | Login="yourlogin" | ||||||
| Password="yourpass" | Password="yourpass" | ||||||
|  |  | ||||||
| @@ -143,32 +217,36 @@ Password="yourpass" | |||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| NoTLS=false | NoTLS=false | ||||||
|  |  | ||||||
| #### Shared settings for matterbridge and -plus | #### Settings for webhook matterbridge. | ||||||
|  | #NOT RECOMMENDED TO USE INCOMING/OUTGOING WEBHOOK. USE DEDICATED BOT USER WHEN POSSIBLE! | ||||||
|  | #You don't need to configure this, if you have configured the settings  | ||||||
|  | #above. | ||||||
|  |  | ||||||
|  | #Url is your incoming webhook url as specified in mattermost.  | ||||||
|  | #See account settings - integrations - incoming webhooks on mattermost. | ||||||
|  | #If specified, messages will be sent to mattermost using this URL | ||||||
|  | #OPTIONAL | ||||||
|  | WebhookURL="https://yourdomain/hooks/yourhookkey" | ||||||
|  |  | ||||||
|  | #Address to listen on for outgoing webhook requests from mattermost. | ||||||
|  | #See account settings - integrations - outgoing webhooks on mattermost. | ||||||
|  | #If specified, messages will be received from mattermost on this ip:port | ||||||
|  | #(this will only work if WebhookURL above is also configured) | ||||||
|  | #OPTIONAL  | ||||||
|  | WebhookBindAddress="0.0.0.0:9999" | ||||||
|  |  | ||||||
|  | #Icon that will be showed in mattermost.  | ||||||
|  | #This only works when WebhookURL is configured | ||||||
|  | #OPTIONAL | ||||||
|  | IconURL="http://youricon.png" | ||||||
|  |  | ||||||
|  | #### End settings for webhook matterbridge. | ||||||
|  |  | ||||||
| #Enable to not verify the certificate on your mattermost server.  | #Enable to not verify the certificate on your mattermost server.  | ||||||
| #e.g. when using selfsigned certificates | #e.g. when using selfsigned certificates | ||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| SkipTLSVerify=true | SkipTLSVerify=true | ||||||
|  |  | ||||||
| #Enable to show IRC joins/parts in mattermost.  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| ShowJoinPart=false |  | ||||||
|  |  | ||||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  |  | ||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  |  | ||||||
| #mattermost server. If you set PrefixMessagesWithNick to true, each message  |  | ||||||
| #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  |  | ||||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| PrefixMessagesWithNick=false |  | ||||||
|  |  | ||||||
| #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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge |  | ||||||
| #OPTIONAL (default empty) |  | ||||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " |  | ||||||
|  |  | ||||||
| #how to format the list of IRC nicks when displayed in mattermost.  | #how to format the list of IRC nicks when displayed in mattermost.  | ||||||
| #Possible options are "table" and "plain" | #Possible options are "table" and "plain" | ||||||
| #OPTIONAL (default plain) | #OPTIONAL (default plain) | ||||||
| @@ -177,9 +255,44 @@ NickFormatter="plain" | |||||||
| #OPTIONAL (default 4) | #OPTIONAL (default 4) | ||||||
| NicksPerRow=4 | NicksPerRow=4 | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||||
| #OPTIONAL  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
| IgnoreNicks="mmbot spammer2" | #mattermost server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  | ||||||
|  | #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #Disable sending of edits to other bridges | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | EditDisable=false | ||||||
|  |  | ||||||
|  | #Message to be appended to every edited message | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | EditSuffix=" (edited)" | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #Gitter section | #Gitter section | ||||||
| @@ -197,9 +310,16 @@ IgnoreNicks="mmbot spammer2" | |||||||
| #REQUIRED | #REQUIRED | ||||||
| Token="Yourtokenhere" | Token="Yourtokenhere" | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages of those users will not be bridged. | #Nicks you want to ignore.  | ||||||
| #OPTIONAL  | #Messages from those users will not be sent to other bridges. | ||||||
| IgnoreNicks="spammer1 spammer2" | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
| #RemoteNickFormat defines how remote users appear on this bridge  | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
| @@ -208,6 +328,11 @@ IgnoreNicks="spammer1 spammer2" | |||||||
| #OPTIONAL (default empty) | #OPTIONAL (default empty) | ||||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||||
|  |  | ||||||
|  | #Enable to show users joins/parts from other bridges  | ||||||
|  | #Only works hiding/show messages from irc and mattermost bridge for now | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #slack section | #slack section | ||||||
| ################################################################### | ################################################################### | ||||||
| @@ -217,30 +342,29 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | |||||||
| #In this example we use [slack.hobby] | #In this example we use [slack.hobby] | ||||||
| #REQUIRED | #REQUIRED | ||||||
| [slack.hobby] | [slack.hobby] | ||||||
| #### Settings for webhook matterbridge. | #Token to connect with the Slack API | ||||||
| #### These settings will not be used when useAPI is enabled | #You'll have to use a test/api-token using a dedicated user and not a bot token. | ||||||
|  | #See https://github.com/42wim/matterbridge/issues/75 for more info. | ||||||
|  | #Use https://api.slack.com/custom-integrations/legacy-tokens | ||||||
|  | #REQUIRED (when not using webhooks) | ||||||
|  | Token="yourslacktoken" | ||||||
|  |  | ||||||
|  | #### Settings for webhook matterbridge. | ||||||
|  | #NOT RECOMMENDED TO USE INCOMING/OUTGOING WEBHOOK. USE SLACK API | ||||||
|  | #AND DEDICATED BOT USER WHEN POSSIBLE! | ||||||
| #Url is your incoming webhook url as specified in slack | #Url is your incoming webhook url as specified in slack | ||||||
| #See account settings - integrations - incoming webhooks on slack | #See account settings - integrations - incoming webhooks on slack | ||||||
| #REQUIRED (unless useAPI=true) | #OPTIONAL | ||||||
| URL="https://hooks.slack.com/services/yourhook" | WebhookURL="https://hooks.slack.com/services/yourhook" | ||||||
|  |  | ||||||
|  | #NOT RECOMMENDED TO USE INCOMING/OUTGOING WEBHOOK. USE SLACK API | ||||||
|  | #AND DEDICATED BOT USER WHEN POSSIBLE! | ||||||
| #Address to listen on for outgoing webhook requests from slack | #Address to listen on for outgoing webhook requests from slack | ||||||
| #See account settings - integrations - outgoing webhooks on slack | #See account settings - integrations - outgoing webhooks on slack | ||||||
| #This setting will not be used when useAPI is eanbled | #This setting will not be used when useAPI is eanbled | ||||||
| #webhooks | #webhooks | ||||||
| #REQUIRED (unless useAPI=true) |  | ||||||
| BindAddress="0.0.0.0:9999" |  | ||||||
|  |  | ||||||
| #### Settings for using slack API |  | ||||||
| #OPTIONAL | #OPTIONAL | ||||||
| useAPI=false | WebhookBindAddress="0.0.0.0:9999" | ||||||
|  |  | ||||||
| #Token to connect with the Slack API |  | ||||||
| #REQUIRED (when useAPI=true) |  | ||||||
| Token="yourslacktoken" |  | ||||||
|  |  | ||||||
| #### Shared settings for webhooks and API |  | ||||||
|  |  | ||||||
| #Icon that will be showed in slack | #Icon that will be showed in slack | ||||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
| @@ -249,21 +373,6 @@ Token="yourslacktoken" | |||||||
| #OPTIONAL | #OPTIONAL | ||||||
| IconURL="https://robohash.org/{NICK}.png?size=48x48" | IconURL="https://robohash.org/{NICK}.png?size=48x48" | ||||||
|  |  | ||||||
| #Whether to prefix messages from other bridges to mattermost with RemoteNickFormat |  | ||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  |  | ||||||
| #slack server. If you set PrefixMessagesWithNick to true, each message  |  | ||||||
| #from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,  |  | ||||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  |  | ||||||
| #OPTIONAL (default false) |  | ||||||
| PrefixMessagesWithNick=false |  | ||||||
|  |  | ||||||
| #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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge |  | ||||||
| #OPTIONAL (default empty) |  | ||||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " |  | ||||||
|  |  | ||||||
| #how to format the list of IRC nicks when displayed in slack | #how to format the list of IRC nicks when displayed in slack | ||||||
| #Possible options are "table" and "plain" | #Possible options are "table" and "plain" | ||||||
| #OPTIONAL (default plain) | #OPTIONAL (default plain) | ||||||
| @@ -272,9 +381,44 @@ NickFormatter="plain" | |||||||
| #OPTIONAL (default 4) | #OPTIONAL (default 4) | ||||||
| NicksPerRow=4 | NicksPerRow=4 | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | #Disable sending of edits to other bridges | ||||||
| #OPTIONAL  | #OPTIONAL (default false) | ||||||
| IgnoreNicks="mmbot spammer2" | EditDisable=true | ||||||
|  |  | ||||||
|  | #Message to be appended to every edited message | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | EditSuffix=" (edited)" | ||||||
|  |  | ||||||
|  | #Whether to prefix messages from other bridges to mattermost with RemoteNickFormat | ||||||
|  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
|  | #slack server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,  | ||||||
|  | #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #discord section | #discord section | ||||||
| @@ -288,16 +432,44 @@ IgnoreNicks="mmbot spammer2" | |||||||
| #Token to connect with Discord API | #Token to connect with Discord API | ||||||
| #You can get your token by following the instructions on  | #You can get your token by following the instructions on  | ||||||
| #https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token | #https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token | ||||||
| #The "Bot" tag needs to be added before the token | #If you want roles/groups mentions to be shown with names instead of ID, you'll need to give your bot the "Manage Roles" permission. | ||||||
| #REQUIRED | #REQUIRED | ||||||
| Token="Bot Yourtokenhere" | Token="Yourtokenhere" | ||||||
|  |  | ||||||
| #REQUIRED | #REQUIRED | ||||||
| Server="yourservername" | Server="yourservername" | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages of those users will not be bridged. | #Shows title, description and URL of embedded messages (sent by other bots) | ||||||
| #OPTIONAL  | #OPTIONAL (default false) | ||||||
| IgnoreNicks="spammer1 spammer2" | ShowEmbeds=false | ||||||
|  |  | ||||||
|  | #Shows the username (minus the discriminator) instead of the server nickname | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | UseUserName=false | ||||||
|  |  | ||||||
|  | #Specify WebhookURL. If given, will relay messages using the Webhook, which gives a better look to messages. | ||||||
|  | #This only works if you have one discord channel, if you have multiple discord channels you'll have to specify it in the gateway config | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | WebhookURL="Yourwebhooktokenhere" | ||||||
|  |  | ||||||
|  | #Disable sending of edits to other bridges | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | EditDisable=false | ||||||
|  |  | ||||||
|  | #Message to be appended to every edited message | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | EditSuffix=" (edited)" | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
| #RemoteNickFormat defines how remote users appear on this bridge  | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
| @@ -306,6 +478,283 @@ IgnoreNicks="spammer1 spammer2" | |||||||
| #OPTIONAL (default empty) | #OPTIONAL (default empty) | ||||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||||
|  |  | ||||||
|  | #Enable to show users joins/parts from other bridges  | ||||||
|  | #Only works hiding/show messages from irc and mattermost bridge for now | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #telegram section | ||||||
|  | ################################################################### | ||||||
|  | [telegram] | ||||||
|  |  | ||||||
|  | #You can configure multiple servers "[telegram.name]" or "[telegram.name2]" | ||||||
|  | #In this example we use [telegram.secure] | ||||||
|  | #REQUIRED | ||||||
|  | [telegram.secure] | ||||||
|  | #Token to connect with telegram API | ||||||
|  | #See https://core.telegram.org/bots#6-botfather and https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau | ||||||
|  | #REQUIRED | ||||||
|  | Token="Yourtokenhere" | ||||||
|  |  | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | #Only supported format is "HTML", messages will be sent in html parsemode. | ||||||
|  | #See https://core.telegram.org/bots/api#html-style | ||||||
|  | MessageFormat="" | ||||||
|  |  | ||||||
|  | #If enabled use the "First Name" as username. If this is empty use the Username | ||||||
|  | #If disabled use the "Username" as username. If this is empty use the First Name  | ||||||
|  | #If all names are empty, username will be "unknown" | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | UseFirstName=false | ||||||
|  |  | ||||||
|  | #WARNING! If enabled this will relay GIF/stickers/documents and other attachments as URLs | ||||||
|  | #Those URLs will contain your bot-token. This may not be what you want. | ||||||
|  | #For now there is no secure way to relay GIF/stickers/documents without seeing your token. | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | UseInsecureURL=false | ||||||
|  |  | ||||||
|  | #Disable sending of edits to other bridges | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | EditDisable=false | ||||||
|  |  | ||||||
|  | #Message to be appended to every edited message | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | EditSuffix=" (edited)" | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="spammer1 spammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #rocketchat section | ||||||
|  | ################################################################### | ||||||
|  | [rocketchat] | ||||||
|  | #You can configure multiple servers "[rocketchat.name]" or "[rocketchat.name2]" | ||||||
|  | #In this example we use [rocketchat.work] | ||||||
|  | #REQUIRED | ||||||
|  |  | ||||||
|  | [rocketchat.rockme] | ||||||
|  | #Url is your incoming webhook url as specified in rocketchat | ||||||
|  | #Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook | ||||||
|  | #See administration - integrations - new integration - incoming webhook | ||||||
|  | #REQUIRED | ||||||
|  | WebhookURL="https://yourdomain/hooks/yourhookkey" | ||||||
|  |  | ||||||
|  | #Address to listen on for outgoing webhook requests from rocketchat. | ||||||
|  | #See administration - integrations - new integration - outgoing webhook | ||||||
|  | #REQUIRED  | ||||||
|  | WebhookBindAddress="0.0.0.0:9999" | ||||||
|  |  | ||||||
|  | #Your nick/username as specified in your incoming webhook "Post as" setting | ||||||
|  | #REQUIRED | ||||||
|  | Nick="matterbot" | ||||||
|  |  | ||||||
|  | #Enable this to make a http connection (instead of https) to your rocketchat | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | NoTLS=false | ||||||
|  |  | ||||||
|  | #Enable to not verify the certificate on your rocketchat server.  | ||||||
|  | #e.g. when using selfsigned certificates | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | SkipTLSVerify=true | ||||||
|  |  | ||||||
|  | #Whether to prefix messages from other bridges to rocketchat with the sender's nick.  | ||||||
|  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
|  | #rocketchat server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #matrix section | ||||||
|  | ################################################################### | ||||||
|  | [matrix] | ||||||
|  | #You can configure multiple servers "[matrix.name]" or "[matrix.name2]" | ||||||
|  | #In this example we use [matrix.neo] | ||||||
|  | #REQUIRED | ||||||
|  |  | ||||||
|  | [matrix.neo] | ||||||
|  | #Server is your homeserver (eg https://matrix.org) | ||||||
|  | #REQUIRED | ||||||
|  | Server="https://matrix.org" | ||||||
|  |  | ||||||
|  | #login/pass of your bot.  | ||||||
|  | #Use a dedicated user for this and not your own!  | ||||||
|  | #Messages sent from this user will not be relayed to avoid loops. | ||||||
|  | #REQUIRED  | ||||||
|  | Login="yourlogin" | ||||||
|  | Password="yourpass" | ||||||
|  |  | ||||||
|  | #Whether to send the homeserver suffix. eg ":matrix.org" in @username:matrix.org | ||||||
|  | #to other bridges, or only send "username".(true only sends username) | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | NoHomeServerSuffix=false | ||||||
|  |  | ||||||
|  | #Whether to prefix messages from other bridges to matrix with the sender's nick.  | ||||||
|  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
|  | #matrix server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="spammer1 spammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #steam section | ||||||
|  | ################################################################### | ||||||
|  | [steam] | ||||||
|  | #You can configure multiple servers "[steam.name]" or "[steam.name2]" | ||||||
|  | #In this example we use [steam.gamechat] | ||||||
|  | #REQUIRED | ||||||
|  |  | ||||||
|  | [steam.gamechat] | ||||||
|  | #login/pass of your bot.  | ||||||
|  | #Use a dedicated user for this and not your own account!  | ||||||
|  | #REQUIRED  | ||||||
|  | Login="yourlogin" | ||||||
|  | Password="yourpass" | ||||||
|  |  | ||||||
|  | #steamguard mail authcode (not the 2FA code) | ||||||
|  | #OPTIONAL  | ||||||
|  | Authcode="ABCE12" | ||||||
|  |  | ||||||
|  | #Whether to prefix messages from other bridges to matrix with the sender's nick.  | ||||||
|  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
|  | #matrix server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to matrix will by default be prefixed by the RemoteNickFormat setting. i | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore.  | ||||||
|  | #Messages from those users will not be sent to other bridges. | ||||||
|  | #OPTIONAL | ||||||
|  | IgnoreNicks="spammer1 spammer2" | ||||||
|  |  | ||||||
|  | #Messages you want to ignore.  | ||||||
|  | #Messages matching these regexp will be ignored and not sent to other bridges | ||||||
|  | #See https://regex-golang.appspot.com/assets/html/index.html for more regex info | ||||||
|  | #OPTIONAL (example below ignores messages starting with ~~ or messages containing badword | ||||||
|  | IgnoreMessages="^~~ badword" | ||||||
|  |  | ||||||
|  | #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 "{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 | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | ShowJoinPart=false | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #API | ||||||
|  | ################################################################### | ||||||
|  | [api] | ||||||
|  | #You can configure multiple API hooks | ||||||
|  | #In this example we use [api.local] | ||||||
|  | #REQUIRED | ||||||
|  |  | ||||||
|  | [api.local] | ||||||
|  | #Address to listen on for API | ||||||
|  | #REQUIRED  | ||||||
|  | BindAddress="127.0.0.1:4242" | ||||||
|  |  | ||||||
|  | #Amount of messages to keep in memory | ||||||
|  | Buffer=1000 | ||||||
|  |  | ||||||
|  | #Bearer token used for authentication | ||||||
|  | #curl -H "Authorization: Bearer token" http://localhost:4242/api/messages | ||||||
|  | #OPTIONAL (no authorization if token is empty) | ||||||
|  | Token="mytoken" | ||||||
|  |  | ||||||
|  | #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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | RemoteNickFormat="{NICK}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #General configuration | ||||||
|  | ################################################################### | ||||||
|  | #Settings here override specific settings for each protocol | ||||||
|  | [general] | ||||||
|  | #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 "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||||
|  | #OPTIONAL (default empty) | ||||||
|  | RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #Gateway configuration | #Gateway configuration | ||||||
| @@ -318,11 +767,11 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | |||||||
| #from [[gateway.in]] to. | #from [[gateway.in]] to. | ||||||
| # | # | ||||||
| #Most of the time [[gateway.in]] and [[gateway.out]] are the same if you  | #Most of the time [[gateway.in]] and [[gateway.out]] are the same if you  | ||||||
| #want bidirectional bridging. | #want bidirectional bridging. You can then use [[gateway.inout]] | ||||||
| # | # | ||||||
|  |  | ||||||
| [[gateway]] | [[gateway]] | ||||||
| #OPTIONAL (not used for now) | #REQUIRED and UNIQUE | ||||||
| name="gateway1" | name="gateway1" | ||||||
| #Enable enables this gateway | #Enable enables this gateway | ||||||
| ##OPTIONAL (default false) | ##OPTIONAL (default false) | ||||||
| @@ -338,7 +787,7 @@ enable=true | |||||||
|     #channel to connect on that account |     #channel to connect on that account | ||||||
|     #How to specify them for the different bridges: |     #How to specify them for the different bridges: | ||||||
|     # |     # | ||||||
|     #irc        - #channel (# is required) |     #irc        - #channel (# is required) (this needs to be lowercase!) | ||||||
|     #mattermost - channel (the channel name as seen in the URL, not the displayname) |     #mattermost - channel (the channel name as seen in the URL, not the displayname) | ||||||
|     #gitter     - username/room  |     #gitter     - username/room  | ||||||
|     #xmpp       - channel |     #xmpp       - channel | ||||||
| @@ -346,21 +795,61 @@ enable=true | |||||||
|     #discord    - channel (without the #) |     #discord    - channel (without the #) | ||||||
|     #           - ID:123456789 (where 123456789 is the channel ID)  |     #           - ID:123456789 (where 123456789 is the channel ID)  | ||||||
|     #               (https://github.com/42wim/matterbridge/issues/57) |     #               (https://github.com/42wim/matterbridge/issues/57) | ||||||
|  |     #telegram   - chatid (a large negative number, eg -123456789) | ||||||
|  |     #             see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau) | ||||||
|  |     #hipchat    - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel) | ||||||
|  |     #rocketchat - #channel (# is required (also needed for private channels!) | ||||||
|  |     #matrix     - #channel:server (eg #yourchannel:matrix.org)  | ||||||
|  |     #           - encrypted rooms are not supported in matrix | ||||||
|  |     #steam      - chatid (a large number).  | ||||||
|  |     #             The number in the URL when you click "enter chat room" in the browser | ||||||
|  |     #                   | ||||||
|     #REQUIRED |     #REQUIRED | ||||||
|     channel="#testing" |     channel="#testing" | ||||||
|  |  | ||||||
|     [[gateway.in]] |         #OPTIONAL - only used for IRC protocol at the moment | ||||||
|     account="mattermost.work" |         [gateway.in.options] | ||||||
|     channel="off-topic" |         #OPTIONAL - your irc channel key | ||||||
|  |         key="yourkey" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #[[gateway.out]] specifies the account and channels we will sent messages to. | ||||||
|     [[gateway.out]] |     [[gateway.out]] | ||||||
|     account="irc.freenode" |     account="irc.freenode" | ||||||
|     channel="#testing" |     channel="#testing" | ||||||
|  |  | ||||||
|     [[gateway.out]] |         #OPTIONAL - only used for IRC protocol at the moment | ||||||
|  |         [gateway.out.options] | ||||||
|  |         #OPTIONAL - your irc channel key | ||||||
|  |         key="yourkey" | ||||||
|  |  | ||||||
|  |     #[[gateway.inout]] can be used when then channel will be used to receive from  | ||||||
|  |     #and send messages to | ||||||
|  |     [[gateway.inout]] | ||||||
|     account="mattermost.work" |     account="mattermost.work" | ||||||
|     channel="off-topic" |     channel="off-topic" | ||||||
|  |  | ||||||
|  |         #OPTIONAL - only used for IRC protocol at the moment | ||||||
|  |         [gateway.inout.options] | ||||||
|  |         #OPTIONAL - your irc channel key | ||||||
|  |         key="yourkey" | ||||||
|  |  | ||||||
|  |     [[gateway.inout]] | ||||||
|  |     account="discord.game" | ||||||
|  |     channel="mygreatgame" | ||||||
|  |  | ||||||
|  |         #OPTIONAL - webhookurl only works for discord (it needs a different URL for each cahnnel) | ||||||
|  |         [gateway.inout.options] | ||||||
|  |         webhookurl=""https://discordapp.com/api/webhooks/123456789123456789/C9WPqExYWONPDZabcdef-def1434FGFjstasJX9pYht73y" | ||||||
|  |  | ||||||
|  |     #API example | ||||||
|  |     #[[gateway.inout]] | ||||||
|  |     #account="api.local" | ||||||
|  |     #channel="api" | ||||||
|  |     #To send data to the api: | ||||||
|  |     #curl -XPOST -H 'Content-Type: application/json'  -d '{"text":"test","username":"randomuser","gateway":"gateway1"}' http://localhost:4242/api/message | ||||||
|  |     #To read from the api: | ||||||
|  |     #curl http://localhost:4242/api/messages | ||||||
|  |  | ||||||
| #If you want to do a 1:1 mapping between protocols where the channelnames are the same | #If you want to do a 1:1 mapping between protocols where the channelnames are the same | ||||||
| #e.g. slack and mattermost you can use the samechannelgateway configuration | #e.g. slack and mattermost you can use the samechannelgateway configuration | ||||||
| @@ -368,6 +857,7 @@ enable=true | |||||||
| #channel testing on slack and vice versa. (and for the channel testing2 and testing3) | #channel testing on slack and vice versa. (and for the channel testing2 and testing3) | ||||||
|  |  | ||||||
| [[samechannelgateway]] | [[samechannelgateway]] | ||||||
|  |    name="samechannel1" | ||||||
|    enable = false |    enable = false | ||||||
|    accounts = [ "mattermost.work","slack.hobby" ] |    accounts = [ "mattermost.work","slack.hobby" ] | ||||||
|    channels = [ "testing","testing2","testing3"] |    channels = [ "testing","testing2","testing3"] | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | #WARNING: as this file contains credentials, be sure to set correct file permissions | ||||||
| [irc] | [irc] | ||||||
|     [irc.freenode] |     [irc.freenode] | ||||||
|     Server="irc.freenode.net:6667" |     Server="irc.freenode.net:6667" | ||||||
| @@ -6,7 +7,8 @@ | |||||||
| [mattermost] | [mattermost] | ||||||
|     [mattermost.work] |     [mattermost.work] | ||||||
|     useAPI=true |     useAPI=true | ||||||
|     Server="yourmattermostserver.domain" |     #do not prefix it wit http:// or https:// | ||||||
|  |     Server="yourmattermostserver.domain"  | ||||||
|     Team="yourteam" |     Team="yourteam" | ||||||
|     Login="yourlogin" |     Login="yourlogin" | ||||||
|     Password="yourpass" |     Password="yourpass" | ||||||
| @@ -15,18 +17,19 @@ | |||||||
| [[gateway]] | [[gateway]] | ||||||
| name="gateway1" | name="gateway1" | ||||||
| enable=true | enable=true | ||||||
|     [[gateway.in]] |     [[gateway.inout]] | ||||||
|     account="irc.freenode" |     account="irc.freenode" | ||||||
|     channel="#testing" |     channel="#testing" | ||||||
|  |  | ||||||
|     [[gateway.in]] |     [[gateway.inout]] | ||||||
|     account="mattermost.work" |  | ||||||
|     channel="off-topic" |  | ||||||
|  |  | ||||||
|     [[gateway.out]] |  | ||||||
|     account="irc.freenode" |  | ||||||
|     channel="#testing" |  | ||||||
|  |  | ||||||
|     [[gateway.out]] |  | ||||||
|     account="mattermost.work" |     account="mattermost.work" | ||||||
|     channel="off-topic" |     channel="off-topic" | ||||||
|  |      | ||||||
|  | #simpler config possible since v0.10.2 | ||||||
|  | #[[gateway]] | ||||||
|  | #name="gateway2" | ||||||
|  | #enable=true | ||||||
|  | #inout = [ | ||||||
|  | #    { account="irc.freenode", channel="#testing", options={key="channelkey"}}, | ||||||
|  | #    { account="mattermost.work", channel="off-topic" }, | ||||||
|  | #] | ||||||
|   | |||||||
| @@ -1,12 +1,15 @@ | |||||||
| package matterclient | package matterclient | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/md5" | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/cookiejar" | 	"net/http/cookiejar" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -14,6 +17,7 @@ import ( | |||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
|  |  | ||||||
| 	"github.com/gorilla/websocket" | 	"github.com/gorilla/websocket" | ||||||
|  | 	"github.com/hashicorp/golang-lru" | ||||||
| 	"github.com/jpillora/backoff" | 	"github.com/jpillora/backoff" | ||||||
| 	"github.com/mattermost/platform/model" | 	"github.com/mattermost/platform/model" | ||||||
| ) | ) | ||||||
| @@ -34,6 +38,8 @@ type Message struct { | |||||||
| 	Channel  string | 	Channel  string | ||||||
| 	Username string | 	Username string | ||||||
| 	Text     string | 	Text     string | ||||||
|  | 	Type     string | ||||||
|  | 	UserID   string | ||||||
| } | } | ||||||
|  |  | ||||||
| type Team struct { | type Team struct { | ||||||
| @@ -47,19 +53,22 @@ type Team struct { | |||||||
| type MMClient struct { | type MMClient struct { | ||||||
| 	sync.RWMutex | 	sync.RWMutex | ||||||
| 	*Credentials | 	*Credentials | ||||||
| 	Team        *Team | 	Team          *Team | ||||||
| 	OtherTeams  []*Team | 	OtherTeams    []*Team | ||||||
| 	Client      *model.Client | 	Client        *model.Client | ||||||
| 	User        *model.User | 	User          *model.User | ||||||
| 	Users       map[string]*model.User | 	Users         map[string]*model.User | ||||||
| 	MessageChan chan *Message | 	MessageChan   chan *Message | ||||||
| 	log         *log.Entry | 	log           *log.Entry | ||||||
| 	WsClient    *websocket.Conn | 	WsClient      *websocket.Conn | ||||||
| 	WsQuit      bool | 	WsQuit        bool | ||||||
| 	WsAway      bool | 	WsAway        bool | ||||||
| 	WsConnected bool | 	WsConnected   bool | ||||||
| 	WsSequence  int64 | 	WsSequence    int64 | ||||||
| 	WsPingChan  chan *model.WebSocketResponse | 	WsPingChan    chan *model.WebSocketResponse | ||||||
|  | 	ServerVersion string | ||||||
|  | 	OnWsConnect   func() | ||||||
|  | 	lruCache      *lru.Cache | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(login, pass, team, server string) *MMClient { | func New(login, pass, team, server string) *MMClient { | ||||||
| @@ -67,6 +76,7 @@ func New(login, pass, team, server string) *MMClient { | |||||||
| 	mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)} | 	mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)} | ||||||
| 	mmclient.log = log.WithFields(log.Fields{"module": "matterclient"}) | 	mmclient.log = log.WithFields(log.Fields{"module": "matterclient"}) | ||||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||||
|  | 	mmclient.lruCache, _ = lru.New(500) | ||||||
| 	return mmclient | 	return mmclient | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -80,6 +90,11 @@ func (m *MMClient) SetLogLevel(level string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) Login() error { | func (m *MMClient) Login() error { | ||||||
|  | 	// check if this is a first connect or a reconnection | ||||||
|  | 	firstConnection := true | ||||||
|  | 	if m.WsConnected { | ||||||
|  | 		firstConnection = false | ||||||
|  | 	} | ||||||
| 	m.WsConnected = false | 	m.WsConnected = false | ||||||
| 	if m.WsQuit { | 	if m.WsQuit { | ||||||
| 		return nil | 		return nil | ||||||
| @@ -90,14 +105,35 @@ func (m *MMClient) Login() error { | |||||||
| 		Jitter: true, | 		Jitter: true, | ||||||
| 	} | 	} | ||||||
| 	uriScheme := "https://" | 	uriScheme := "https://" | ||||||
| 	wsScheme := "wss://" |  | ||||||
| 	if m.NoTLS { | 	if m.NoTLS { | ||||||
| 		uriScheme = "http://" | 		uriScheme = "http://" | ||||||
| 		wsScheme = "ws://" |  | ||||||
| 	} | 	} | ||||||
| 	// login to mattermost | 	// login to mattermost | ||||||
| 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) | 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) | ||||||
| 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}, Proxy: http.ProxyFromEnvironment} | ||||||
|  | 	m.Client.HttpClient.Timeout = time.Second * 10 | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		d := b.Duration() | ||||||
|  | 		// bogus call to get the serverversion | ||||||
|  | 		_, err := m.Client.GetClientProperties() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("%#v", err.Error()) | ||||||
|  | 		} | ||||||
|  | 		if firstConnection && !supportedVersion(m.Client.ServerVersion) { | ||||||
|  | 			return fmt.Errorf("unsupported mattermost version: %s", m.Client.ServerVersion) | ||||||
|  | 		} | ||||||
|  | 		m.ServerVersion = m.Client.ServerVersion | ||||||
|  | 		if m.ServerVersion == "" { | ||||||
|  | 			m.log.Debugf("Server not up yet, reconnecting in %s", d) | ||||||
|  | 			time.Sleep(d) | ||||||
|  | 		} else { | ||||||
|  | 			m.log.Infof("Found version %s", m.ServerVersion) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	b.Reset() | ||||||
|  |  | ||||||
| 	var myinfo *model.Result | 	var myinfo *model.Result | ||||||
| 	var appErr *model.AppError | 	var appErr *model.AppError | ||||||
| 	var logmsg = "trying login" | 	var logmsg = "trying login" | ||||||
| @@ -120,16 +156,12 @@ func (m *MMClient) Login() error { | |||||||
| 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) | 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | 			_, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||||
| 		} | 		} | ||||||
| 		if appErr != nil { | 		if appErr != nil { | ||||||
| 			d := b.Duration() | 			d := b.Duration() | ||||||
| 			m.log.Debug(appErr.DetailedError) | 			m.log.Debug(appErr.DetailedError) | ||||||
| 			//TODO more generic fix needed | 			if firstConnection { | ||||||
| 			if !strings.Contains(appErr.DetailedError, "connection refused") && |  | ||||||
| 				!strings.Contains(appErr.DetailedError, "invalid character") && |  | ||||||
| 				!strings.Contains(appErr.DetailedError, "connection reset by peer") && |  | ||||||
| 				!strings.Contains(appErr.DetailedError, "connection timed out") { |  | ||||||
| 				if appErr.Message == "" { | 				if appErr.Message == "" { | ||||||
| 					return errors.New(appErr.DetailedError) | 					return errors.New(appErr.DetailedError) | ||||||
| 				} | 				} | ||||||
| @@ -156,14 +188,33 @@ func (m *MMClient) Login() error { | |||||||
| 	// set our team id as default route | 	// set our team id as default route | ||||||
| 	m.Client.SetTeamId(m.Team.Id) | 	m.Client.SetTeamId(m.Team.Id) | ||||||
|  |  | ||||||
|  | 	m.wsConnect() | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) wsConnect() { | ||||||
|  | 	b := &backoff.Backoff{ | ||||||
|  | 		Min:    time.Second, | ||||||
|  | 		Max:    5 * time.Minute, | ||||||
|  | 		Jitter: true, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.WsConnected = false | ||||||
|  | 	wsScheme := "wss://" | ||||||
|  | 	if m.NoTLS { | ||||||
|  | 		wsScheme = "ws://" | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// setup websocket connection | 	// setup websocket connection | ||||||
| 	wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX + "/users/websocket" | 	wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V3 + "/users/websocket" | ||||||
| 	header := http.Header{} | 	header := http.Header{} | ||||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||||
|  |  | ||||||
| 	m.log.Debug("WsClient: making connection") | 	m.log.Debugf("WsClient: making connection: %s", wsurl) | ||||||
| 	for { | 	for { | ||||||
| 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||||
|  | 		var err error | ||||||
| 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) | 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			d := b.Duration() | 			d := b.Duration() | ||||||
| @@ -173,14 +224,12 @@ func (m *MMClient) Login() error { | |||||||
| 		} | 		} | ||||||
| 		break | 		break | ||||||
| 	} | 	} | ||||||
| 	b.Reset() |  | ||||||
|  |  | ||||||
|  | 	m.log.Debug("WsClient: connected") | ||||||
| 	m.WsSequence = 1 | 	m.WsSequence = 1 | ||||||
| 	m.WsPingChan = make(chan *model.WebSocketResponse) | 	m.WsPingChan = make(chan *model.WebSocketResponse) | ||||||
| 	// only start to parse WS messages when login is completely done | 	// only start to parse WS messages when login is completely done | ||||||
| 	m.WsConnected = true | 	m.WsConnected = true | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) Logout() error { | func (m *MMClient) Logout() error { | ||||||
| @@ -188,6 +237,10 @@ func (m *MMClient) Logout() error { | |||||||
| 	m.WsQuit = true | 	m.WsQuit = true | ||||||
| 	m.WsClient.Close() | 	m.WsClient.Close() | ||||||
| 	m.WsClient.UnderlyingConn().Close() | 	m.WsClient.UnderlyingConn().Close() | ||||||
|  | 	if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { | ||||||
|  | 		m.log.Debug("Not invalidating session in logout, credential is a token") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 	_, err := m.Client.Logout() | 	_, err := m.Client.Logout() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -213,21 +266,24 @@ func (m *MMClient) WsReceiver() { | |||||||
| 		if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil { | 		if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil { | ||||||
| 			m.log.Error("error:", err) | 			m.log.Error("error:", err) | ||||||
| 			// reconnect | 			// reconnect | ||||||
| 			m.Login() | 			m.wsConnect() | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var event model.WebSocketEvent | 		var event model.WebSocketEvent | ||||||
| 		if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | 		if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | ||||||
| 			m.log.Debugf("WsReceiver: %#v", event) | 			m.log.Debugf("WsReceiver event: %#v", event) | ||||||
| 			msg := &Message{Raw: &event, Team: m.Credentials.Team} | 			msg := &Message{Raw: &event, Team: m.Credentials.Team} | ||||||
| 			m.parseMessage(msg) | 			m.parseMessage(msg) | ||||||
| 			m.MessageChan <- msg | 			// check if we didn't empty the message | ||||||
|  | 			if msg.Text != "" { | ||||||
|  | 				m.MessageChan <- msg | ||||||
|  | 			} | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var response model.WebSocketResponse | 		var response model.WebSocketResponse | ||||||
| 		if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | 		if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | ||||||
| 			m.log.Debugf("WsReceiver: %#v", response) | 			m.log.Debugf("WsReceiver response: %#v", response) | ||||||
| 			m.parseResponse(response) | 			m.parseResponse(response) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -236,7 +292,7 @@ func (m *MMClient) WsReceiver() { | |||||||
|  |  | ||||||
| func (m *MMClient) parseMessage(rmsg *Message) { | func (m *MMClient) parseMessage(rmsg *Message) { | ||||||
| 	switch rmsg.Raw.Event { | 	switch rmsg.Raw.Event { | ||||||
| 	case model.WEBSOCKET_EVENT_POSTED: | 	case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED: | ||||||
| 		m.parseActionPost(rmsg) | 		m.parseActionPost(rmsg) | ||||||
| 		/* | 		/* | ||||||
| 			case model.ACTION_USER_REMOVED: | 			case model.ACTION_USER_REMOVED: | ||||||
| @@ -257,25 +313,43 @@ func (m *MMClient) parseResponse(rmsg model.WebSocketResponse) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) parseActionPost(rmsg *Message) { | func (m *MMClient) parseActionPost(rmsg *Message) { | ||||||
|  | 	// add post to cache, if it already exists don't relay this again. | ||||||
|  | 	// this should fix reposts | ||||||
|  | 	if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok { | ||||||
|  | 		m.log.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string)) | ||||||
|  | 		rmsg.Text = "" | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string))) | 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string))) | ||||||
| 	// we don't have the user, refresh the userlist | 	// we don't have the user, refresh the userlist | ||||||
| 	if m.GetUser(data.UserId) == nil { | 	if m.GetUser(data.UserId) == nil { | ||||||
| 		m.UpdateUsers() | 		m.log.Infof("User %s is not known, ignoring message %s", data) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	rmsg.Username = m.GetUser(data.UserId).Username | 	rmsg.Username = m.GetUserName(data.UserId) | ||||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) | 	rmsg.Channel = m.GetChannelName(data.ChannelId) | ||||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | 	rmsg.UserID = data.UserId | ||||||
|  | 	rmsg.Type = data.Type | ||||||
|  | 	teamid, _ := rmsg.Raw.Data["team_id"].(string) | ||||||
|  | 	// edit messsages have no team_id for some reason | ||||||
|  | 	if teamid == "" { | ||||||
|  | 		// we can find the team_id from the channelid | ||||||
|  | 		teamid = m.GetChannelTeamId(data.ChannelId) | ||||||
|  | 		rmsg.Raw.Data["team_id"] = teamid | ||||||
|  | 	} | ||||||
|  | 	if teamid != "" { | ||||||
|  | 		rmsg.Team = m.GetTeamName(teamid) | ||||||
|  | 	} | ||||||
| 	// direct message | 	// direct message | ||||||
| 	if rmsg.Raw.Data["channel_type"] == "D" { | 	if rmsg.Raw.Data["channel_type"] == "D" { | ||||||
| 		rmsg.Channel = m.GetUser(data.UserId).Username | 		rmsg.Channel = m.GetUser(data.UserId).Username | ||||||
| 	} | 	} | ||||||
| 	rmsg.Text = data.Message | 	rmsg.Text = data.Message | ||||||
| 	rmsg.Post = data | 	rmsg.Post = data | ||||||
| 	return |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateUsers() error { | func (m *MMClient) UpdateUsers() error { | ||||||
| 	mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | 	mmusers, err := m.Client.GetProfiles(0, 50000, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New(err.DetailedError) | 		return errors.New(err.DetailedError) | ||||||
| 	} | 	} | ||||||
| @@ -290,7 +364,12 @@ func (m *MMClient) UpdateChannels() error { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New(err.DetailedError) | 		return errors.New(err.DetailedError) | ||||||
| 	} | 	} | ||||||
| 	mmchannels2, err := m.Client.GetMoreChannels("") | 	var mmchannels2 *model.Result | ||||||
|  | 	if m.mmVersion() >= 3.08 { | ||||||
|  | 		mmchannels2, err = m.Client.GetMoreChannelsPage(0, 5000) | ||||||
|  | 	} else { | ||||||
|  | 		mmchannels2, err = m.Client.GetMoreChannels("") | ||||||
|  | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.New(err.DetailedError) | 		return errors.New(err.DetailedError) | ||||||
| 	} | 	} | ||||||
| @@ -305,9 +384,21 @@ func (m *MMClient) GetChannelName(channelId string) string { | |||||||
| 	m.RLock() | 	m.RLock() | ||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | 		if t == nil { | ||||||
| 			if channel.Id == channelId { | 			continue | ||||||
| 				return channel.Name | 		} | ||||||
|  | 		if t.Channels != nil { | ||||||
|  | 			for _, channel := range *t.Channels { | ||||||
|  | 				if channel.Id == channelId { | ||||||
|  | 					return channel.Name | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if t.MoreChannels != nil { | ||||||
|  | 			for _, channel := range *t.MoreChannels { | ||||||
|  | 				if channel.Id == channelId { | ||||||
|  | 					return channel.Name | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -322,7 +413,7 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { | |||||||
| 	} | 	} | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		if t.Id == teamId { | 		if t.Id == teamId { | ||||||
| 			for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | 			for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||||
| 				if channel.Name == name { | 				if channel.Name == name { | ||||||
| 					return channel.Id | 					return channel.Id | ||||||
| 				} | 				} | ||||||
| @@ -332,11 +423,24 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { | |||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) GetChannelTeamId(id string) string { | ||||||
|  | 	m.RLock() | ||||||
|  | 	defer m.RUnlock() | ||||||
|  | 	for _, t := range append(m.OtherTeams, m.Team) { | ||||||
|  | 		for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||||
|  | 			if channel.Id == id { | ||||||
|  | 				return channel.TeamId | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetChannelHeader(channelId string) string { | func (m *MMClient) GetChannelHeader(channelId string) string { | ||||||
| 	m.RLock() | 	m.RLock() | ||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | 		for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||||
| 			if channel.Id == channelId { | 			if channel.Id == channelId { | ||||||
| 				return channel.Header | 				return channel.Header | ||||||
| 			} | 			} | ||||||
| @@ -354,7 +458,7 @@ func (m *MMClient) PostMessage(channelId string, text string) { | |||||||
| func (m *MMClient) JoinChannel(channelId string) error { | func (m *MMClient) JoinChannel(channelId string) error { | ||||||
| 	m.RLock() | 	m.RLock() | ||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	for _, c := range m.Team.Channels.Channels { | 	for _, c := range *m.Team.Channels { | ||||||
| 		if c.Id == channelId { | 		if c.Id == channelId { | ||||||
| 			m.log.Debug("Not joining ", channelId, " already joined.") | 			m.log.Debug("Not joining ", channelId, " already joined.") | ||||||
| 			return nil | 			return nil | ||||||
| @@ -397,7 +501,7 @@ func (m *MMClient) GetPublicLink(filename string) string { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "" | 		return "" | ||||||
| 	} | 	} | ||||||
| 	return res.Data.(string) | 	return res | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { | func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||||
| @@ -407,7 +511,26 @@ func (m *MMClient) GetPublicLinks(filenames []string) []string { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		output = append(output, res.Data.(string)) | 		output = append(output, res) | ||||||
|  | 	} | ||||||
|  | 	return output | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) GetFileLinks(filenames []string) []string { | ||||||
|  | 	uriScheme := "https://" | ||||||
|  | 	if m.NoTLS { | ||||||
|  | 		uriScheme = "http://" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var output []string | ||||||
|  | 	for _, f := range filenames { | ||||||
|  | 		res, err := m.Client.GetPublicLink(f) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// public links is probably disabled, create the link ourselves | ||||||
|  | 			output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get") | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		output = append(output, res) | ||||||
| 	} | 	} | ||||||
| 	return output | 	return output | ||||||
| } | } | ||||||
| @@ -425,6 +548,14 @@ func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | |||||||
|  |  | ||||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | func (m *MMClient) UpdateLastViewed(channelId string) { | ||||||
| 	m.log.Debugf("posting lastview %#v", channelId) | 	m.log.Debugf("posting lastview %#v", channelId) | ||||||
|  | 	if m.mmVersion() >= 3.08 { | ||||||
|  | 		view := model.ChannelView{ChannelId: channelId} | ||||||
|  | 		res, _ := m.Client.ViewChannel(view) | ||||||
|  | 		if !res { | ||||||
|  | 			m.log.Errorf("ChannelView update for %s failed", channelId) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId, true) | 	_, err := m.Client.UpdateLastViewedAt(channelId, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		m.log.Error(err) | 		m.log.Error(err) | ||||||
| @@ -432,15 +563,15 @@ func (m *MMClient) UpdateLastViewed(channelId string) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) UsernamesInChannel(channelId string) []string { | func (m *MMClient) UsernamesInChannel(channelId string) []string { | ||||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(channelId, 5000, "") | 	res, err := m.Client.GetProfilesInChannel(channelId, 0, 50000, "") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) | 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) | ||||||
| 		return []string{} | 		return []string{} | ||||||
| 	} | 	} | ||||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) | 	members := res.Data.(map[string]*model.User) | ||||||
| 	result := []string{} | 	result := []string{} | ||||||
| 	for _, member := range extra.Members { | 	for _, member := range members { | ||||||
| 		result = append(result, member.Username) | 		result = append(result, member.Nickname) | ||||||
| 	} | 	} | ||||||
| 	return result | 	return result | ||||||
| } | } | ||||||
| @@ -467,11 +598,16 @@ func (m *MMClient) SendDirectMessage(toUserId string, msg string) { | |||||||
| 	_, err := m.Client.CreateDirectChannel(toUserId) | 	_, err := m.Client.CreateDirectChannel(toUserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) | 		m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	channelName := model.GetDMNameFromIds(toUserId, m.User.Id) | 	channelName := model.GetDMNameFromIds(toUserId, m.User.Id) | ||||||
|  |  | ||||||
| 	// update our channels | 	// update our channels | ||||||
| 	mmchannels, _ := m.Client.GetChannels("") | 	mmchannels, err := m.Client.GetChannels("") | ||||||
|  | 	if err != nil { | ||||||
|  | 		m.log.Debug("SendDirectMessage: Couldn't update channels") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	m.Lock() | 	m.Lock() | ||||||
| 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | ||||||
| 	m.Unlock() | 	m.Unlock() | ||||||
| @@ -500,10 +636,10 @@ func (m *MMClient) GetChannels() []*model.Channel { | |||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	var channels []*model.Channel | 	var channels []*model.Channel | ||||||
| 	// our primary team channels first | 	// our primary team channels first | ||||||
| 	channels = append(channels, m.Team.Channels.Channels...) | 	channels = append(channels, *m.Team.Channels...) | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		if t.Id != m.Team.Id { | 		if t.Id != m.Team.Id { | ||||||
| 			channels = append(channels, t.Channels.Channels...) | 			channels = append(channels, *t.Channels...) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return channels | 	return channels | ||||||
| @@ -515,7 +651,7 @@ func (m *MMClient) GetMoreChannels() []*model.Channel { | |||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	var channels []*model.Channel | 	var channels []*model.Channel | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		channels = append(channels, t.MoreChannels.Channels...) | 		channels = append(channels, *t.MoreChannels...) | ||||||
| 	} | 	} | ||||||
| 	return channels | 	return channels | ||||||
| } | } | ||||||
| @@ -526,8 +662,10 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | |||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	var channels []*model.Channel | 	var channels []*model.Channel | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		channels = append(channels, t.Channels.Channels...) | 		channels = append(channels, *t.Channels...) | ||||||
| 		channels = append(channels, t.MoreChannels.Channels...) | 		if t.MoreChannels != nil { | ||||||
|  | 			channels = append(channels, *t.MoreChannels...) | ||||||
|  | 		} | ||||||
| 		for _, c := range channels { | 		for _, c := range channels { | ||||||
| 			if c.Id == channelId { | 			if c.Id == channelId { | ||||||
| 				return t.Id | 				return t.Id | ||||||
| @@ -540,12 +678,12 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | |||||||
| func (m *MMClient) GetLastViewedAt(channelId string) int64 { | func (m *MMClient) GetLastViewedAt(channelId string) int64 { | ||||||
| 	m.RLock() | 	m.RLock() | ||||||
| 	defer m.RUnlock() | 	defer m.RUnlock() | ||||||
| 	for _, t := range m.OtherTeams { | 	res, err := m.Client.GetChannel(channelId, "") | ||||||
| 		if _, ok := t.Channels.Members[channelId]; ok { | 	if err != nil { | ||||||
| 			return t.Channels.Members[channelId].LastViewedAt | 		return model.GetMillis() | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return 0 | 	data := res.Data.(*model.ChannelData) | ||||||
|  | 	return data.Member.LastViewedAt | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetUsers() map[string]*model.User { | func (m *MMClient) GetUsers() map[string]*model.User { | ||||||
| @@ -559,13 +697,30 @@ func (m *MMClient) GetUsers() map[string]*model.User { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetUser(userId string) *model.User { | func (m *MMClient) GetUser(userId string) *model.User { | ||||||
| 	m.RLock() | 	m.Lock() | ||||||
| 	defer m.RUnlock() | 	defer m.Unlock() | ||||||
|  | 	_, ok := m.Users[userId] | ||||||
|  | 	if !ok { | ||||||
|  | 		res, err := m.Client.GetProfilesByIds([]string{userId}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		u := res.Data.(map[string]*model.User)[userId] | ||||||
|  | 		m.Users[userId] = u | ||||||
|  | 	} | ||||||
| 	return m.Users[userId] | 	return m.Users[userId] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) GetUserName(userId string) string { | ||||||
|  | 	user := m.GetUser(userId) | ||||||
|  | 	if user != nil { | ||||||
|  | 		return user.Username | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetStatus(userId string) string { | func (m *MMClient) GetStatus(userId string) string { | ||||||
| 	res, err := m.Client.GetStatuses() | 	res, err := m.Client.GetStatusesByIds([]string{userId}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "" | 		return "" | ||||||
| 	} | 	} | ||||||
| @@ -579,11 +734,38 @@ func (m *MMClient) GetStatus(userId string) string { | |||||||
| 	return "offline" | 	return "offline" | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) GetStatuses() map[string]string { | ||||||
|  | 	var ok bool | ||||||
|  | 	statuses := make(map[string]string) | ||||||
|  | 	res, err := m.Client.GetStatuses() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return statuses | ||||||
|  | 	} | ||||||
|  | 	if statuses, ok = res.Data.(map[string]string); ok { | ||||||
|  | 		for userId, status := range statuses { | ||||||
|  | 			statuses[userId] = "offline" | ||||||
|  | 			if status == model.STATUS_AWAY { | ||||||
|  | 				statuses[userId] = "away" | ||||||
|  | 			} | ||||||
|  | 			if status == model.STATUS_ONLINE { | ||||||
|  | 				statuses[userId] = "online" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return statuses | ||||||
|  | } | ||||||
|  |  | ||||||
| func (m *MMClient) GetTeamId() string { | func (m *MMClient) GetTeamId() string { | ||||||
| 	return m.Team.Id | 	return m.Team.Id | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) StatusLoop() { | func (m *MMClient) StatusLoop() { | ||||||
|  | 	retries := 0 | ||||||
|  | 	backoff := time.Second * 60 | ||||||
|  | 	if m.OnWsConnect != nil { | ||||||
|  | 		m.OnWsConnect() | ||||||
|  | 	} | ||||||
|  | 	m.log.Debug("StatusLoop:", m.OnWsConnect) | ||||||
| 	for { | 	for { | ||||||
| 		if m.WsQuit { | 		if m.WsQuit { | ||||||
| 			return | 			return | ||||||
| @@ -594,13 +776,23 @@ func (m *MMClient) StatusLoop() { | |||||||
| 			select { | 			select { | ||||||
| 			case <-m.WsPingChan: | 			case <-m.WsPingChan: | ||||||
| 				m.log.Debug("WS PONG received") | 				m.log.Debug("WS PONG received") | ||||||
|  | 				backoff = time.Second * 60 | ||||||
| 			case <-time.After(time.Second * 5): | 			case <-time.After(time.Second * 5): | ||||||
| 				m.Logout() | 				if retries > 3 { | ||||||
| 				m.WsQuit = false | 					m.Logout() | ||||||
| 				m.Login() | 					m.WsQuit = false | ||||||
|  | 					m.Login() | ||||||
|  | 					if m.OnWsConnect != nil { | ||||||
|  | 						m.OnWsConnect() | ||||||
|  | 					} | ||||||
|  | 					go m.WsReceiver() | ||||||
|  | 				} else { | ||||||
|  | 					retries++ | ||||||
|  | 					backoff = time.Second * 5 | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		time.Sleep(time.Second * 60) | 		time.Sleep(backoff) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -619,11 +811,24 @@ func (m *MMClient) initUser() error { | |||||||
| 	//m.log.Debug("initUser(): loading all team data") | 	//m.log.Debug("initUser(): loading all team data") | ||||||
| 	for _, v := range initData.Teams { | 	for _, v := range initData.Teams { | ||||||
| 		m.Client.SetTeamId(v.Id) | 		m.Client.SetTeamId(v.Id) | ||||||
| 		mmusers, _ := m.Client.GetProfiles(v.Id, "") | 		mmusers, err := m.Client.GetProfiles(0, 50000, "") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New(err.DetailedError) | ||||||
|  | 		} | ||||||
| 		t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} | 		t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} | ||||||
| 		mmchannels, _ := m.Client.GetChannels("") | 		mmchannels, err := m.Client.GetChannels("") | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New(err.DetailedError) | ||||||
|  | 		} | ||||||
| 		t.Channels = mmchannels.Data.(*model.ChannelList) | 		t.Channels = mmchannels.Data.(*model.ChannelList) | ||||||
| 		mmchannels, _ = m.Client.GetMoreChannels("") | 		if m.mmVersion() >= 3.08 { | ||||||
|  | 			mmchannels, err = m.Client.GetMoreChannelsPage(0, 5000) | ||||||
|  | 		} else { | ||||||
|  | 			mmchannels, err = m.Client.GetMoreChannels("") | ||||||
|  | 		} | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.New(err.DetailedError) | ||||||
|  | 		} | ||||||
| 		t.MoreChannels = mmchannels.Data.(*model.ChannelList) | 		t.MoreChannels = mmchannels.Data.(*model.ChannelList) | ||||||
| 		m.OtherTeams = append(m.OtherTeams, t) | 		m.OtherTeams = append(m.OtherTeams, t) | ||||||
| 		if v.Name == m.Credentials.Team { | 		if v.Name == m.Credentials.Team { | ||||||
| @@ -648,3 +853,29 @@ func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) err | |||||||
| 	m.WsClient.WriteJSON(req) | 	m.WsClient.WriteJSON(req) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) mmVersion() float64 { | ||||||
|  | 	v, _ := strconv.ParseFloat(string(m.ServerVersion[0:2])+"0"+string(m.ServerVersion[2]), 64) | ||||||
|  | 	if string(m.ServerVersion[4]) == "." { | ||||||
|  | 		v, _ = strconv.ParseFloat(m.ServerVersion[0:4], 64) | ||||||
|  | 	} | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func supportedVersion(version string) bool { | ||||||
|  | 	if strings.HasPrefix(version, "3.5.0") || | ||||||
|  | 		strings.HasPrefix(version, "3.6.0") || | ||||||
|  | 		strings.HasPrefix(version, "3.7.0") || | ||||||
|  | 		strings.HasPrefix(version, "3.8.0") || | ||||||
|  | 		strings.HasPrefix(version, "3.9.0") || | ||||||
|  | 		strings.HasPrefix(version, "3.10.0") || | ||||||
|  | 		strings.HasPrefix(version, "4.0") || | ||||||
|  | 		strings.HasPrefix(version, "4.1") { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func digestString(s string) string { | ||||||
|  | 	return fmt.Sprintf("%x", md5.Sum([]byte(s))) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // OMessage for mattermost incoming webhook. (send to mattermost) | // OMessage for mattermost incoming webhook. (send to mattermost) | ||||||
| @@ -42,6 +43,7 @@ type IMessage struct { | |||||||
| 	ServiceId   string `schema:"service_id"` | 	ServiceId   string `schema:"service_id"` | ||||||
| 	Text        string `schema:"text"` | 	Text        string `schema:"text"` | ||||||
| 	TriggerWord string `schema:"trigger_word"` | 	TriggerWord string `schema:"trigger_word"` | ||||||
|  | 	FileIDs     string `schema:"file_ids"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // Client for Mattermost. | // Client for Mattermost. | ||||||
| @@ -82,8 +84,14 @@ func New(url string, config Config) *Client { | |||||||
| func (c *Client) StartServer() { | func (c *Client) StartServer() { | ||||||
| 	mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
| 	mux.Handle("/", c) | 	mux.Handle("/", c) | ||||||
|  | 	srv := &http.Server{ | ||||||
|  | 		ReadTimeout:  5 * time.Second, | ||||||
|  | 		WriteTimeout: 10 * time.Second, | ||||||
|  | 		Handler:      mux, | ||||||
|  | 		Addr:         c.BindAddress, | ||||||
|  | 	} | ||||||
| 	log.Printf("Listening on http://%v...\n", c.BindAddress) | 	log.Printf("Listening on http://%v...\n", c.BindAddress) | ||||||
| 	if err := http.ListenAndServe(c.BindAddress, mux); err != nil { | 	if err := srv.ListenAndServe(); err != nil { | ||||||
| 		log.Fatal(err) | 		log.Fatal(err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -127,12 +135,11 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||||
|  |  | ||||||
| // Receive returns an incoming message from mattermost outgoing webhooks URL. | // Receive returns an incoming message from mattermost outgoing webhooks URL. | ||||||
| func (c *Client) Receive() IMessage { | func (c *Client) Receive() IMessage { | ||||||
| 	for { | 	var msg IMessage | ||||||
| 		select { | 	for msg := range c.In { | ||||||
| 		case msg := <-c.In: | 		return msg | ||||||
| 			return msg |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 	return msg | ||||||
| } | } | ||||||
|  |  | ||||||
| // Send sends a msg to mattermost incoming webhooks URL. | // Send sends a msg to mattermost incoming webhooks URL. | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								migration.md
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								migration.md
									
									
									
									
									
								
							| @@ -1,50 +0,0 @@ | |||||||
| # Breaking changes from 0.4 to 0.5 for matterbridge (webhooks version) |  | ||||||
| ## IRC section |  | ||||||
| ### Server |  | ||||||
| Port removed, added to server |  | ||||||
| ``` |  | ||||||
| server="irc.freenode.net" |  | ||||||
| port=6667 |  | ||||||
| ``` |  | ||||||
| changed to |  | ||||||
| ``` |  | ||||||
| server="irc.freenode.net:6667" |  | ||||||
| ``` |  | ||||||
| ### Channel |  | ||||||
| Removed see Channels section below |  | ||||||
|  |  | ||||||
| ### UseSlackCircumfix=true |  | ||||||
| Removed, can be done by using ```RemoteNickFormat="<{NICK}> "``` |  | ||||||
|  |  | ||||||
| ## Mattermost section |  | ||||||
| ### BindAddress |  | ||||||
| Port removed, added to BindAddress |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| BindAddress="0.0.0.0" |  | ||||||
| port=9999 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| changed to |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| BindAddress="0.0.0.0:9999" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Token |  | ||||||
| Removed |  | ||||||
|  |  | ||||||
| ## Channels section |  | ||||||
| ``` |  | ||||||
| [Token "outgoingwebhooktoken1"]  |  | ||||||
| IRCChannel="#off-topic" |  | ||||||
| MMChannel="off-topic" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| changed to |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| [Channel "channelnameofchoice"]  |  | ||||||
| IRC="#off-topic" |  | ||||||
| Mattermost="off-topic" |  | ||||||
| ``` |  | ||||||
							
								
								
									
										156
									
								
								vendor/github.com/thoj/go-ircevent/irc.go → vendor/github.com/42wim/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										156
									
								
								vendor/github.com/thoj/go-ircevent/irc.go → vendor/github.com/42wim/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -74,7 +74,9 @@ func (irc *Connection) readLoop() { | |||||||
| 				irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) | 				irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			irc.Lock() | ||||||
| 			irc.lastMessage = time.Now() | 			irc.lastMessage = time.Now() | ||||||
|  | 			irc.Unlock() | ||||||
| 			event, err := parseToEvent(msg) | 			event, err := parseToEvent(msg) | ||||||
| 			event.Connection = irc | 			event.Connection = irc | ||||||
| 			if err == nil { | 			if err == nil { | ||||||
| @@ -85,6 +87,17 @@ func (irc *Connection) readLoop() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Unescape tag values as defined in the IRCv3.2 message tags spec | ||||||
|  | // http://ircv3.net/specs/core/message-tags-3.2.html | ||||||
|  | func unescapeTagValue(value string) string { | ||||||
|  | 	value = strings.Replace(value, "\\:", ";", -1) | ||||||
|  | 	value = strings.Replace(value, "\\s", " ", -1) | ||||||
|  | 	value = strings.Replace(value, "\\\\", "\\", -1) | ||||||
|  | 	value = strings.Replace(value, "\\r", "\r", -1) | ||||||
|  | 	value = strings.Replace(value, "\\n", "\n", -1) | ||||||
|  | 	return value | ||||||
|  | } | ||||||
|  | 
 | ||||||
| //Parse raw irc messages | //Parse raw irc messages | ||||||
| func parseToEvent(msg string) (*Event, error) { | func parseToEvent(msg string) (*Event, error) { | ||||||
| 	msg = strings.TrimSuffix(msg, "\n") //Remove \r\n | 	msg = strings.TrimSuffix(msg, "\n") //Remove \r\n | ||||||
| @@ -93,6 +106,26 @@ func parseToEvent(msg string) (*Event, error) { | |||||||
| 	if len(msg) < 5 { | 	if len(msg) < 5 { | ||||||
| 		return nil, errors.New("Malformed msg from server") | 		return nil, errors.New("Malformed msg from server") | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if msg[0] == '@' { | ||||||
|  | 		// IRCv3 Message Tags | ||||||
|  | 		if i := strings.Index(msg, " "); i > -1 { | ||||||
|  | 			event.Tags = make(map[string]string) | ||||||
|  | 			tags := strings.Split(msg[1:i], ";") | ||||||
|  | 			for _, data := range tags { | ||||||
|  | 				parts := strings.SplitN(data, "=", 2) | ||||||
|  | 				if len(parts) == 1 { | ||||||
|  | 					event.Tags[parts[0]] = "" | ||||||
|  | 				} else { | ||||||
|  | 					event.Tags[parts[0]] = unescapeTagValue(parts[1]) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			msg = msg[i+1 : len(msg)] | ||||||
|  | 		} else { | ||||||
|  | 			return nil, errors.New("Malformed msg from server") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if msg[0] == ':' { | 	if msg[0] == ':' { | ||||||
| 		if i := strings.Index(msg, " "); i > -1 { | 		if i := strings.Index(msg, " "); i > -1 { | ||||||
| 			event.Source = msg[1:i] | 			event.Source = msg[1:i] | ||||||
| @@ -171,10 +204,12 @@ func (irc *Connection) pingLoop() { | |||||||
| 			//Ping at the ping frequency | 			//Ping at the ping frequency | ||||||
| 			irc.SendRawf("PING %d", time.Now().UnixNano()) | 			irc.SendRawf("PING %d", time.Now().UnixNano()) | ||||||
| 			//Try to recapture nickname if it's not as configured. | 			//Try to recapture nickname if it's not as configured. | ||||||
|  | 			irc.Lock() | ||||||
| 			if irc.nick != irc.nickcurrent { | 			if irc.nick != irc.nickcurrent { | ||||||
| 				irc.nickcurrent = irc.nick | 				irc.nickcurrent = irc.nick | ||||||
| 				irc.SendRawf("NICK %s", irc.nick) | 				irc.SendRawf("NICK %s", irc.nick) | ||||||
| 			} | 			} | ||||||
|  | 			irc.Unlock() | ||||||
| 		case <-irc.end: | 		case <-irc.end: | ||||||
| 			ticker.Stop() | 			ticker.Stop() | ||||||
| 			ticker2.Stop() | 			ticker2.Stop() | ||||||
| @@ -183,13 +218,21 @@ func (irc *Connection) pingLoop() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (irc *Connection) isQuitting() bool { | ||||||
|  | 	irc.Lock() | ||||||
|  | 	defer irc.Unlock() | ||||||
|  | 	return irc.quit | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Main loop to control the connection. | // Main loop to control the connection. | ||||||
| func (irc *Connection) Loop() { | func (irc *Connection) Loop() { | ||||||
| 	errChan := irc.ErrorChan() | 	errChan := irc.ErrorChan() | ||||||
| 	for !irc.quit { | 	for !irc.isQuitting() { | ||||||
| 		err := <-errChan | 		err := <-errChan | ||||||
| 		irc.Log.Printf("Error, disconnected: %s\n", err) | 		close(irc.end) | ||||||
| 		for !irc.quit { | 		irc.Wait() | ||||||
|  | 		for !irc.isQuitting() { | ||||||
|  | 			irc.Log.Printf("Error, disconnected: %s\n", err) | ||||||
| 			if err = irc.Reconnect(); err != nil { | 			if err = irc.Reconnect(); err != nil { | ||||||
| 				irc.Log.Printf("Error while reconnecting: %s\n", err) | 				irc.Log.Printf("Error while reconnecting: %s\n", err) | ||||||
| 				time.Sleep(60 * time.Second) | 				time.Sleep(60 * time.Second) | ||||||
| @@ -211,8 +254,10 @@ func (irc *Connection) Quit() { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	irc.SendRaw(quit) | 	irc.SendRaw(quit) | ||||||
|  | 	irc.Lock() | ||||||
| 	irc.stopped = true | 	irc.stopped = true | ||||||
| 	irc.quit = true | 	irc.quit = true | ||||||
|  | 	irc.Unlock() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Use the connection to join a given channel. | // Use the connection to join a given channel. | ||||||
| @@ -341,37 +386,14 @@ func (irc *Connection) Connected() bool { | |||||||
| // A disconnect sends all buffered messages (if possible), | // A disconnect sends all buffered messages (if possible), | ||||||
| // stops all goroutines and then closes the socket. | // stops all goroutines and then closes the socket. | ||||||
| func (irc *Connection) Disconnect() { | func (irc *Connection) Disconnect() { | ||||||
| 	for event := range irc.events { |  | ||||||
| 		irc.ClearCallback(event) |  | ||||||
| 	} |  | ||||||
| 	if irc.end != nil { |  | ||||||
| 		close(irc.end) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	irc.end = nil |  | ||||||
| 
 |  | ||||||
| 	if irc.pwrite != nil { |  | ||||||
| 		close(irc.pwrite) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	irc.Wait() |  | ||||||
| 	if irc.socket != nil { | 	if irc.socket != nil { | ||||||
| 		irc.socket.Close() | 		irc.socket.Close() | ||||||
| 	} | 	} | ||||||
| 	irc.socket = nil |  | ||||||
| 	irc.ErrorChan() <- ErrDisconnected | 	irc.ErrorChan() <- ErrDisconnected | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Reconnect to a server using the current connection. | // Reconnect to a server using the current connection. | ||||||
| func (irc *Connection) Reconnect() error { | func (irc *Connection) Reconnect() error { | ||||||
| 	if irc.end != nil { |  | ||||||
| 		close(irc.end) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	irc.end = nil |  | ||||||
| 
 |  | ||||||
| 	irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed |  | ||||||
| 
 |  | ||||||
| 	irc.end = make(chan struct{}) | 	irc.end = make(chan struct{}) | ||||||
| 	return irc.Connect(irc.Server) | 	return irc.Connect(irc.Server) | ||||||
| } | } | ||||||
| @@ -427,7 +449,7 @@ func (irc *Connection) Connect(server string) error { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	irc.stopped = false | 	irc.stopped = false | ||||||
| 	//irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) | 	irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) | ||||||
| 
 | 
 | ||||||
| 	irc.pwrite = make(chan string, 10) | 	irc.pwrite = make(chan string, 10) | ||||||
| 	irc.Error = make(chan error, 2) | 	irc.Error = make(chan error, 2) | ||||||
| @@ -439,26 +461,84 @@ func (irc *Connection) Connect(server string) error { | |||||||
| 		irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password) | 		irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	resChan := make(chan *SASLResult) | 	err = irc.negotiateCaps() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) | ||||||
|  | 	irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Negotiate IRCv3 capabilities | ||||||
|  | func (irc *Connection) negotiateCaps() error { | ||||||
|  | 	saslResChan := make(chan *SASLResult) | ||||||
|  | 	if irc.UseSASL { | ||||||
|  | 		irc.RequestCaps = append(irc.RequestCaps, "sasl") | ||||||
|  | 		irc.setupSASLCallbacks(saslResChan) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(irc.RequestCaps) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	cap_chan := make(chan bool, len(irc.RequestCaps)) | ||||||
|  | 	irc.AddCallback("CAP", func(e *Event) { | ||||||
|  | 		if len(e.Arguments) != 3 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		command := e.Arguments[1] | ||||||
|  | 
 | ||||||
|  | 		if command == "LS" { | ||||||
|  | 			missing_caps := len(irc.RequestCaps) | ||||||
|  | 			for _, cap_name := range strings.Split(e.Arguments[2], " ") { | ||||||
|  | 				for _, req_cap := range irc.RequestCaps { | ||||||
|  | 					if cap_name == req_cap { | ||||||
|  | 						irc.pwrite <- fmt.Sprintf("CAP REQ :%s\r\n", cap_name) | ||||||
|  | 						missing_caps-- | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for i := 0; i < missing_caps; i++ { | ||||||
|  | 				cap_chan <- true | ||||||
|  | 			} | ||||||
|  | 		} else if command == "ACK" || command == "NAK" { | ||||||
|  | 			for _, cap_name := range strings.Split(strings.TrimSpace(e.Arguments[2]), " ") { | ||||||
|  | 				if cap_name == "" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if command == "ACK" { | ||||||
|  | 					irc.AcknowledgedCaps = append(irc.AcknowledgedCaps, cap_name) | ||||||
|  | 				} | ||||||
|  | 				cap_chan <- true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	irc.pwrite <- "CAP LS\r\n" | ||||||
|  | 
 | ||||||
| 	if irc.UseSASL { | 	if irc.UseSASL { | ||||||
| 		irc.setupSASLCallbacks(resChan) |  | ||||||
| 		irc.pwrite <- fmt.Sprintf("CAP LS\r\n") |  | ||||||
| 		// request SASL |  | ||||||
| 		irc.pwrite <- fmt.Sprintf("CAP REQ :sasl\r\n") |  | ||||||
| 		// if sasl request doesn't complete in 15 seconds, close chan and timeout |  | ||||||
| 		select { | 		select { | ||||||
| 		case res := <-resChan: | 		case res := <-saslResChan: | ||||||
| 			if res.Failed { | 			if res.Failed { | ||||||
| 				close(resChan) | 				close(saslResChan) | ||||||
| 				return res.Err | 				return res.Err | ||||||
| 			} | 			} | ||||||
| 		case <-time.After(time.Second * 15): | 		case <-time.After(time.Second * 15): | ||||||
| 			close(resChan) | 			close(saslResChan) | ||||||
| 			return errors.New("SASL setup timed out. This shouldn't happen.") | 			return errors.New("SASL setup timed out. This shouldn't happen.") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick) | 
 | ||||||
| 	irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user) | 	// Wait for all capabilities to be ACKed or NAKed before ending negotiation | ||||||
|  | 	for i := 0; i < len(irc.RequestCaps); i++ { | ||||||
|  | 		<-cap_chan | ||||||
|  | 	} | ||||||
|  | 	irc.pwrite <- fmt.Sprintf("CAP END\r\n") | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -136,9 +136,8 @@ func (irc *Connection) RunCallbacks(event *Event) { | |||||||
| func (irc *Connection) setupCallbacks() { | func (irc *Connection) setupCallbacks() { | ||||||
| 	irc.events = make(map[string]map[int]func(*Event)) | 	irc.events = make(map[string]map[int]func(*Event)) | ||||||
| 
 | 
 | ||||||
| 	//Handle error events. This has to be called in a new thred to allow | 	//Handle error events. | ||||||
| 	//readLoop to exit | 	irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() }) | ||||||
| 	irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() }) |  | ||||||
| 
 | 
 | ||||||
| 	//Handle ping events | 	//Handle ping events | ||||||
| 	irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) | 	irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) | ||||||
| @@ -201,7 +200,7 @@ func (irc *Connection) setupCallbacks() { | |||||||
| 		ns, _ := strconv.ParseInt(e.Message(), 10, 64) | 		ns, _ := strconv.ParseInt(e.Message(), 10, 64) | ||||||
| 		delta := time.Duration(time.Now().UnixNano() - ns) | 		delta := time.Duration(time.Now().UnixNano() - ns) | ||||||
| 		if irc.Debug { | 		if irc.Debug { | ||||||
| 			irc.Log.Printf("Lag: %vs\n", delta) | 			irc.Log.Printf("Lag: %.3f s\n", delta.Seconds()) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| @@ -216,6 +215,8 @@ func (irc *Connection) setupCallbacks() { | |||||||
| 	// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>" | 	// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>" | ||||||
| 	// Set irc.nickcurrent to the actually used nick in this connection. | 	// Set irc.nickcurrent to the actually used nick in this connection. | ||||||
| 	irc.AddCallback("001", func(e *Event) { | 	irc.AddCallback("001", func(e *Event) { | ||||||
|  | 		irc.Lock() | ||||||
| 		irc.nickcurrent = e.Arguments[0] | 		irc.nickcurrent = e.Arguments[0] | ||||||
|  | 		irc.Unlock() | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @@ -43,7 +43,6 @@ func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) { | |||||||
| 		result <- &SASLResult{true, errors.New(e.Arguments[1])} | 		result <- &SASLResult{true, errors.New(e.Arguments[1])} | ||||||
| 	}) | 	}) | ||||||
| 	irc.AddCallback("903", func(e *Event) { | 	irc.AddCallback("903", func(e *Event) { | ||||||
| 		irc.SendRaw("CAP END") |  | ||||||
| 		result <- &SASLResult{false, nil} | 		result <- &SASLResult{false, nil} | ||||||
| 	}) | 	}) | ||||||
| 	irc.AddCallback("904", func(e *Event) { | 	irc.AddCallback("904", func(e *Event) { | ||||||
| @@ -13,21 +13,24 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Connection struct { | type Connection struct { | ||||||
|  | 	sync.Mutex | ||||||
| 	sync.WaitGroup | 	sync.WaitGroup | ||||||
| 	Debug        bool | 	Debug            bool | ||||||
| 	Error        chan error | 	Error            chan error | ||||||
| 	Password     string | 	Password         string | ||||||
| 	UseTLS       bool | 	UseTLS           bool | ||||||
| 	UseSASL      bool | 	UseSASL          bool | ||||||
| 	SASLLogin    string | 	RequestCaps      []string | ||||||
| 	SASLPassword string | 	AcknowledgedCaps []string | ||||||
| 	SASLMech     string | 	SASLLogin        string | ||||||
| 	TLSConfig    *tls.Config | 	SASLPassword     string | ||||||
| 	Version      string | 	SASLMech         string | ||||||
| 	Timeout      time.Duration | 	TLSConfig        *tls.Config | ||||||
| 	PingFreq     time.Duration | 	Version          string | ||||||
| 	KeepAlive    time.Duration | 	Timeout          time.Duration | ||||||
| 	Server       string | 	PingFreq         time.Duration | ||||||
|  | 	KeepAlive        time.Duration | ||||||
|  | 	Server           string | ||||||
| 
 | 
 | ||||||
| 	socket net.Conn | 	socket net.Conn | ||||||
| 	pwrite chan string | 	pwrite chan string | ||||||
| @@ -46,7 +49,7 @@ type Connection struct { | |||||||
| 	Log                    *log.Logger | 	Log                    *log.Logger | ||||||
| 
 | 
 | ||||||
| 	stopped bool | 	stopped bool | ||||||
| 	quit    bool | 	quit    bool //User called Quit, do not reconnect. | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // A struct to represent an event. | // A struct to represent an event. | ||||||
| @@ -58,6 +61,7 @@ type Event struct { | |||||||
| 	Source     string //<host> | 	Source     string //<host> | ||||||
| 	User       string //<usr> | 	User       string //<usr> | ||||||
| 	Arguments  []string | 	Arguments  []string | ||||||
|  | 	Tags       map[string]string | ||||||
| 	Connection *Connection | 	Connection *Connection | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										202
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,202 +0,0 @@ | |||||||
|                                  Apache License |  | ||||||
|                            Version 2.0, January 2004 |  | ||||||
|                         http://www.apache.org/licenses/ |  | ||||||
|  |  | ||||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |  | ||||||
|  |  | ||||||
|    1. Definitions. |  | ||||||
|  |  | ||||||
|       "License" shall mean the terms and conditions for use, reproduction, |  | ||||||
|       and distribution as defined by Sections 1 through 9 of this document. |  | ||||||
|  |  | ||||||
|       "Licensor" shall mean the copyright owner or entity authorized by |  | ||||||
|       the copyright owner that is granting the License. |  | ||||||
|  |  | ||||||
|       "Legal Entity" shall mean the union of the acting entity and all |  | ||||||
|       other entities that control, are controlled by, or are under common |  | ||||||
|       control with that entity. For the purposes of this definition, |  | ||||||
|       "control" means (i) the power, direct or indirect, to cause the |  | ||||||
|       direction or management of such entity, whether by contract or |  | ||||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the |  | ||||||
|       outstanding shares, or (iii) beneficial ownership of such entity. |  | ||||||
|  |  | ||||||
|       "You" (or "Your") shall mean an individual or Legal Entity |  | ||||||
|       exercising permissions granted by this License. |  | ||||||
|  |  | ||||||
|       "Source" form shall mean the preferred form for making modifications, |  | ||||||
|       including but not limited to software source code, documentation |  | ||||||
|       source, and configuration files. |  | ||||||
|  |  | ||||||
|       "Object" form shall mean any form resulting from mechanical |  | ||||||
|       transformation or translation of a Source form, including but |  | ||||||
|       not limited to compiled object code, generated documentation, |  | ||||||
|       and conversions to other media types. |  | ||||||
|  |  | ||||||
|       "Work" shall mean the work of authorship, whether in Source or |  | ||||||
|       Object form, made available under the License, as indicated by a |  | ||||||
|       copyright notice that is included in or attached to the work |  | ||||||
|       (an example is provided in the Appendix below). |  | ||||||
|  |  | ||||||
|       "Derivative Works" shall mean any work, whether in Source or Object |  | ||||||
|       form, that is based on (or derived from) the Work and for which the |  | ||||||
|       editorial revisions, annotations, elaborations, or other modifications |  | ||||||
|       represent, as a whole, an original work of authorship. For the purposes |  | ||||||
|       of this License, Derivative Works shall not include works that remain |  | ||||||
|       separable from, or merely link (or bind by name) to the interfaces of, |  | ||||||
|       the Work and Derivative Works thereof. |  | ||||||
|  |  | ||||||
|       "Contribution" shall mean any work of authorship, including |  | ||||||
|       the original version of the Work and any modifications or additions |  | ||||||
|       to that Work or Derivative Works thereof, that is intentionally |  | ||||||
|       submitted to Licensor for inclusion in the Work by the copyright owner |  | ||||||
|       or by an individual or Legal Entity authorized to submit on behalf of |  | ||||||
|       the copyright owner. For the purposes of this definition, "submitted" |  | ||||||
|       means any form of electronic, verbal, or written communication sent |  | ||||||
|       to the Licensor or its representatives, including but not limited to |  | ||||||
|       communication on electronic mailing lists, source code control systems, |  | ||||||
|       and issue tracking systems that are managed by, or on behalf of, the |  | ||||||
|       Licensor for the purpose of discussing and improving the Work, but |  | ||||||
|       excluding communication that is conspicuously marked or otherwise |  | ||||||
|       designated in writing by the copyright owner as "Not a Contribution." |  | ||||||
|  |  | ||||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity |  | ||||||
|       on behalf of whom a Contribution has been received by Licensor and |  | ||||||
|       subsequently incorporated within the Work. |  | ||||||
|  |  | ||||||
|    2. Grant of Copyright License. Subject to the terms and conditions of |  | ||||||
|       this License, each Contributor hereby grants to You a perpetual, |  | ||||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable |  | ||||||
|       copyright license to reproduce, prepare Derivative Works of, |  | ||||||
|       publicly display, publicly perform, sublicense, and distribute the |  | ||||||
|       Work and such Derivative Works in Source or Object form. |  | ||||||
|  |  | ||||||
|    3. Grant of Patent License. Subject to the terms and conditions of |  | ||||||
|       this License, each Contributor hereby grants to You a perpetual, |  | ||||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable |  | ||||||
|       (except as stated in this section) patent license to make, have made, |  | ||||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, |  | ||||||
|       where such license applies only to those patent claims licensable |  | ||||||
|       by such Contributor that are necessarily infringed by their |  | ||||||
|       Contribution(s) alone or by combination of their Contribution(s) |  | ||||||
|       with the Work to which such Contribution(s) was submitted. If You |  | ||||||
|       institute patent litigation against any entity (including a |  | ||||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work |  | ||||||
|       or a Contribution incorporated within the Work constitutes direct |  | ||||||
|       or contributory patent infringement, then any patent licenses |  | ||||||
|       granted to You under this License for that Work shall terminate |  | ||||||
|       as of the date such litigation is filed. |  | ||||||
|  |  | ||||||
|    4. Redistribution. You may reproduce and distribute copies of the |  | ||||||
|       Work or Derivative Works thereof in any medium, with or without |  | ||||||
|       modifications, and in Source or Object form, provided that You |  | ||||||
|       meet the following conditions: |  | ||||||
|  |  | ||||||
|       (a) You must give any other recipients of the Work or |  | ||||||
|           Derivative Works a copy of this License; and |  | ||||||
|  |  | ||||||
|       (b) You must cause any modified files to carry prominent notices |  | ||||||
|           stating that You changed the files; and |  | ||||||
|  |  | ||||||
|       (c) You must retain, in the Source form of any Derivative Works |  | ||||||
|           that You distribute, all copyright, patent, trademark, and |  | ||||||
|           attribution notices from the Source form of the Work, |  | ||||||
|           excluding those notices that do not pertain to any part of |  | ||||||
|           the Derivative Works; and |  | ||||||
|  |  | ||||||
|       (d) If the Work includes a "NOTICE" text file as part of its |  | ||||||
|           distribution, then any Derivative Works that You distribute must |  | ||||||
|           include a readable copy of the attribution notices contained |  | ||||||
|           within such NOTICE file, excluding those notices that do not |  | ||||||
|           pertain to any part of the Derivative Works, in at least one |  | ||||||
|           of the following places: within a NOTICE text file distributed |  | ||||||
|           as part of the Derivative Works; within the Source form or |  | ||||||
|           documentation, if provided along with the Derivative Works; or, |  | ||||||
|           within a display generated by the Derivative Works, if and |  | ||||||
|           wherever such third-party notices normally appear. The contents |  | ||||||
|           of the NOTICE file are for informational purposes only and |  | ||||||
|           do not modify the License. You may add Your own attribution |  | ||||||
|           notices within Derivative Works that You distribute, alongside |  | ||||||
|           or as an addendum to the NOTICE text from the Work, provided |  | ||||||
|           that such additional attribution notices cannot be construed |  | ||||||
|           as modifying the License. |  | ||||||
|  |  | ||||||
|       You may add Your own copyright statement to Your modifications and |  | ||||||
|       may provide additional or different license terms and conditions |  | ||||||
|       for use, reproduction, or distribution of Your modifications, or |  | ||||||
|       for any such Derivative Works as a whole, provided Your use, |  | ||||||
|       reproduction, and distribution of the Work otherwise complies with |  | ||||||
|       the conditions stated in this License. |  | ||||||
|  |  | ||||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, |  | ||||||
|       any Contribution intentionally submitted for inclusion in the Work |  | ||||||
|       by You to the Licensor shall be under the terms and conditions of |  | ||||||
|       this License, without any additional terms or conditions. |  | ||||||
|       Notwithstanding the above, nothing herein shall supersede or modify |  | ||||||
|       the terms of any separate license agreement you may have executed |  | ||||||
|       with Licensor regarding such Contributions. |  | ||||||
|  |  | ||||||
|    6. Trademarks. This License does not grant permission to use the trade |  | ||||||
|       names, trademarks, service marks, or product names of the Licensor, |  | ||||||
|       except as required for reasonable and customary use in describing the |  | ||||||
|       origin of the Work and reproducing the content of the NOTICE file. |  | ||||||
|  |  | ||||||
|    7. Disclaimer of Warranty. Unless required by applicable law or |  | ||||||
|       agreed to in writing, Licensor provides the Work (and each |  | ||||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, |  | ||||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |  | ||||||
|       implied, including, without limitation, any warranties or conditions |  | ||||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |  | ||||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the |  | ||||||
|       appropriateness of using or redistributing the Work and assume any |  | ||||||
|       risks associated with Your exercise of permissions under this License. |  | ||||||
|  |  | ||||||
|    8. Limitation of Liability. In no event and under no legal theory, |  | ||||||
|       whether in tort (including negligence), contract, or otherwise, |  | ||||||
|       unless required by applicable law (such as deliberate and grossly |  | ||||||
|       negligent acts) or agreed to in writing, shall any Contributor be |  | ||||||
|       liable to You for damages, including any direct, indirect, special, |  | ||||||
|       incidental, or consequential damages of any character arising as a |  | ||||||
|       result of this License or out of the use or inability to use the |  | ||||||
|       Work (including but not limited to damages for loss of goodwill, |  | ||||||
|       work stoppage, computer failure or malfunction, or any and all |  | ||||||
|       other commercial damages or losses), even if such Contributor |  | ||||||
|       has been advised of the possibility of such damages. |  | ||||||
|  |  | ||||||
|    9. Accepting Warranty or Additional Liability. While redistributing |  | ||||||
|       the Work or Derivative Works thereof, You may choose to offer, |  | ||||||
|       and charge a fee for, acceptance of support, warranty, indemnity, |  | ||||||
|       or other liability obligations and/or rights consistent with this |  | ||||||
|       License. However, in accepting such obligations, You may act only |  | ||||||
|       on Your own behalf and on Your sole responsibility, not on behalf |  | ||||||
|       of any other Contributor, and only if You agree to indemnify, |  | ||||||
|       defend, and hold each Contributor harmless for any liability |  | ||||||
|       incurred by, or claims asserted against, such Contributor by reason |  | ||||||
|       of your accepting any such warranty or additional liability. |  | ||||||
|  |  | ||||||
|    END OF TERMS AND CONDITIONS |  | ||||||
|  |  | ||||||
|    APPENDIX: How to apply the Apache License to your work. |  | ||||||
|  |  | ||||||
|       To apply the Apache License to your work, attach the following |  | ||||||
|       boilerplate notice, with the fields enclosed by brackets "{}" |  | ||||||
|       replaced with your own identifying information. (Don't include |  | ||||||
|       the brackets!)  The text should be enclosed in the appropriate |  | ||||||
|       comment syntax for the file format. We also recommend that a |  | ||||||
|       file or class name and description of purpose be included on the |  | ||||||
|       same "printed page" as the copyright notice for easier |  | ||||||
|       identification within third-party archives. |  | ||||||
|  |  | ||||||
|    Copyright {yyyy} {name of copyright owner} |  | ||||||
|  |  | ||||||
|    Licensed under the Apache License, Version 2.0 (the "License"); |  | ||||||
|    you may not use this file except in compliance with the License. |  | ||||||
|    You may obtain a copy of the License at |  | ||||||
|  |  | ||||||
|        http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
|  |  | ||||||
|    Unless required by applicable law or agreed to in writing, software |  | ||||||
|    distributed under the License is distributed on an "AS IS" BASIS, |  | ||||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | ||||||
|    See the License for the specific language governing permissions and |  | ||||||
|    limitations under the License. |  | ||||||
|  |  | ||||||
							
								
								
									
										434
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										434
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,434 +0,0 @@ | |||||||
| package bridge |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/tls" |  | ||||||
| 	"github.com/42wim/matterbridge-plus/matterclient" |  | ||||||
| 	"github.com/42wim/matterbridge/matterhook" |  | ||||||
| 	log "github.com/Sirupsen/logrus" |  | ||||||
| 	"github.com/peterhellberg/giphy" |  | ||||||
| 	ircm "github.com/sorcix/irc" |  | ||||||
| 	"github.com/thoj/go-ircevent" |  | ||||||
| 	"regexp" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| //type Bridge struct { |  | ||||||
| type MMhook struct { |  | ||||||
| 	mh *matterhook.Client |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMapi struct { |  | ||||||
| 	mc            *matterclient.MMClient |  | ||||||
| 	mmMap         map[string]string |  | ||||||
| 	mmIgnoreNicks []string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMirc struct { |  | ||||||
| 	i              *irc.Connection |  | ||||||
| 	ircNick        string |  | ||||||
| 	ircMap         map[string]string |  | ||||||
| 	names          map[string][]string |  | ||||||
| 	ircIgnoreNicks []string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMMessage struct { |  | ||||||
| 	Text     string |  | ||||||
| 	Channel  string |  | ||||||
| 	Username string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Bridge struct { |  | ||||||
| 	MMhook |  | ||||||
| 	MMapi |  | ||||||
| 	MMirc |  | ||||||
| 	*Config |  | ||||||
| 	kind string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type FancyLog struct { |  | ||||||
| 	irc *log.Entry |  | ||||||
| 	mm  *log.Entry |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var flog FancyLog |  | ||||||
|  |  | ||||||
| const Legacy = "legacy" |  | ||||||
|  |  | ||||||
| func initFLog() { |  | ||||||
| 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) |  | ||||||
| 	flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewBridge(name string, config *Config, kind string) *Bridge { |  | ||||||
| 	initFLog() |  | ||||||
| 	b := &Bridge{} |  | ||||||
| 	b.Config = config |  | ||||||
| 	b.kind = kind |  | ||||||
| 	b.ircNick = b.Config.IRC.Nick |  | ||||||
| 	b.ircMap = make(map[string]string) |  | ||||||
| 	b.MMirc.names = make(map[string][]string) |  | ||||||
| 	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) |  | ||||||
| 	b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) |  | ||||||
| 	if kind == Legacy { |  | ||||||
| 		if len(b.Config.Token) > 0 { |  | ||||||
| 			for _, val := range b.Config.Token { |  | ||||||
| 				b.ircMap[val.IRCChannel] = val.MMChannel |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		b.mh = matterhook.New(b.Config.Mattermost.URL, |  | ||||||
| 			matterhook.Config{Port: b.Config.Mattermost.Port, Token: b.Config.Mattermost.Token, |  | ||||||
| 				InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify, |  | ||||||
| 				BindAddress:        b.Config.Mattermost.BindAddress}) |  | ||||||
| 	} else { |  | ||||||
| 		b.mmMap = make(map[string]string) |  | ||||||
| 		if len(b.Config.Channel) > 0 { |  | ||||||
| 			for _, val := range b.Config.Channel { |  | ||||||
| 				b.ircMap[val.IRC] = val.Mattermost |  | ||||||
| 				b.mmMap[val.Mattermost] = val.IRC |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, |  | ||||||
| 			b.Config.Mattermost.Team, b.Config.Mattermost.Server) |  | ||||||
| 		b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify |  | ||||||
| 		b.mc.NoTLS = b.Config.Mattermost.NoTLS |  | ||||||
| 		flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) |  | ||||||
| 		err := b.mc.Login() |  | ||||||
| 		if err != nil { |  | ||||||
| 			flog.mm.Fatal("Can not connect", err) |  | ||||||
| 		} |  | ||||||
| 		flog.mm.Info("Login ok") |  | ||||||
| 		b.mc.JoinChannel(b.Config.Mattermost.Channel) |  | ||||||
| 		if len(b.Config.Channel) > 0 { |  | ||||||
| 			for _, val := range b.Config.Channel { |  | ||||||
| 				b.mc.JoinChannel(val.Mattermost) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		go b.mc.WsReceiver() |  | ||||||
| 	} |  | ||||||
| 	flog.irc.Info("Trying IRC connection") |  | ||||||
| 	b.i = b.createIRC(name) |  | ||||||
| 	flog.irc.Info("Connection succeeded") |  | ||||||
| 	go b.handleMatter() |  | ||||||
| 	return b |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) createIRC(name string) *irc.Connection { |  | ||||||
| 	i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) |  | ||||||
| 	i.UseTLS = b.Config.IRC.UseTLS |  | ||||||
| 	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} |  | ||||||
| 	if b.Config.IRC.Password != "" { |  | ||||||
| 		i.Password = b.Config.IRC.Password |  | ||||||
| 	} |  | ||||||
| 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) |  | ||||||
| 	i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port)) |  | ||||||
| 	return i |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleNewConnection(event *irc.Event) { |  | ||||||
| 	flog.irc.Info("Registering callbacks") |  | ||||||
| 	i := b.i |  | ||||||
| 	b.ircNick = event.Arguments[0] |  | ||||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) |  | ||||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) |  | ||||||
| 	i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) |  | ||||||
| 	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) |  | ||||||
| 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) |  | ||||||
| 	i.AddCallback(ircm.NOTICE, b.handleNotice) |  | ||||||
| 	i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) |  | ||||||
| 	i.AddCallback("PING", func(e *irc.Event) { |  | ||||||
| 		i.SendRaw("PONG :" + e.Message()) |  | ||||||
| 		flog.irc.Debugf("PING/PONG") |  | ||||||
| 	}) |  | ||||||
| 	if b.Config.Mattermost.ShowJoinPart { |  | ||||||
| 		i.AddCallback("JOIN", b.handleJoinPart) |  | ||||||
| 		i.AddCallback("PART", b.handleJoinPart) |  | ||||||
| 	} |  | ||||||
| 	i.AddCallback("*", b.handleOther) |  | ||||||
| 	b.setupChannels() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) setupChannels() { |  | ||||||
| 	i := b.i |  | ||||||
| 	if b.Config.IRC.Channel != "" { |  | ||||||
| 		flog.irc.Infof("Joining %s as %s", b.Config.IRC.Channel, b.ircNick) |  | ||||||
| 		i.Join(b.Config.IRC.Channel) |  | ||||||
| 	} |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		for _, val := range b.Config.Token { |  | ||||||
| 			flog.irc.Infof("Joining %s as %s", val.IRCChannel, b.ircNick) |  | ||||||
| 			i.Join(val.IRCChannel) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		for _, val := range b.Config.Channel { |  | ||||||
| 			flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) |  | ||||||
| 			i.Join(val.IRC) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { |  | ||||||
| 	parts := strings.Fields(event.Message()) |  | ||||||
| 	exp, _ := regexp.Compile("[:,]+$") |  | ||||||
| 	channel := event.Arguments[0] |  | ||||||
| 	command := "" |  | ||||||
| 	if len(parts) == 2 { |  | ||||||
| 		command = parts[1] |  | ||||||
| 	} |  | ||||||
| 	if exp.ReplaceAllString(parts[0], "") == b.ircNick { |  | ||||||
| 		switch command { |  | ||||||
| 		case "users": |  | ||||||
| 			usernames := b.mc.UsernamesInChannel(b.getMMChannel(channel)) |  | ||||||
| 			sort.Strings(usernames) |  | ||||||
| 			b.i.Privmsg(channel, "Users on Mattermost: "+strings.Join(usernames, ", ")) |  | ||||||
| 		default: |  | ||||||
| 			b.i.Privmsg(channel, "Valid commands are: [users, help]") |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) ircNickFormat(nick string) string { |  | ||||||
| 	if nick == b.ircNick { |  | ||||||
| 		return nick |  | ||||||
| 	} |  | ||||||
| 	if b.Config.Mattermost.RemoteNickFormat == nil { |  | ||||||
| 		return "irc-" + nick |  | ||||||
| 	} |  | ||||||
| 	return strings.Replace(*b.Config.Mattermost.RemoteNickFormat, "{NICK}", nick, -1) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handlePrivMsg(event *irc.Event) { |  | ||||||
| 	if b.ignoreMessage(event.Nick, event.Message(), "irc") { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if b.handleIrcBotCommand(event) { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	msg := "" |  | ||||||
| 	if event.Code == "CTCP_ACTION" { |  | ||||||
| 		msg = event.Nick + " " |  | ||||||
| 	} |  | ||||||
| 	msg += event.Message() |  | ||||||
| 	b.Send(b.ircNickFormat(event.Nick), msg, b.getMMChannel(event.Arguments[0])) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleJoinPart(event *irc.Event) { |  | ||||||
| 	b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0])) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleNotice(event *irc.Event) { |  | ||||||
| 	if strings.Contains(event.Message(), "This nickname is registered") { |  | ||||||
| 		b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) nicksPerRow() int { |  | ||||||
| 	if b.Config.Mattermost.NicksPerRow < 1 { |  | ||||||
| 		return 4 |  | ||||||
| 	} |  | ||||||
| 	return b.Config.Mattermost.NicksPerRow |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) formatnicks(nicks []string, continued bool) string { |  | ||||||
| 	switch b.Config.Mattermost.NickFormatter { |  | ||||||
| 	case "table": |  | ||||||
| 		return tableformatter(nicks, b.nicksPerRow(), continued) |  | ||||||
| 	default: |  | ||||||
| 		return plainformatter(nicks, b.nicksPerRow()) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) storeNames(event *irc.Event) { |  | ||||||
| 	channel := event.Arguments[2] |  | ||||||
| 	b.MMirc.names[channel] = append( |  | ||||||
| 		b.MMirc.names[channel], |  | ||||||
| 		strings.Split(strings.TrimSpace(event.Message()), " ")...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) endNames(event *irc.Event) { |  | ||||||
| 	channel := event.Arguments[1] |  | ||||||
| 	sort.Strings(b.MMirc.names[channel]) |  | ||||||
| 	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow() |  | ||||||
| 	continued := false |  | ||||||
| 	for len(b.MMirc.names[channel]) > maxNamesPerPost { |  | ||||||
| 		b.Send( |  | ||||||
| 			b.ircNick, |  | ||||||
| 			b.formatnicks(b.MMirc.names[channel][0:maxNamesPerPost], continued), |  | ||||||
| 			b.getMMChannel(channel)) |  | ||||||
| 		b.MMirc.names[channel] = b.MMirc.names[channel][maxNamesPerPost:] |  | ||||||
| 		continued = true |  | ||||||
| 	} |  | ||||||
| 	b.Send(b.ircNick, b.formatnicks(b.MMirc.names[channel], continued), b.getMMChannel(channel)) |  | ||||||
| 	b.MMirc.names[channel] = nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) { |  | ||||||
| 	parts := strings.Split(event.Arguments[2], "!") |  | ||||||
| 	t, err := strconv.ParseInt(event.Arguments[3], 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) |  | ||||||
| 	} |  | ||||||
| 	user := parts[0] |  | ||||||
| 	if len(parts) > 1 { |  | ||||||
| 		user += " [" + parts[1] + "]" |  | ||||||
| 	} |  | ||||||
| 	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleOther(event *irc.Event) { |  | ||||||
| 	flog.irc.Debugf("%#v", event) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) Send(nick string, message string, channel string) error { |  | ||||||
| 	return b.SendType(nick, message, channel, "") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) SendType(nick string, message string, channel string, mtype string) error { |  | ||||||
| 	if b.Config.Mattermost.PrefixMessagesWithNick { |  | ||||||
| 		if IsMarkup(message) { |  | ||||||
| 			message = nick + "\n\n" + message |  | ||||||
| 		} else { |  | ||||||
| 			message = nick + " " + message |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} |  | ||||||
| 		matterMessage.Channel = channel |  | ||||||
| 		matterMessage.UserName = nick |  | ||||||
| 		matterMessage.Type = mtype |  | ||||||
| 		matterMessage.Text = message |  | ||||||
| 		err := b.mh.Send(matterMessage) |  | ||||||
| 		if err != nil { |  | ||||||
| 			flog.mm.Info(err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	flog.mm.Debug("->mattermost channel: ", channel, " ", message) |  | ||||||
| 	b.mc.PostMessage(channel, message) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatterHook(mchan chan *MMMessage) { |  | ||||||
| 	for { |  | ||||||
| 		message := b.mh.Receive() |  | ||||||
| 		m := &MMMessage{} |  | ||||||
| 		m.Username = message.UserName |  | ||||||
| 		m.Text = message.Text |  | ||||||
| 		m.Channel = message.Token |  | ||||||
| 		mchan <- m |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { |  | ||||||
| 	for message := range b.mc.MessageChan { |  | ||||||
| 		// do not post our own messages back to irc |  | ||||||
| 		if message.Raw.Action == "posted" && b.mc.User.Username != message.Username { |  | ||||||
| 			m := &MMMessage{} |  | ||||||
| 			m.Username = message.Username |  | ||||||
| 			m.Channel = message.Channel |  | ||||||
| 			m.Text = message.Text |  | ||||||
| 			flog.mm.Debugf("<-mattermost channel: %s %#v %#v", message.Channel, message.Post, message.Raw) |  | ||||||
| 			mchan <- m |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatter() { |  | ||||||
| 	flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) |  | ||||||
| 	mchan := make(chan *MMMessage) |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		go b.handleMatterHook(mchan) |  | ||||||
| 	} else { |  | ||||||
| 		go b.handleMatterClient(mchan) |  | ||||||
| 	} |  | ||||||
| 	flog.mm.Info("Start listening for Mattermost messages") |  | ||||||
| 	for message := range mchan { |  | ||||||
| 		var username string |  | ||||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		username = message.Username + ": " |  | ||||||
| 		if b.Config.IRC.RemoteNickFormat != "" { |  | ||||||
| 			username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) |  | ||||||
| 		} else if b.Config.IRC.UseSlackCircumfix { |  | ||||||
| 			username = "<" + message.Username + "> " |  | ||||||
| 		} |  | ||||||
| 		cmds := strings.Fields(message.Text) |  | ||||||
| 		// empty message |  | ||||||
| 		if len(cmds) == 0 { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		cmd := cmds[0] |  | ||||||
| 		switch cmd { |  | ||||||
| 		case "!users": |  | ||||||
| 			flog.mm.Info("Received !users from ", message.Username) |  | ||||||
| 			b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) |  | ||||||
| 			continue |  | ||||||
| 		case "!gif": |  | ||||||
| 			message.Text = b.giphyRandom(strings.Fields(strings.Replace(message.Text, "!gif ", "", 1))) |  | ||||||
| 			b.Send(b.ircNick, message.Text, b.getIRCChannel(message.Channel)) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		texts := strings.Split(message.Text, "\n") |  | ||||||
| 		for _, text := range texts { |  | ||||||
| 			flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel) |  | ||||||
| 			b.i.Privmsg(b.getIRCChannel(message.Channel), username+text) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) giphyRandom(query []string) string { |  | ||||||
| 	g := giphy.DefaultClient |  | ||||||
| 	if b.Config.General.GiphyAPIKey != "" { |  | ||||||
| 		g.APIKey = b.Config.General.GiphyAPIKey |  | ||||||
| 	} |  | ||||||
| 	res, err := g.Random(query) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "error" |  | ||||||
| 	} |  | ||||||
| 	return res.Data.FixedHeightDownsampledURL |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) getMMChannel(ircChannel string) string { |  | ||||||
| 	mmchannel, ok := b.ircMap[ircChannel] |  | ||||||
| 	if !ok { |  | ||||||
| 		mmchannel = b.Config.Mattermost.Channel |  | ||||||
| 	} |  | ||||||
| 	return mmchannel |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) getIRCChannel(channel string) string { |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		ircchannel := b.Config.IRC.Channel |  | ||||||
| 		_, ok := b.Config.Token[channel] |  | ||||||
| 		if ok { |  | ||||||
| 			ircchannel = b.Config.Token[channel].IRCChannel |  | ||||||
| 		} |  | ||||||
| 		return ircchannel |  | ||||||
| 	} |  | ||||||
| 	ircchannel, ok := b.mmMap[channel] |  | ||||||
| 	if !ok { |  | ||||||
| 		ircchannel = b.Config.IRC.Channel |  | ||||||
| 	} |  | ||||||
| 	return ircchannel |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { |  | ||||||
| 	var ignoreNicks = b.mmIgnoreNicks |  | ||||||
| 	if protocol == "irc" { |  | ||||||
| 		ignoreNicks = b.ircIgnoreNicks |  | ||||||
| 	} |  | ||||||
| 	// should we discard messages ? |  | ||||||
| 	for _, entry := range ignoreNicks { |  | ||||||
| 		if nick == entry { |  | ||||||
| 			return true |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
							
								
								
									
										68
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										68
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,68 +0,0 @@ | |||||||
| package bridge |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"gopkg.in/gcfg.v1" |  | ||||||
| 	"io/ioutil" |  | ||||||
| 	"log" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Config struct { |  | ||||||
| 	IRC struct { |  | ||||||
| 		UseTLS            bool |  | ||||||
| 		SkipTLSVerify     bool |  | ||||||
| 		Server            string |  | ||||||
| 		Port              int |  | ||||||
| 		Nick              string |  | ||||||
| 		Password          string |  | ||||||
| 		Channel           string |  | ||||||
| 		UseSlackCircumfix bool |  | ||||||
| 		NickServNick      string |  | ||||||
| 		NickServPassword  string |  | ||||||
| 		RemoteNickFormat  string |  | ||||||
| 		IgnoreNicks       string |  | ||||||
| 	} |  | ||||||
| 	Mattermost struct { |  | ||||||
| 		URL                    string |  | ||||||
| 		Port                   int |  | ||||||
| 		ShowJoinPart           bool |  | ||||||
| 		Token                  string |  | ||||||
| 		IconURL                string |  | ||||||
| 		SkipTLSVerify          bool |  | ||||||
| 		BindAddress            string |  | ||||||
| 		Channel                string |  | ||||||
| 		PrefixMessagesWithNick bool |  | ||||||
| 		NicksPerRow            int |  | ||||||
| 		NickFormatter          string |  | ||||||
| 		Server                 string |  | ||||||
| 		Team                   string |  | ||||||
| 		Login                  string |  | ||||||
| 		Password               string |  | ||||||
| 		RemoteNickFormat       *string |  | ||||||
| 		IgnoreNicks            string |  | ||||||
| 		NoTLS                  bool |  | ||||||
| 	} |  | ||||||
| 	Token map[string]*struct { |  | ||||||
| 		IRCChannel string |  | ||||||
| 		MMChannel  string |  | ||||||
| 	} |  | ||||||
| 	Channel map[string]*struct { |  | ||||||
| 		IRC        string |  | ||||||
| 		Mattermost string |  | ||||||
| 	} |  | ||||||
| 	General struct { |  | ||||||
| 		GiphyAPIKey string |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewConfig(cfgfile string) *Config { |  | ||||||
| 	var cfg Config |  | ||||||
| 	content, err := ioutil.ReadFile(cfgfile) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal(err) |  | ||||||
| 	} |  | ||||||
| 	err = gcfg.ReadStringInto(&cfg, string(content)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatal("Failed to parse "+cfgfile+":", err) |  | ||||||
| 	} |  | ||||||
| 	return &cfg |  | ||||||
| } |  | ||||||
							
								
								
									
										59
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/helper.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/helper.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,59 +0,0 @@ | |||||||
| package bridge |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func tableformatter(nicks []string, nicksPerRow int, continued bool) string { |  | ||||||
| 	result := "|IRC users" |  | ||||||
| 	if continued { |  | ||||||
| 		result = "|(continued)" |  | ||||||
| 	} |  | ||||||
| 	for i := 0; i < 2; i++ { |  | ||||||
| 		for j := 1; j <= nicksPerRow && j <= len(nicks); j++ { |  | ||||||
| 			if i == 0 { |  | ||||||
| 				result += "|" |  | ||||||
| 			} else { |  | ||||||
| 				result += ":-|" |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		result += "\r\n|" |  | ||||||
| 	} |  | ||||||
| 	result += nicks[0] + "|" |  | ||||||
| 	for i := 1; i < len(nicks); i++ { |  | ||||||
| 		if i%nicksPerRow == 0 { |  | ||||||
| 			result += "\r\n|" + nicks[i] + "|" |  | ||||||
| 		} else { |  | ||||||
| 			result += nicks[i] + "|" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return result |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func plainformatter(nicks []string, nicksPerRow int) string { |  | ||||||
| 	return strings.Join(nicks, ", ") + " currently on IRC" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func IsMarkup(message string) bool { |  | ||||||
| 	switch message[0] { |  | ||||||
| 	case '|': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '#': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '_': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '*': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '~': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '-': |  | ||||||
| 		fallthrough |  | ||||||
| 	case ':': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '>': |  | ||||||
| 		fallthrough |  | ||||||
| 	case '=': |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
							
								
								
									
										441
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										441
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,441 +0,0 @@ | |||||||
| package matterclient |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/tls" |  | ||||||
| 	"errors" |  | ||||||
| 	log "github.com/Sirupsen/logrus" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/cookiejar" |  | ||||||
| 	"net/url" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/gorilla/websocket" |  | ||||||
| 	"github.com/jpillora/backoff" |  | ||||||
| 	"github.com/mattermost/platform/model" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Credentials struct { |  | ||||||
| 	Login         string |  | ||||||
| 	Team          string |  | ||||||
| 	Pass          string |  | ||||||
| 	Server        string |  | ||||||
| 	NoTLS         bool |  | ||||||
| 	SkipTLSVerify bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Message struct { |  | ||||||
| 	Raw      *model.Message |  | ||||||
| 	Post     *model.Post |  | ||||||
| 	Team     string |  | ||||||
| 	Channel  string |  | ||||||
| 	Username string |  | ||||||
| 	Text     string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMClient struct { |  | ||||||
| 	*Credentials |  | ||||||
| 	Client       *model.Client |  | ||||||
| 	WsClient     *websocket.Conn |  | ||||||
| 	WsQuit       bool |  | ||||||
| 	WsAway       bool |  | ||||||
| 	Channels     *model.ChannelList |  | ||||||
| 	MoreChannels *model.ChannelList |  | ||||||
| 	User         *model.User |  | ||||||
| 	Users        map[string]*model.User |  | ||||||
| 	MessageChan  chan *Message |  | ||||||
| 	Team         *model.Team |  | ||||||
| 	log          *log.Entry |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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)} |  | ||||||
| 	mmclient.log = log.WithFields(log.Fields{"module": "matterclient"}) |  | ||||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) |  | ||||||
| 	return mmclient |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) SetLogLevel(level string) { |  | ||||||
| 	l, err := log.ParseLevel(level) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.SetLevel(log.InfoLevel) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	log.SetLevel(l) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) Login() error { |  | ||||||
| 	if m.WsQuit { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	b := &backoff.Backoff{ |  | ||||||
| 		Min:    time.Second, |  | ||||||
| 		Max:    5 * time.Minute, |  | ||||||
| 		Jitter: true, |  | ||||||
| 	} |  | ||||||
| 	uriScheme := "https://" |  | ||||||
| 	wsScheme := "wss://" |  | ||||||
| 	if m.NoTLS { |  | ||||||
| 		uriScheme = "http://" |  | ||||||
| 		wsScheme = "ws://" |  | ||||||
| 	} |  | ||||||
| 	// login to mattermost |  | ||||||
| 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) |  | ||||||
| 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} |  | ||||||
| 	var myinfo *model.Result |  | ||||||
| 	var appErr *model.AppError |  | ||||||
| 	var logmsg = "trying login" |  | ||||||
| 	for { |  | ||||||
| 		m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) |  | ||||||
| 		if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { |  | ||||||
| 			m.log.Debugf(logmsg+" with ", model.SESSION_COOKIE_TOKEN) |  | ||||||
| 			token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") |  | ||||||
| 			m.Client.HttpClient.Jar = m.createCookieJar(token[1]) |  | ||||||
| 			m.Client.MockSession(token[1]) |  | ||||||
| 			myinfo, appErr = m.Client.GetMe("") |  | ||||||
| 			if myinfo.Data.(*model.User) == nil { |  | ||||||
| 				m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) |  | ||||||
| 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) |  | ||||||
| 		} |  | ||||||
| 		if appErr != nil { |  | ||||||
| 			d := b.Duration() |  | ||||||
| 			m.log.Debug(appErr.DetailedError) |  | ||||||
| 			if !strings.Contains(appErr.DetailedError, "connection refused") && |  | ||||||
| 				!strings.Contains(appErr.DetailedError, "invalid character") { |  | ||||||
| 				if appErr.Message == "" { |  | ||||||
| 					return errors.New(appErr.DetailedError) |  | ||||||
| 				} |  | ||||||
| 				return errors.New(appErr.Message) |  | ||||||
| 			} |  | ||||||
| 			m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) |  | ||||||
| 			time.Sleep(d) |  | ||||||
| 			logmsg = "retrying login" |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		break |  | ||||||
| 	} |  | ||||||
| 	// reset timer |  | ||||||
| 	b.Reset() |  | ||||||
|  |  | ||||||
| 	initLoad, _ := m.Client.GetInitialLoad() |  | ||||||
| 	initData := initLoad.Data.(*model.InitialLoad) |  | ||||||
| 	m.User = initData.User |  | ||||||
| 	for _, v := range initData.Teams { |  | ||||||
| 		m.log.Debugf("trying %s (id: %s)", v.Name, v.Id) |  | ||||||
| 		if v.Name == m.Credentials.Team { |  | ||||||
| 			m.Client.SetTeamId(v.Id) |  | ||||||
| 			m.Team = v |  | ||||||
| 			m.log.Debugf("GetallTeamListings: found id %s for team %s", v.Id, v.Name) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if m.Team == nil { |  | ||||||
| 		return errors.New("team not found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// setup websocket connection |  | ||||||
| 	wsurl := wsScheme + m.Credentials.Server + "/api/v3/users/websocket" |  | ||||||
| 	header := http.Header{} |  | ||||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) |  | ||||||
|  |  | ||||||
| 	m.log.Debug("WsClient: making connection") |  | ||||||
| 	var err error |  | ||||||
| 	for { |  | ||||||
| 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} |  | ||||||
| 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) |  | ||||||
| 		if err != nil { |  | ||||||
| 			d := b.Duration() |  | ||||||
| 			m.log.Debugf("WSS: %s, reconnecting in %s", err, d) |  | ||||||
| 			time.Sleep(d) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		break |  | ||||||
| 	} |  | ||||||
| 	b.Reset() |  | ||||||
|  |  | ||||||
| 	// populating users |  | ||||||
| 	m.UpdateUsers() |  | ||||||
|  |  | ||||||
| 	// populating channels |  | ||||||
| 	m.UpdateChannels() |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) WsReceiver() { |  | ||||||
| 	var rmsg model.Message |  | ||||||
| 	for { |  | ||||||
| 		if m.WsQuit { |  | ||||||
| 			m.log.Debug("exiting WsReceiver") |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		if err := m.WsClient.ReadJSON(&rmsg); err != nil { |  | ||||||
| 			m.log.Error("error:", err) |  | ||||||
| 			// reconnect |  | ||||||
| 			m.Login() |  | ||||||
| 		} |  | ||||||
| 		if rmsg.Action == "ping" { |  | ||||||
| 			m.handleWsPing() |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} |  | ||||||
| 		m.parseMessage(msg) |  | ||||||
| 		m.MessageChan <- msg |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) handleWsPing() { |  | ||||||
| 	m.log.Debug("Ws PING") |  | ||||||
| 	if !m.WsQuit && !m.WsAway { |  | ||||||
| 		m.log.Debug("Ws PONG") |  | ||||||
| 		m.WsClient.WriteMessage(websocket.PongMessage, []byte{}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) parseMessage(rmsg *Message) { |  | ||||||
| 	switch rmsg.Raw.Action { |  | ||||||
| 	case model.ACTION_POSTED: |  | ||||||
| 		m.parseActionPost(rmsg) |  | ||||||
| 		/* |  | ||||||
| 			case model.ACTION_USER_REMOVED: |  | ||||||
| 				m.handleWsActionUserRemoved(&rmsg) |  | ||||||
| 			case model.ACTION_USER_ADDED: |  | ||||||
| 				m.handleWsActionUserAdded(&rmsg) |  | ||||||
| 		*/ |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) parseActionPost(rmsg *Message) { |  | ||||||
| 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Props["post"])) |  | ||||||
| 	//	log.Println("receiving userid", data.UserId) |  | ||||||
| 	// we don't have the user, refresh the userlist |  | ||||||
| 	if m.Users[data.UserId] == nil { |  | ||||||
| 		m.UpdateUsers() |  | ||||||
| 	} |  | ||||||
| 	rmsg.Username = m.Users[data.UserId].Username |  | ||||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) |  | ||||||
| 	// direct message |  | ||||||
| 	if strings.Contains(rmsg.Channel, "__") { |  | ||||||
| 		//log.Println("direct message") |  | ||||||
| 		rcvusers := strings.Split(rmsg.Channel, "__") |  | ||||||
| 		if rcvusers[0] != m.User.Id { |  | ||||||
| 			rmsg.Channel = m.Users[rcvusers[0]].Username |  | ||||||
| 		} else { |  | ||||||
| 			rmsg.Channel = m.Users[rcvusers[1]].Username |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	rmsg.Text = data.Message |  | ||||||
| 	rmsg.Post = data |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateUsers() error { |  | ||||||
| 	mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) |  | ||||||
| 	m.Users = mmusers.Data.(map[string]*model.User) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateChannels() error { |  | ||||||
| 	mmchannels, _ := m.Client.GetChannels("") |  | ||||||
| 	m.Channels = mmchannels.Data.(*model.ChannelList) |  | ||||||
| 	mmchannels, _ = m.Client.GetMoreChannels("") |  | ||||||
| 	m.MoreChannels = mmchannels.Data.(*model.ChannelList) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetChannelName(id string) string { |  | ||||||
| 	for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { |  | ||||||
| 		if channel.Id == id { |  | ||||||
| 			return channel.Name |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// not found? could be a new direct message from mattermost. Try to update and check again |  | ||||||
| 	m.UpdateChannels() |  | ||||||
| 	for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { |  | ||||||
| 		if channel.Id == id { |  | ||||||
| 			return channel.Name |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetChannelId(name string) string { |  | ||||||
| 	for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { |  | ||||||
| 		if channel.Name == name { |  | ||||||
| 			return channel.Id |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetChannelHeader(id string) string { |  | ||||||
| 	for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { |  | ||||||
| 		if channel.Id == id { |  | ||||||
| 			return channel.Header |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) PostMessage(channel string, text string) { |  | ||||||
| 	post := &model.Post{ChannelId: m.GetChannelId(channel), Message: text} |  | ||||||
| 	m.Client.CreatePost(post) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) JoinChannel(channel string) error { |  | ||||||
| 	cleanChan := strings.Replace(channel, "#", "", 1) |  | ||||||
| 	if m.GetChannelId(cleanChan) == "" { |  | ||||||
| 		return errors.New("failed to join") |  | ||||||
| 	} |  | ||||||
| 	for _, c := range m.Channels.Channels { |  | ||||||
| 		if c.Name == cleanChan { |  | ||||||
| 			m.log.Debug("Not joining ", cleanChan, " already joined.") |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	m.log.Debug("Joining ", cleanChan) |  | ||||||
| 	_, err := m.Client.JoinChannel(m.GetChannelId(cleanChan)) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return errors.New("failed to join") |  | ||||||
| 	} |  | ||||||
| 	//	m.SyncChannel(m.getMMChannelId(strings.Replace(channel, "#", "", 1)), strings.Replace(channel, "#", "", 1)) |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { |  | ||||||
| 	res, err := m.Client.GetPostsSince(channelId, time) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return res.Data.(*model.PostList) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) SearchPosts(query string) *model.PostList { |  | ||||||
| 	res, err := m.Client.SearchPosts(query, false) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return res.Data.(*model.PostList) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { |  | ||||||
| 	res, err := m.Client.GetPosts(channelId, 0, limit, "") |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	return res.Data.(*model.PostList) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetPublicLink(filename string) string { |  | ||||||
| 	res, err := m.Client.GetPublicLink(filename) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
| 	return res.Data.(string) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { |  | ||||||
| 	var output []string |  | ||||||
| 	for _, f := range filenames { |  | ||||||
| 		res, err := m.Client.GetPublicLink(f) |  | ||||||
| 		if err != nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		output = append(output, res.Data.(string)) |  | ||||||
| 	} |  | ||||||
| 	return output |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateChannelHeader(channelId string, header string) { |  | ||||||
| 	data := make(map[string]string) |  | ||||||
| 	data["channel_id"] = channelId |  | ||||||
| 	data["channel_header"] = header |  | ||||||
| 	m.log.Debugf("updating channelheader %#v, %#v", channelId, header) |  | ||||||
| 	_, err := m.Client.UpdateChannelHeader(data) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateLastViewed(channelId string) { |  | ||||||
| 	m.log.Debugf("posting lastview %#v", channelId) |  | ||||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) |  | ||||||
| 	if err != nil { |  | ||||||
| 		m.log.Error(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) UsernamesInChannel(channelName string) []string { |  | ||||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "") |  | ||||||
| 	if err != nil { |  | ||||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) |  | ||||||
| 		return []string{} |  | ||||||
| 	} |  | ||||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) |  | ||||||
| 	result := []string{} |  | ||||||
| 	for _, member := range extra.Members { |  | ||||||
| 		result = append(result, member.Username) |  | ||||||
| 	} |  | ||||||
| 	return result |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { |  | ||||||
| 	var cookies []*http.Cookie |  | ||||||
| 	jar, _ := cookiejar.New(nil) |  | ||||||
| 	firstCookie := &http.Cookie{ |  | ||||||
| 		Name:   "MMAUTHTOKEN", |  | ||||||
| 		Value:  token, |  | ||||||
| 		Path:   "/", |  | ||||||
| 		Domain: m.Credentials.Server, |  | ||||||
| 	} |  | ||||||
| 	cookies = append(cookies, firstCookie) |  | ||||||
| 	cookieURL, _ := url.Parse("https://" + m.Credentials.Server) |  | ||||||
| 	jar.SetCookies(cookieURL, cookies) |  | ||||||
| 	return jar |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) GetOtherUserDM(channel string) *model.User { |  | ||||||
| 	m.UpdateUsers() |  | ||||||
| 	var rcvuser *model.User |  | ||||||
| 	if strings.Contains(channel, "__") { |  | ||||||
| 		rcvusers := strings.Split(channel, "__") |  | ||||||
| 		if rcvusers[0] != m.User.Id { |  | ||||||
| 			rcvuser = m.Users[rcvusers[0]] |  | ||||||
| 		} else { |  | ||||||
| 			rcvuser = m.Users[rcvusers[1]] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return rcvuser |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) SendDirectMessage(toUserId string, msg string) { |  | ||||||
| 	m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) |  | ||||||
| 	var channel string |  | ||||||
| 	// We don't have a DM with this user yet. |  | ||||||
| 	if m.GetChannelId(toUserId+"__"+m.User.Id) == "" && m.GetChannelId(m.User.Id+"__"+toUserId) == "" { |  | ||||||
| 		// create DM channel |  | ||||||
| 		_, err := m.Client.CreateDirectChannel(toUserId) |  | ||||||
| 		if err != nil { |  | ||||||
| 			m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) |  | ||||||
| 		} |  | ||||||
| 		// update our channels |  | ||||||
| 		mmchannels, _ := m.Client.GetChannels("") |  | ||||||
| 		m.Channels = mmchannels.Data.(*model.ChannelList) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// build the channel name |  | ||||||
| 	if toUserId > m.User.Id { |  | ||||||
| 		channel = m.User.Id + "__" + toUserId |  | ||||||
| 	} else { |  | ||||||
| 		channel = toUserId + "__" + m.User.Id |  | ||||||
| 	} |  | ||||||
| 	// build & send the message |  | ||||||
| 	msg = strings.Replace(msg, "\r", "", -1) |  | ||||||
| 	post := &model.Post{ChannelId: m.GetChannelId(channel), Message: msg} |  | ||||||
| 	m.Client.CreatePost(post) |  | ||||||
| } |  | ||||||
							
								
								
									
										2
									
								
								vendor/github.com/BurntSushi/toml/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/BurntSushi/toml/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with | |||||||
| the Primitive type, and querying the set of keys in a TOML document with the | the Primitive type, and querying the set of keys in a TOML document with the | ||||||
| MetaData type. | MetaData type. | ||||||
|  |  | ||||||
| The specification implemented: https://github.com/mojombo/toml | The specification implemented: https://github.com/toml-lang/toml | ||||||
|  |  | ||||||
| The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify | The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify | ||||||
| whether a file is a valid TOML document. It can also be used to print the | whether a file is a valid TOML document. It can also be used to print the | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								vendor/github.com/BurntSushi/toml/encode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/BurntSushi/toml/encode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { | |||||||
| func (enc *Encoder) eTable(key Key, rv reflect.Value) { | func (enc *Encoder) eTable(key Key, rv reflect.Value) { | ||||||
| 	panicIfInvalidKey(key) | 	panicIfInvalidKey(key) | ||||||
| 	if len(key) == 1 { | 	if len(key) == 1 { | ||||||
| 		// Output an extra new line between top-level tables. | 		// Output an extra newline between top-level tables. | ||||||
| 		// (The newline isn't written if nothing else has been written though.) | 		// (The newline isn't written if nothing else has been written though.) | ||||||
| 		enc.newline() | 		enc.newline() | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										259
									
								
								vendor/github.com/BurntSushi/toml/lex.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										259
									
								
								vendor/github.com/BurntSushi/toml/lex.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -30,24 +30,28 @@ const ( | |||||||
| 	itemArrayTableEnd | 	itemArrayTableEnd | ||||||
| 	itemKeyStart | 	itemKeyStart | ||||||
| 	itemCommentStart | 	itemCommentStart | ||||||
|  | 	itemInlineTableStart | ||||||
|  | 	itemInlineTableEnd | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	eof             = 0 | 	eof              = 0 | ||||||
| 	tableStart      = '[' | 	comma            = ',' | ||||||
| 	tableEnd        = ']' | 	tableStart       = '[' | ||||||
| 	arrayTableStart = '[' | 	tableEnd         = ']' | ||||||
| 	arrayTableEnd   = ']' | 	arrayTableStart  = '[' | ||||||
| 	tableSep        = '.' | 	arrayTableEnd    = ']' | ||||||
| 	keySep          = '=' | 	tableSep         = '.' | ||||||
| 	arrayStart      = '[' | 	keySep           = '=' | ||||||
| 	arrayEnd        = ']' | 	arrayStart       = '[' | ||||||
| 	arrayValTerm    = ',' | 	arrayEnd         = ']' | ||||||
| 	commentStart    = '#' | 	commentStart     = '#' | ||||||
| 	stringStart     = '"' | 	stringStart      = '"' | ||||||
| 	stringEnd       = '"' | 	stringEnd        = '"' | ||||||
| 	rawStringStart  = '\'' | 	rawStringStart   = '\'' | ||||||
| 	rawStringEnd    = '\'' | 	rawStringEnd     = '\'' | ||||||
|  | 	inlineTableStart = '{' | ||||||
|  | 	inlineTableEnd   = '}' | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type stateFn func(lx *lexer) stateFn | type stateFn func(lx *lexer) stateFn | ||||||
| @@ -56,11 +60,18 @@ type lexer struct { | |||||||
| 	input string | 	input string | ||||||
| 	start int | 	start int | ||||||
| 	pos   int | 	pos   int | ||||||
| 	width int |  | ||||||
| 	line  int | 	line  int | ||||||
| 	state stateFn | 	state stateFn | ||||||
| 	items chan item | 	items chan item | ||||||
|  |  | ||||||
|  | 	// Allow for backing up up to three runes. | ||||||
|  | 	// This is necessary because TOML contains 3-rune tokens (""" and '''). | ||||||
|  | 	prevWidths [3]int | ||||||
|  | 	nprev      int // how many of prevWidths are in use | ||||||
|  | 	// If we emit an eof, we can still back up, but it is not OK to call | ||||||
|  | 	// next again. | ||||||
|  | 	atEOF bool | ||||||
|  |  | ||||||
| 	// A stack of state functions used to maintain context. | 	// A stack of state functions used to maintain context. | ||||||
| 	// The idea is to reuse parts of the state machine in various places. | 	// The idea is to reuse parts of the state machine in various places. | ||||||
| 	// For example, values can appear at the top level or within arbitrarily | 	// For example, values can appear at the top level or within arbitrarily | ||||||
| @@ -88,7 +99,7 @@ func (lx *lexer) nextItem() item { | |||||||
|  |  | ||||||
| func lex(input string) *lexer { | func lex(input string) *lexer { | ||||||
| 	lx := &lexer{ | 	lx := &lexer{ | ||||||
| 		input: input + "\n", | 		input: input, | ||||||
| 		state: lexTop, | 		state: lexTop, | ||||||
| 		line:  1, | 		line:  1, | ||||||
| 		items: make(chan item, 10), | 		items: make(chan item, 10), | ||||||
| @@ -103,7 +114,7 @@ func (lx *lexer) push(state stateFn) { | |||||||
|  |  | ||||||
| func (lx *lexer) pop() stateFn { | func (lx *lexer) pop() stateFn { | ||||||
| 	if len(lx.stack) == 0 { | 	if len(lx.stack) == 0 { | ||||||
| 		return lx.errorf("BUG in lexer: no states to pop.") | 		return lx.errorf("BUG in lexer: no states to pop") | ||||||
| 	} | 	} | ||||||
| 	last := lx.stack[len(lx.stack)-1] | 	last := lx.stack[len(lx.stack)-1] | ||||||
| 	lx.stack = lx.stack[0 : len(lx.stack)-1] | 	lx.stack = lx.stack[0 : len(lx.stack)-1] | ||||||
| @@ -125,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (lx *lexer) next() (r rune) { | func (lx *lexer) next() (r rune) { | ||||||
|  | 	if lx.atEOF { | ||||||
|  | 		panic("next called after EOF") | ||||||
|  | 	} | ||||||
| 	if lx.pos >= len(lx.input) { | 	if lx.pos >= len(lx.input) { | ||||||
| 		lx.width = 0 | 		lx.atEOF = true | ||||||
| 		return eof | 		return eof | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if lx.input[lx.pos] == '\n' { | 	if lx.input[lx.pos] == '\n' { | ||||||
| 		lx.line++ | 		lx.line++ | ||||||
| 	} | 	} | ||||||
| 	r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) | 	lx.prevWidths[2] = lx.prevWidths[1] | ||||||
| 	lx.pos += lx.width | 	lx.prevWidths[1] = lx.prevWidths[0] | ||||||
|  | 	if lx.nprev < 3 { | ||||||
|  | 		lx.nprev++ | ||||||
|  | 	} | ||||||
|  | 	r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) | ||||||
|  | 	lx.prevWidths[0] = w | ||||||
|  | 	lx.pos += w | ||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -143,9 +163,20 @@ func (lx *lexer) ignore() { | |||||||
| 	lx.start = lx.pos | 	lx.start = lx.pos | ||||||
| } | } | ||||||
|  |  | ||||||
| // backup steps back one rune. Can be called only once per call of next. | // backup steps back one rune. Can be called only twice between calls to next. | ||||||
| func (lx *lexer) backup() { | func (lx *lexer) backup() { | ||||||
| 	lx.pos -= lx.width | 	if lx.atEOF { | ||||||
|  | 		lx.atEOF = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if lx.nprev < 1 { | ||||||
|  | 		panic("backed up too far") | ||||||
|  | 	} | ||||||
|  | 	w := lx.prevWidths[0] | ||||||
|  | 	lx.prevWidths[0] = lx.prevWidths[1] | ||||||
|  | 	lx.prevWidths[1] = lx.prevWidths[2] | ||||||
|  | 	lx.nprev-- | ||||||
|  | 	lx.pos -= w | ||||||
| 	if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { | 	if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { | ||||||
| 		lx.line-- | 		lx.line-- | ||||||
| 	} | 	} | ||||||
| @@ -182,7 +213,7 @@ func (lx *lexer) skip(pred func(rune) bool) { | |||||||
|  |  | ||||||
| // errorf stops all lexing by emitting an error and returning `nil`. | // errorf stops all lexing by emitting an error and returning `nil`. | ||||||
| // Note that any value that is a character is escaped if it's a special | // Note that any value that is a character is escaped if it's a special | ||||||
| // character (new lines, tabs, etc.). | // character (newlines, tabs, etc.). | ||||||
| func (lx *lexer) errorf(format string, values ...interface{}) stateFn { | func (lx *lexer) errorf(format string, values ...interface{}) stateFn { | ||||||
| 	lx.items <- item{ | 	lx.items <- item{ | ||||||
| 		itemError, | 		itemError, | ||||||
| @@ -198,7 +229,6 @@ func lexTop(lx *lexer) stateFn { | |||||||
| 	if isWhitespace(r) || isNL(r) { | 	if isWhitespace(r) || isNL(r) { | ||||||
| 		return lexSkip(lx, lexTop) | 		return lexSkip(lx, lexTop) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch r { | 	switch r { | ||||||
| 	case commentStart: | 	case commentStart: | ||||||
| 		lx.push(lexTop) | 		lx.push(lexTop) | ||||||
| @@ -207,7 +237,7 @@ func lexTop(lx *lexer) stateFn { | |||||||
| 		return lexTableStart | 		return lexTableStart | ||||||
| 	case eof: | 	case eof: | ||||||
| 		if lx.pos > lx.start { | 		if lx.pos > lx.start { | ||||||
| 			return lx.errorf("Unexpected EOF.") | 			return lx.errorf("unexpected EOF") | ||||||
| 		} | 		} | ||||||
| 		lx.emit(itemEOF) | 		lx.emit(itemEOF) | ||||||
| 		return nil | 		return nil | ||||||
| @@ -222,12 +252,12 @@ func lexTop(lx *lexer) stateFn { | |||||||
|  |  | ||||||
| // lexTopEnd is entered whenever a top-level item has been consumed. (A value | // lexTopEnd is entered whenever a top-level item has been consumed. (A value | ||||||
| // or a table.) It must see only whitespace, and will turn back to lexTop | // or a table.) It must see only whitespace, and will turn back to lexTop | ||||||
| // upon a new line. If it sees EOF, it will quit the lexer successfully. | // upon a newline. If it sees EOF, it will quit the lexer successfully. | ||||||
| func lexTopEnd(lx *lexer) stateFn { | func lexTopEnd(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| 	case r == commentStart: | 	case r == commentStart: | ||||||
| 		// a comment will read to a new line for us. | 		// a comment will read to a newline for us. | ||||||
| 		lx.push(lexTop) | 		lx.push(lexTop) | ||||||
| 		return lexCommentStart | 		return lexCommentStart | ||||||
| 	case isWhitespace(r): | 	case isWhitespace(r): | ||||||
| @@ -236,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn { | |||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
| 		return lexTop | 		return lexTop | ||||||
| 	case r == eof: | 	case r == eof: | ||||||
| 		lx.ignore() | 		lx.emit(itemEOF) | ||||||
| 		return lexTop | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Expected a top-level item to end with a new line, "+ | 	return lx.errorf("expected a top-level item to end with a newline, "+ | ||||||
| 		"comment or EOF, but got %q instead.", r) | 		"comment, or EOF, but got %q instead", r) | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexTable lexes the beginning of a table. Namely, it makes sure that | // lexTable lexes the beginning of a table. Namely, it makes sure that | ||||||
| @@ -267,8 +297,8 @@ func lexTableEnd(lx *lexer) stateFn { | |||||||
|  |  | ||||||
| func lexArrayTableEnd(lx *lexer) stateFn { | func lexArrayTableEnd(lx *lexer) stateFn { | ||||||
| 	if r := lx.next(); r != arrayTableEnd { | 	if r := lx.next(); r != arrayTableEnd { | ||||||
| 		return lx.errorf("Expected end of table array name delimiter %q, "+ | 		return lx.errorf("expected end of table array name delimiter %q, "+ | ||||||
| 			"but got %q instead.", arrayTableEnd, r) | 			"but got %q instead", arrayTableEnd, r) | ||||||
| 	} | 	} | ||||||
| 	lx.emit(itemArrayTableEnd) | 	lx.emit(itemArrayTableEnd) | ||||||
| 	return lexTopEnd | 	return lexTopEnd | ||||||
| @@ -278,11 +308,11 @@ func lexTableNameStart(lx *lexer) stateFn { | |||||||
| 	lx.skip(isWhitespace) | 	lx.skip(isWhitespace) | ||||||
| 	switch r := lx.peek(); { | 	switch r := lx.peek(); { | ||||||
| 	case r == tableEnd || r == eof: | 	case r == tableEnd || r == eof: | ||||||
| 		return lx.errorf("Unexpected end of table name. (Table names cannot " + | 		return lx.errorf("unexpected end of table name " + | ||||||
| 			"be empty.)") | 			"(table names cannot be empty)") | ||||||
| 	case r == tableSep: | 	case r == tableSep: | ||||||
| 		return lx.errorf("Unexpected table separator. (Table names cannot " + | 		return lx.errorf("unexpected table separator " + | ||||||
| 			"be empty.)") | 			"(table names cannot be empty)") | ||||||
| 	case r == stringStart || r == rawStringStart: | 	case r == stringStart || r == rawStringStart: | ||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
| 		lx.push(lexTableNameEnd) | 		lx.push(lexTableNameEnd) | ||||||
| @@ -317,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn { | |||||||
| 	case r == tableEnd: | 	case r == tableEnd: | ||||||
| 		return lx.pop() | 		return lx.pop() | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("Expected '.' or ']' to end table name, but got %q "+ | 		return lx.errorf("expected '.' or ']' to end table name, "+ | ||||||
| 			"instead.", r) | 			"but got %q instead", r) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -328,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn { | |||||||
| 	r := lx.peek() | 	r := lx.peek() | ||||||
| 	switch { | 	switch { | ||||||
| 	case r == keySep: | 	case r == keySep: | ||||||
| 		return lx.errorf("Unexpected key separator %q.", keySep) | 		return lx.errorf("unexpected key separator %q", keySep) | ||||||
| 	case isWhitespace(r) || isNL(r): | 	case isWhitespace(r) || isNL(r): | ||||||
| 		lx.next() | 		lx.next() | ||||||
| 		return lexSkip(lx, lexKeyStart) | 		return lexSkip(lx, lexKeyStart) | ||||||
| @@ -359,7 +389,7 @@ func lexBareKey(lx *lexer) stateFn { | |||||||
| 		lx.emit(itemText) | 		lx.emit(itemText) | ||||||
| 		return lexKeyEnd | 		return lexKeyEnd | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("Bare keys cannot contain %q.", r) | 		return lx.errorf("bare keys cannot contain %q", r) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -372,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn { | |||||||
| 	case isWhitespace(r): | 	case isWhitespace(r): | ||||||
| 		return lexSkip(lx, lexKeyEnd) | 		return lexSkip(lx, lexKeyEnd) | ||||||
| 	default: | 	default: | ||||||
| 		return lx.errorf("Expected key separator %q, but got %q instead.", | 		return lx.errorf("expected key separator %q, but got %q instead", | ||||||
| 			keySep, r) | 			keySep, r) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -381,9 +411,8 @@ func lexKeyEnd(lx *lexer) stateFn { | |||||||
| // lexValue will ignore whitespace. | // lexValue will ignore whitespace. | ||||||
| // After a value is lexed, the last state on the next is popped and returned. | // After a value is lexed, the last state on the next is popped and returned. | ||||||
| func lexValue(lx *lexer) stateFn { | func lexValue(lx *lexer) stateFn { | ||||||
| 	// We allow whitespace to precede a value, but NOT new lines. | 	// We allow whitespace to precede a value, but NOT newlines. | ||||||
| 	// In array syntax, the array states are responsible for ignoring new | 	// In array syntax, the array states are responsible for ignoring newlines. | ||||||
| 	// lines. |  | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| 	case isWhitespace(r): | 	case isWhitespace(r): | ||||||
| @@ -397,6 +426,10 @@ func lexValue(lx *lexer) stateFn { | |||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
| 		lx.emit(itemArray) | 		lx.emit(itemArray) | ||||||
| 		return lexArrayValue | 		return lexArrayValue | ||||||
|  | 	case inlineTableStart: | ||||||
|  | 		lx.ignore() | ||||||
|  | 		lx.emit(itemInlineTableStart) | ||||||
|  | 		return lexInlineTableValue | ||||||
| 	case stringStart: | 	case stringStart: | ||||||
| 		if lx.accept(stringStart) { | 		if lx.accept(stringStart) { | ||||||
| 			if lx.accept(stringStart) { | 			if lx.accept(stringStart) { | ||||||
| @@ -420,7 +453,7 @@ func lexValue(lx *lexer) stateFn { | |||||||
| 	case '+', '-': | 	case '+', '-': | ||||||
| 		return lexNumberStart | 		return lexNumberStart | ||||||
| 	case '.': // special error case, be kind to users | 	case '.': // special error case, be kind to users | ||||||
| 		return lx.errorf("Floats must start with a digit, not '.'.") | 		return lx.errorf("floats must start with a digit, not '.'") | ||||||
| 	} | 	} | ||||||
| 	if unicode.IsLetter(r) { | 	if unicode.IsLetter(r) { | ||||||
| 		// Be permissive here; lexBool will give a nice error if the | 		// Be permissive here; lexBool will give a nice error if the | ||||||
| @@ -430,11 +463,11 @@ func lexValue(lx *lexer) stateFn { | |||||||
| 		lx.backup() | 		lx.backup() | ||||||
| 		return lexBool | 		return lexBool | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Expected value but found %q instead.", r) | 	return lx.errorf("expected value but found %q instead", r) | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexArrayValue consumes one value in an array. It assumes that '[' or ',' | // lexArrayValue consumes one value in an array. It assumes that '[' or ',' | ||||||
| // have already been consumed. All whitespace and new lines are ignored. | // have already been consumed. All whitespace and newlines are ignored. | ||||||
| func lexArrayValue(lx *lexer) stateFn { | func lexArrayValue(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| @@ -443,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn { | |||||||
| 	case r == commentStart: | 	case r == commentStart: | ||||||
| 		lx.push(lexArrayValue) | 		lx.push(lexArrayValue) | ||||||
| 		return lexCommentStart | 		return lexCommentStart | ||||||
| 	case r == arrayValTerm: | 	case r == comma: | ||||||
| 		return lx.errorf("Unexpected array value terminator %q.", | 		return lx.errorf("unexpected comma") | ||||||
| 			arrayValTerm) |  | ||||||
| 	case r == arrayEnd: | 	case r == arrayEnd: | ||||||
|  | 		// NOTE(caleb): The spec isn't clear about whether you can have | ||||||
|  | 		// a trailing comma or not, so we'll allow it. | ||||||
| 		return lexArrayEnd | 		return lexArrayEnd | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -455,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn { | |||||||
| 	return lexValue | 	return lexValue | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexArrayValueEnd consumes the cruft between values of an array. Namely, | // lexArrayValueEnd consumes everything between the end of an array value and | ||||||
| // it ignores whitespace and expects either a ',' or a ']'. | // the next value (or the end of the array): it ignores whitespace and newlines | ||||||
|  | // and expects either a ',' or a ']'. | ||||||
| func lexArrayValueEnd(lx *lexer) stateFn { | func lexArrayValueEnd(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
| @@ -465,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn { | |||||||
| 	case r == commentStart: | 	case r == commentStart: | ||||||
| 		lx.push(lexArrayValueEnd) | 		lx.push(lexArrayValueEnd) | ||||||
| 		return lexCommentStart | 		return lexCommentStart | ||||||
| 	case r == arrayValTerm: | 	case r == comma: | ||||||
| 		lx.ignore() | 		lx.ignore() | ||||||
| 		return lexArrayValue // move on to the next value | 		return lexArrayValue // move on to the next value | ||||||
| 	case r == arrayEnd: | 	case r == arrayEnd: | ||||||
| 		return lexArrayEnd | 		return lexArrayEnd | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Expected an array value terminator %q or an array "+ | 	return lx.errorf( | ||||||
| 		"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) | 		"expected a comma or array terminator %q, but got %q instead", | ||||||
|  | 		arrayEnd, r, | ||||||
|  | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexArrayEnd finishes the lexing of an array. It assumes that a ']' has | // lexArrayEnd finishes the lexing of an array. | ||||||
| // just been consumed. | // It assumes that a ']' has just been consumed. | ||||||
| func lexArrayEnd(lx *lexer) stateFn { | func lexArrayEnd(lx *lexer) stateFn { | ||||||
| 	lx.ignore() | 	lx.ignore() | ||||||
| 	lx.emit(itemArrayEnd) | 	lx.emit(itemArrayEnd) | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // lexInlineTableValue consumes one key/value pair in an inline table. | ||||||
|  | // It assumes that '{' or ',' have already been consumed. Whitespace is ignored. | ||||||
|  | func lexInlineTableValue(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	switch { | ||||||
|  | 	case isWhitespace(r): | ||||||
|  | 		return lexSkip(lx, lexInlineTableValue) | ||||||
|  | 	case isNL(r): | ||||||
|  | 		return lx.errorf("newlines not allowed within inline tables") | ||||||
|  | 	case r == commentStart: | ||||||
|  | 		lx.push(lexInlineTableValue) | ||||||
|  | 		return lexCommentStart | ||||||
|  | 	case r == comma: | ||||||
|  | 		return lx.errorf("unexpected comma") | ||||||
|  | 	case r == inlineTableEnd: | ||||||
|  | 		return lexInlineTableEnd | ||||||
|  | 	} | ||||||
|  | 	lx.backup() | ||||||
|  | 	lx.push(lexInlineTableValueEnd) | ||||||
|  | 	return lexKeyStart | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // lexInlineTableValueEnd consumes everything between the end of an inline table | ||||||
|  | // key/value pair and the next pair (or the end of the table): | ||||||
|  | // it ignores whitespace and expects either a ',' or a '}'. | ||||||
|  | func lexInlineTableValueEnd(lx *lexer) stateFn { | ||||||
|  | 	r := lx.next() | ||||||
|  | 	switch { | ||||||
|  | 	case isWhitespace(r): | ||||||
|  | 		return lexSkip(lx, lexInlineTableValueEnd) | ||||||
|  | 	case isNL(r): | ||||||
|  | 		return lx.errorf("newlines not allowed within inline tables") | ||||||
|  | 	case r == commentStart: | ||||||
|  | 		lx.push(lexInlineTableValueEnd) | ||||||
|  | 		return lexCommentStart | ||||||
|  | 	case r == comma: | ||||||
|  | 		lx.ignore() | ||||||
|  | 		return lexInlineTableValue | ||||||
|  | 	case r == inlineTableEnd: | ||||||
|  | 		return lexInlineTableEnd | ||||||
|  | 	} | ||||||
|  | 	return lx.errorf("expected a comma or an inline table terminator %q, "+ | ||||||
|  | 		"but got %q instead", inlineTableEnd, r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // lexInlineTableEnd finishes the lexing of an inline table. | ||||||
|  | // It assumes that a '}' has just been consumed. | ||||||
|  | func lexInlineTableEnd(lx *lexer) stateFn { | ||||||
|  | 	lx.ignore() | ||||||
|  | 	lx.emit(itemInlineTableEnd) | ||||||
|  | 	return lx.pop() | ||||||
|  | } | ||||||
|  |  | ||||||
| // lexString consumes the inner contents of a string. It assumes that the | // lexString consumes the inner contents of a string. It assumes that the | ||||||
| // beginning '"' has already been consumed and ignored. | // beginning '"' has already been consumed and ignored. | ||||||
| func lexString(lx *lexer) stateFn { | func lexString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
|  | 	case r == eof: | ||||||
|  | 		return lx.errorf("unexpected EOF") | ||||||
| 	case isNL(r): | 	case isNL(r): | ||||||
| 		return lx.errorf("Strings cannot contain new lines.") | 		return lx.errorf("strings cannot contain newlines") | ||||||
| 	case r == '\\': | 	case r == '\\': | ||||||
| 		lx.push(lexString) | 		lx.push(lexString) | ||||||
| 		return lexStringEscape | 		return lexStringEscape | ||||||
| @@ -506,11 +598,12 @@ func lexString(lx *lexer) stateFn { | |||||||
| // lexMultilineString consumes the inner contents of a string. It assumes that | // lexMultilineString consumes the inner contents of a string. It assumes that | ||||||
| // the beginning '"""' has already been consumed and ignored. | // the beginning '"""' has already been consumed and ignored. | ||||||
| func lexMultilineString(lx *lexer) stateFn { | func lexMultilineString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	switch lx.next() { | ||||||
| 	switch { | 	case eof: | ||||||
| 	case r == '\\': | 		return lx.errorf("unexpected EOF") | ||||||
|  | 	case '\\': | ||||||
| 		return lexMultilineStringEscape | 		return lexMultilineStringEscape | ||||||
| 	case r == stringEnd: | 	case stringEnd: | ||||||
| 		if lx.accept(stringEnd) { | 		if lx.accept(stringEnd) { | ||||||
| 			if lx.accept(stringEnd) { | 			if lx.accept(stringEnd) { | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| @@ -534,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn { | |||||||
| func lexRawString(lx *lexer) stateFn { | func lexRawString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	switch { | 	switch { | ||||||
|  | 	case r == eof: | ||||||
|  | 		return lx.errorf("unexpected EOF") | ||||||
| 	case isNL(r): | 	case isNL(r): | ||||||
| 		return lx.errorf("Strings cannot contain new lines.") | 		return lx.errorf("strings cannot contain newlines") | ||||||
| 	case r == rawStringEnd: | 	case r == rawStringEnd: | ||||||
| 		lx.backup() | 		lx.backup() | ||||||
| 		lx.emit(itemRawString) | 		lx.emit(itemRawString) | ||||||
| @@ -547,12 +642,13 @@ func lexRawString(lx *lexer) stateFn { | |||||||
| } | } | ||||||
|  |  | ||||||
| // lexMultilineRawString consumes a raw string. Nothing can be escaped in such | // lexMultilineRawString consumes a raw string. Nothing can be escaped in such | ||||||
| // a string. It assumes that the beginning "'" has already been consumed and | // a string. It assumes that the beginning "'''" has already been consumed and | ||||||
| // ignored. | // ignored. | ||||||
| func lexMultilineRawString(lx *lexer) stateFn { | func lexMultilineRawString(lx *lexer) stateFn { | ||||||
| 	r := lx.next() | 	switch lx.next() { | ||||||
| 	switch { | 	case eof: | ||||||
| 	case r == rawStringEnd: | 		return lx.errorf("unexpected EOF") | ||||||
|  | 	case rawStringEnd: | ||||||
| 		if lx.accept(rawStringEnd) { | 		if lx.accept(rawStringEnd) { | ||||||
| 			if lx.accept(rawStringEnd) { | 			if lx.accept(rawStringEnd) { | ||||||
| 				lx.backup() | 				lx.backup() | ||||||
| @@ -605,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn { | |||||||
| 	case 'U': | 	case 'U': | ||||||
| 		return lexLongUnicodeEscape | 		return lexLongUnicodeEscape | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Invalid escape character %q. Only the following "+ | 	return lx.errorf("invalid escape character %q; only the following "+ | ||||||
| 		"escape characters are allowed: "+ | 		"escape characters are allowed: "+ | ||||||
| 		"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ | 		`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) | ||||||
| 		"\\uXXXX and \\UXXXXXXXX.", r) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func lexShortUnicodeEscape(lx *lexer) stateFn { | func lexShortUnicodeEscape(lx *lexer) stateFn { | ||||||
| @@ -616,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn { | |||||||
| 	for i := 0; i < 4; i++ { | 	for i := 0; i < 4; i++ { | ||||||
| 		r = lx.next() | 		r = lx.next() | ||||||
| 		if !isHexadecimal(r) { | 		if !isHexadecimal(r) { | ||||||
| 			return lx.errorf("Expected four hexadecimal digits after '\\u', "+ | 			return lx.errorf(`expected four hexadecimal digits after '\u', `+ | ||||||
| 				"but got '%s' instead.", lx.current()) | 				"but got %q instead", lx.current()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
| @@ -628,8 +723,8 @@ func lexLongUnicodeEscape(lx *lexer) stateFn { | |||||||
| 	for i := 0; i < 8; i++ { | 	for i := 0; i < 8; i++ { | ||||||
| 		r = lx.next() | 		r = lx.next() | ||||||
| 		if !isHexadecimal(r) { | 		if !isHexadecimal(r) { | ||||||
| 			return lx.errorf("Expected eight hexadecimal digits after '\\U', "+ | 			return lx.errorf(`expected eight hexadecimal digits after '\U', `+ | ||||||
| 				"but got '%s' instead.", lx.current()) | 				"but got %q instead", lx.current()) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return lx.pop() | 	return lx.pop() | ||||||
| @@ -647,9 +742,9 @@ func lexNumberOrDateStart(lx *lexer) stateFn { | |||||||
| 	case 'e', 'E': | 	case 'e', 'E': | ||||||
| 		return lexFloat | 		return lexFloat | ||||||
| 	case '.': | 	case '.': | ||||||
| 		return lx.errorf("Floats must start with a digit, not '.'.") | 		return lx.errorf("floats must start with a digit, not '.'") | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Expected a digit but got %q.", r) | 	return lx.errorf("expected a digit but got %q", r) | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexNumberOrDate consumes either an integer, float or datetime. | // lexNumberOrDate consumes either an integer, float or datetime. | ||||||
| @@ -697,9 +792,9 @@ func lexNumberStart(lx *lexer) stateFn { | |||||||
| 	r := lx.next() | 	r := lx.next() | ||||||
| 	if !isDigit(r) { | 	if !isDigit(r) { | ||||||
| 		if r == '.' { | 		if r == '.' { | ||||||
| 			return lx.errorf("Floats must start with a digit, not '.'.") | 			return lx.errorf("floats must start with a digit, not '.'") | ||||||
| 		} | 		} | ||||||
| 		return lx.errorf("Expected a digit but got %q.", r) | 		return lx.errorf("expected a digit but got %q", r) | ||||||
| 	} | 	} | ||||||
| 	return lexNumber | 	return lexNumber | ||||||
| } | } | ||||||
| @@ -757,7 +852,7 @@ func lexBool(lx *lexer) stateFn { | |||||||
| 		lx.emit(itemBool) | 		lx.emit(itemBool) | ||||||
| 		return lx.pop() | 		return lx.pop() | ||||||
| 	} | 	} | ||||||
| 	return lx.errorf("Expected value but found %q instead.", s) | 	return lx.errorf("expected value but found %q instead", s) | ||||||
| } | } | ||||||
|  |  | ||||||
| // lexCommentStart begins the lexing of a comment. It will emit | // lexCommentStart begins the lexing of a comment. It will emit | ||||||
| @@ -769,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn { | |||||||
| } | } | ||||||
|  |  | ||||||
| // lexComment lexes an entire comment. It assumes that '#' has been consumed. | // lexComment lexes an entire comment. It assumes that '#' has been consumed. | ||||||
| // It will consume *up to* the first new line character, and pass control | // It will consume *up to* the first newline character, and pass control | ||||||
| // back to the last state on the stack. | // back to the last state on the stack. | ||||||
| func lexComment(lx *lexer) stateFn { | func lexComment(lx *lexer) stateFn { | ||||||
| 	r := lx.peek() | 	r := lx.peek() | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								vendor/github.com/BurntSushi/toml/parse.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/BurntSushi/toml/parse.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -269,6 +269,41 @@ func (p *parser) value(it item) (interface{}, tomlType) { | |||||||
| 			types = append(types, typ) | 			types = append(types, typ) | ||||||
| 		} | 		} | ||||||
| 		return array, p.typeOfArray(types) | 		return array, p.typeOfArray(types) | ||||||
|  | 	case itemInlineTableStart: | ||||||
|  | 		var ( | ||||||
|  | 			hash         = make(map[string]interface{}) | ||||||
|  | 			outerContext = p.context | ||||||
|  | 			outerKey     = p.currentKey | ||||||
|  | 		) | ||||||
|  |  | ||||||
|  | 		p.context = append(p.context, p.currentKey) | ||||||
|  | 		p.currentKey = "" | ||||||
|  | 		for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { | ||||||
|  | 			if it.typ != itemKeyStart { | ||||||
|  | 				p.bug("Expected key start but instead found %q, around line %d", | ||||||
|  | 					it.val, p.approxLine) | ||||||
|  | 			} | ||||||
|  | 			if it.typ == itemCommentStart { | ||||||
|  | 				p.expect(itemText) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// retrieve key | ||||||
|  | 			k := p.next() | ||||||
|  | 			p.approxLine = k.line | ||||||
|  | 			kname := p.keyString(k) | ||||||
|  |  | ||||||
|  | 			// retrieve value | ||||||
|  | 			p.currentKey = kname | ||||||
|  | 			val, typ := p.value(p.next()) | ||||||
|  | 			// make sure we keep metadata up to date | ||||||
|  | 			p.setType(kname, typ) | ||||||
|  | 			p.ordered = append(p.ordered, p.context.add(p.currentKey)) | ||||||
|  | 			hash[kname] = val | ||||||
|  | 		} | ||||||
|  | 		p.context = outerContext | ||||||
|  | 		p.currentKey = outerKey | ||||||
|  | 		return hash, tomlHash | ||||||
| 	} | 	} | ||||||
| 	p.bug("Unexpected value type: %s", it.typ) | 	p.bug("Unexpected value type: %s", it.typ) | ||||||
| 	panic("unreachable") | 	panic("unreachable") | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								vendor/github.com/GeertJohan/go.rice/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/GeertJohan/go.rice/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | Copyright (c) 2013, Geert-Johan Riemer | ||||||
|  | All rights reserved. | ||||||
|  |  | ||||||
|  | Redistribution and use in source and binary forms, with or without | ||||||
|  | modification, are permitted provided that the following conditions are met: | ||||||
|  |  | ||||||
|  | 1. Redistributions of source code must retain the above copyright notice, this | ||||||
|  |    list of conditions and the following disclaimer. | ||||||
|  | 2. Redistributions in binary form must reproduce the above copyright notice, | ||||||
|  |    this list of conditions and the following disclaimer in the documentation | ||||||
|  |    and/or other materials provided with the distribution. | ||||||
|  |  | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||||
|  | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||||
|  | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||||||
|  | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||||
|  | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||||
|  | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||||||
|  | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										138
									
								
								vendor/github.com/GeertJohan/go.rice/appended.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								vendor/github.com/GeertJohan/go.rice/appended.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"archive/zip" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/daaku/go.zipexe" | ||||||
|  | 	"github.com/kardianos/osext" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // appendedBox defines an appended box | ||||||
|  | type appendedBox struct { | ||||||
|  | 	Name  string                   // box name | ||||||
|  | 	Files map[string]*appendedFile // appended files (*zip.File) by full path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type appendedFile struct { | ||||||
|  | 	zipFile  *zip.File | ||||||
|  | 	dir      bool | ||||||
|  | 	dirInfo  *appendedDirInfo | ||||||
|  | 	children []*appendedFile | ||||||
|  | 	content  []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // appendedBoxes is a public register of appendes boxes | ||||||
|  | var appendedBoxes = make(map[string]*appendedBox) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	// find if exec is appended | ||||||
|  | 	thisFile, err := osext.Executable() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return // not appended or cant find self executable | ||||||
|  | 	} | ||||||
|  | 	closer, rd, err := zipexe.OpenCloser(thisFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return // not appended | ||||||
|  | 	} | ||||||
|  | 	defer closer.Close() | ||||||
|  |  | ||||||
|  | 	for _, f := range rd.File { | ||||||
|  | 		// get box and file name from f.Name | ||||||
|  | 		fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2) | ||||||
|  | 		boxName := fileParts[0] | ||||||
|  | 		var fileName string | ||||||
|  | 		if len(fileParts) > 1 { | ||||||
|  | 			fileName = fileParts[1] | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// find box or create new one if doesn't exist | ||||||
|  | 		box := appendedBoxes[boxName] | ||||||
|  | 		if box == nil { | ||||||
|  | 			box = &appendedBox{ | ||||||
|  | 				Name:  boxName, | ||||||
|  | 				Files: make(map[string]*appendedFile), | ||||||
|  | 			} | ||||||
|  | 			appendedBoxes[boxName] = box | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// create and add file to box | ||||||
|  | 		af := &appendedFile{ | ||||||
|  | 			zipFile: f, | ||||||
|  | 		} | ||||||
|  | 		if f.Comment == "dir" { | ||||||
|  | 			af.dir = true | ||||||
|  | 			af.dirInfo = &appendedDirInfo{ | ||||||
|  | 				name: filepath.Base(af.zipFile.Name), | ||||||
|  | 				//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime() | ||||||
|  | 				time: time.Now(), | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened | ||||||
|  | 			// make a new byteslice | ||||||
|  | 			af.content = make([]byte, af.zipFile.FileInfo().Size()) | ||||||
|  | 			// ignore reading empty files from zip (empty file still is a valid file to be read though!) | ||||||
|  | 			if len(af.content) > 0 { | ||||||
|  | 				// open io.ReadCloser | ||||||
|  | 				rc, err := af.zipFile.Open() | ||||||
|  | 				if err != nil { | ||||||
|  | 					af.content = nil // this will cause an error when the file is being opened or seeked (which is good) | ||||||
|  | 					// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet.. | ||||||
|  | 					log.Printf("error opening appended file %s: %v", af.zipFile.Name, err) | ||||||
|  | 				} else { | ||||||
|  | 					_, err = rc.Read(af.content) | ||||||
|  | 					rc.Close() | ||||||
|  | 					if err != nil { | ||||||
|  | 						af.content = nil // this will cause an error when the file is being opened or seeked (which is good) | ||||||
|  | 						// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet.. | ||||||
|  | 						log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// add appendedFile to box file list | ||||||
|  | 		box.Files[fileName] = af | ||||||
|  |  | ||||||
|  | 		// add to parent dir (if any) | ||||||
|  | 		dirName := filepath.Dir(fileName) | ||||||
|  | 		if dirName == "." { | ||||||
|  | 			dirName = "" | ||||||
|  | 		} | ||||||
|  | 		if fileName != "" { // don't make box root dir a child of itself | ||||||
|  | 			if dir := box.Files[dirName]; dir != nil { | ||||||
|  | 				dir.children = append(dir.children, af) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // implements os.FileInfo. | ||||||
|  | // used for Readdir() | ||||||
|  | type appendedDirInfo struct { | ||||||
|  | 	name string | ||||||
|  | 	time time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (adi *appendedDirInfo) Name() string { | ||||||
|  | 	return adi.name | ||||||
|  | } | ||||||
|  | func (adi *appendedDirInfo) Size() int64 { | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  | func (adi *appendedDirInfo) Mode() os.FileMode { | ||||||
|  | 	return os.ModeDir | ||||||
|  | } | ||||||
|  | func (adi *appendedDirInfo) ModTime() time.Time { | ||||||
|  | 	return adi.time | ||||||
|  | } | ||||||
|  | func (adi *appendedDirInfo) IsDir() bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  | func (adi *appendedDirInfo) Sys() interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										337
									
								
								vendor/github.com/GeertJohan/go.rice/box.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								vendor/github.com/GeertJohan/go.rice/box.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,337 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Box abstracts a directory for resources/files. | ||||||
|  | // It can either load files from disk, or from embedded code (when `rice --embed` was ran). | ||||||
|  | type Box struct { | ||||||
|  | 	name         string | ||||||
|  | 	absolutePath string | ||||||
|  | 	embed        *embedded.EmbeddedBox | ||||||
|  | 	appendd      *appendedBox | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS} | ||||||
|  |  | ||||||
|  | func findBox(name string, order []LocateMethod) (*Box, error) { | ||||||
|  | 	b := &Box{name: name} | ||||||
|  |  | ||||||
|  | 	// no support for absolute paths since gopath can be different on different machines. | ||||||
|  | 	// therefore, required box must be located relative to package requiring it. | ||||||
|  | 	if filepath.IsAbs(name) { | ||||||
|  | 		return nil, errors.New("given name/path is absolute") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	for _, method := range order { | ||||||
|  | 		switch method { | ||||||
|  | 		case LocateEmbedded: | ||||||
|  | 			if embed := embedded.EmbeddedBoxes[name]; embed != nil { | ||||||
|  | 				b.embed = embed | ||||||
|  | 				return b, nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case LocateAppended: | ||||||
|  | 			appendedBoxName := strings.Replace(name, `/`, `-`, -1) | ||||||
|  | 			if appendd := appendedBoxes[appendedBoxName]; appendd != nil { | ||||||
|  | 				b.appendd = appendd | ||||||
|  | 				return b, nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case LocateFS: | ||||||
|  | 			// resolve absolute directory path | ||||||
|  | 			err := b.resolveAbsolutePathFromCaller() | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// check if absolutePath exists on filesystem | ||||||
|  | 			info, err := os.Stat(b.absolutePath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// check if absolutePath is actually a directory | ||||||
|  | 			if !info.IsDir() { | ||||||
|  | 				err = errors.New("given name/path is not a directory") | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			return b, nil | ||||||
|  | 		case LocateWorkingDirectory: | ||||||
|  | 			// resolve absolute directory path | ||||||
|  | 			err := b.resolveAbsolutePathFromWorkingDirectory() | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// check if absolutePath exists on filesystem | ||||||
|  | 			info, err := os.Stat(b.absolutePath) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// check if absolutePath is actually a directory | ||||||
|  | 			if !info.IsDir() { | ||||||
|  | 				err = errors.New("given name/path is not a directory") | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			return b, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err == nil { | ||||||
|  | 		err = fmt.Errorf("could not locate box %q", name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FindBox returns a Box instance for given name. | ||||||
|  | // When the given name is a relative path, it's base path will be the calling pkg/cmd's source root. | ||||||
|  | // When the given name is absolute, it's absolute. derp. | ||||||
|  | // Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded). | ||||||
|  | func FindBox(name string) (*Box, error) { | ||||||
|  | 	return findBox(name, defaultLocateOrder) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MustFindBox returns a Box instance for given name, like FindBox does. | ||||||
|  | // It does not return an error, instead it panics when an error occurs. | ||||||
|  | func MustFindBox(name string) *Box { | ||||||
|  | 	box, err := findBox(name, defaultLocateOrder) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return box | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This is injected as a mutable function literal so that we can mock it out in | ||||||
|  | // tests and return a fixed test file. | ||||||
|  | var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) { | ||||||
|  | 	_, callingGoFile, _, ok := runtime.Caller(nStackFrames) | ||||||
|  | 	if !ok { | ||||||
|  | 		return "", errors.New("couldn't find caller on stack") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// resolve to proper path | ||||||
|  | 	pkgDir := filepath.Dir(callingGoFile) | ||||||
|  | 	// fix for go cover | ||||||
|  | 	const coverPath = "_test/_obj_test" | ||||||
|  | 	if !filepath.IsAbs(pkgDir) { | ||||||
|  | 		if i := strings.Index(pkgDir, coverPath); i >= 0 { | ||||||
|  | 			pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):]            // remove coverPath | ||||||
|  | 			pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return filepath.Join(pkgDir, name), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Box) resolveAbsolutePathFromCaller() error { | ||||||
|  | 	path, err := resolveAbsolutePathFromCaller(b.name, 4) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.absolutePath = path | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Box) resolveAbsolutePathFromWorkingDirectory() error { | ||||||
|  | 	path, err := os.Getwd() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	b.absolutePath = filepath.Join(path, b.name) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsEmbedded indicates wether this box was embedded into the application | ||||||
|  | func (b *Box) IsEmbedded() bool { | ||||||
|  | 	return b.embed != nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsAppended indicates wether this box was appended to the application | ||||||
|  | func (b *Box) IsAppended() bool { | ||||||
|  | 	return b.appendd != nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Time returns how actual the box is. | ||||||
|  | // When the box is embedded, it's value is saved in the embedding code. | ||||||
|  | // When the box is live, this methods returns time.Now() | ||||||
|  | func (b *Box) Time() time.Time { | ||||||
|  | 	if b.IsEmbedded() { | ||||||
|  | 		return b.embed.Time | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	//++ TODO: return time for appended box | ||||||
|  |  | ||||||
|  | 	return time.Now() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open opens a File from the box | ||||||
|  | // If there is an error, it will be of type *os.PathError. | ||||||
|  | func (b *Box) Open(name string) (*File, error) { | ||||||
|  | 	if Debug { | ||||||
|  | 		fmt.Printf("Open(%s)\n", name) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if b.IsEmbedded() { | ||||||
|  | 		if Debug { | ||||||
|  | 			fmt.Println("Box is embedded") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// trim prefix (paths are relative to box) | ||||||
|  | 		name = strings.TrimLeft(name, "/") | ||||||
|  | 		if Debug { | ||||||
|  | 			fmt.Printf("Trying %s\n", name) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// search for file | ||||||
|  | 		ef := b.embed.Files[name] | ||||||
|  | 		if ef == nil { | ||||||
|  | 			if Debug { | ||||||
|  | 				fmt.Println("Didn't find file in embed") | ||||||
|  | 			} | ||||||
|  | 			// file not found, try dir | ||||||
|  | 			ed := b.embed.Dirs[name] | ||||||
|  | 			if ed == nil { | ||||||
|  | 				if Debug { | ||||||
|  | 					fmt.Println("Didn't find dir in embed") | ||||||
|  | 				} | ||||||
|  | 				// dir not found, error out | ||||||
|  | 				return nil, &os.PathError{ | ||||||
|  | 					Op:   "open", | ||||||
|  | 					Path: name, | ||||||
|  | 					Err:  os.ErrNotExist, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if Debug { | ||||||
|  | 				fmt.Println("Found dir. Returning virtual dir") | ||||||
|  | 			} | ||||||
|  | 			vd := newVirtualDir(ed) | ||||||
|  | 			return &File{virtualD: vd}, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// box is embedded | ||||||
|  | 		if Debug { | ||||||
|  | 			fmt.Println("Found file. Returning virtual file") | ||||||
|  | 		} | ||||||
|  | 		vf := newVirtualFile(ef) | ||||||
|  | 		return &File{virtualF: vf}, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if b.IsAppended() { | ||||||
|  | 		// trim prefix (paths are relative to box) | ||||||
|  | 		name = strings.TrimLeft(name, "/") | ||||||
|  |  | ||||||
|  | 		// search for file | ||||||
|  | 		appendedFile := b.appendd.Files[name] | ||||||
|  | 		if appendedFile == nil { | ||||||
|  | 			return nil, &os.PathError{ | ||||||
|  | 				Op:   "open", | ||||||
|  | 				Path: name, | ||||||
|  | 				Err:  os.ErrNotExist, | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// create new file | ||||||
|  | 		f := &File{ | ||||||
|  | 			appendedF: appendedFile, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// if this file is a directory, we want to be able to read and seek | ||||||
|  | 		if !appendedFile.dir { | ||||||
|  | 			// looks like malformed data in zip, error now | ||||||
|  | 			if appendedFile.content == nil { | ||||||
|  | 				return nil, &os.PathError{ | ||||||
|  | 					Op:   "open", | ||||||
|  | 					Path: "name", | ||||||
|  | 					Err:  errors.New("error reading data from zip file"), | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			// create new bytes.Reader | ||||||
|  | 			f.appendedFileReader = bytes.NewReader(appendedFile.content) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// all done | ||||||
|  | 		return f, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// perform os open | ||||||
|  | 	if Debug { | ||||||
|  | 		fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name)) | ||||||
|  | 	} | ||||||
|  | 	file, err := os.Open(filepath.Join(b.absolutePath, name)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &File{realF: file}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Bytes returns the content of the file with given name as []byte. | ||||||
|  | func (b *Box) Bytes(name string) ([]byte, error) { | ||||||
|  | 	file, err := b.Open(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer file.Close() | ||||||
|  |  | ||||||
|  | 	content, err := ioutil.ReadAll(file) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return content, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MustBytes returns the content of the file with given name as []byte. | ||||||
|  | // panic's on error. | ||||||
|  | func (b *Box) MustBytes(name string) []byte { | ||||||
|  | 	bts, err := b.Bytes(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return bts | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // String returns the content of the file with given name as string. | ||||||
|  | func (b *Box) String(name string) (string, error) { | ||||||
|  | 	// check if box is embedded, optimized fast path | ||||||
|  | 	if b.IsEmbedded() { | ||||||
|  | 		// find file in embed | ||||||
|  | 		ef := b.embed.Files[name] | ||||||
|  | 		if ef == nil { | ||||||
|  | 			return "", os.ErrNotExist | ||||||
|  | 		} | ||||||
|  | 		// return as string | ||||||
|  | 		return ef.Content, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bts, err := b.Bytes(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return string(bts), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MustString returns the content of the file with given name as string. | ||||||
|  | // panic's on error. | ||||||
|  | func (b *Box) MustString(name string) string { | ||||||
|  | 	str, err := b.String(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return str | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Name returns the name of the box | ||||||
|  | func (b *Box) Name() string { | ||||||
|  | 	return b.name | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								vendor/github.com/GeertJohan/go.rice/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/GeertJohan/go.rice/config.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | // LocateMethod defines how a box is located. | ||||||
|  | type LocateMethod int | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	LocateFS               = LocateMethod(iota) // Locate on the filesystem according to package path. | ||||||
|  | 	LocateAppended                              // Locate boxes appended to the executable. | ||||||
|  | 	LocateEmbedded                              // Locate embedded boxes. | ||||||
|  | 	LocateWorkingDirectory                      // Locate on the binary working directory | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Config allows customizing the box lookup behavior. | ||||||
|  | type Config struct { | ||||||
|  | 	// LocateOrder defines the priority order that boxes are searched for. By | ||||||
|  | 	// default, the package global FindBox searches for embedded boxes first, | ||||||
|  | 	// then appended boxes, and then finally boxes on the filesystem.  That | ||||||
|  | 	// search order may be customized by provided the ordered list here. Leaving | ||||||
|  | 	// out a particular method will omit that from the search space. For | ||||||
|  | 	// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search | ||||||
|  | 	// the filesystem for boxes. | ||||||
|  | 	LocateOrder []LocateMethod | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FindBox searches for boxes using the LocateOrder of the config. | ||||||
|  | func (c *Config) FindBox(boxName string) (*Box, error) { | ||||||
|  | 	return findBox(boxName, c.LocateOrder) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MustFindBox searches for boxes using the LocateOrder of the config, like | ||||||
|  | // FindBox does.  It does not return an error, instead it panics when an error | ||||||
|  | // occurs. | ||||||
|  | func (c *Config) MustFindBox(boxName string) *Box { | ||||||
|  | 	box, err := findBox(boxName, c.LocateOrder) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return box | ||||||
|  | } | ||||||
							
								
								
									
										4
									
								
								vendor/github.com/GeertJohan/go.rice/debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/GeertJohan/go.rice/debug.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | // Debug can be set to true to enable debugging. | ||||||
|  | var Debug = false | ||||||
							
								
								
									
										90
									
								
								vendor/github.com/GeertJohan/go.rice/embedded.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								vendor/github.com/GeertJohan/go.rice/embedded.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // re-type to make exported methods invisible to user (godoc) | ||||||
|  | // they're not required for the user | ||||||
|  | // embeddedDirInfo implements os.FileInfo | ||||||
|  | type embeddedDirInfo embedded.EmbeddedDir | ||||||
|  |  | ||||||
|  | // Name returns the base name of the directory | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) Name() string { | ||||||
|  | 	return ed.Filename | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Size always returns 0 | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) Size() int64 { | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Mode returns the file mode bits | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) Mode() os.FileMode { | ||||||
|  | 	return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ModTime returns the modification time | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) ModTime() time.Time { | ||||||
|  | 	return ed.DirModTime | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsDir returns the abbreviation for Mode().IsDir() (always true) | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) IsDir() bool { | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Sys returns the underlying data source (always nil) | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ed *embeddedDirInfo) Sys() interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // re-type to make exported methods invisible to user (godoc) | ||||||
|  | // they're not required for the user | ||||||
|  | // embeddedFileInfo implements os.FileInfo | ||||||
|  | type embeddedFileInfo embedded.EmbeddedFile | ||||||
|  |  | ||||||
|  | // Name returns the base name of the file | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) Name() string { | ||||||
|  | 	return ef.Filename | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Size returns the length in bytes for regular files; system-dependent for others | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) Size() int64 { | ||||||
|  | 	return int64(len(ef.Content)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Mode returns the file mode bits | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) Mode() os.FileMode { | ||||||
|  | 	return os.FileMode(0555) // r-xr-xr-x | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ModTime returns the modification time | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) ModTime() time.Time { | ||||||
|  | 	return ef.FileModTime | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsDir returns the abbreviation for Mode().IsDir() (always false) | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) IsDir() bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Sys returns the underlying data source (always nil) | ||||||
|  | // (implementing os.FileInfo) | ||||||
|  | func (ef *embeddedFileInfo) Sys() interface{} { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								vendor/github.com/GeertJohan/go.rice/embedded/embedded.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								vendor/github.com/GeertJohan/go.rice/embedded/embedded.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | // Package embedded defines embedded data types that are shared between the go.rice package and generated code. | ||||||
|  | package embedded | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	EmbedTypeGo   = 0 | ||||||
|  | 	EmbedTypeSyso = 1 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // EmbeddedBox defines an embedded box | ||||||
|  | type EmbeddedBox struct { | ||||||
|  | 	Name      string                   // box name | ||||||
|  | 	Time      time.Time                // embed time | ||||||
|  | 	EmbedType int                      // kind of embedding | ||||||
|  | 	Files     map[string]*EmbeddedFile // ALL embedded files by full path | ||||||
|  | 	Dirs      map[string]*EmbeddedDir  // ALL embedded dirs by full path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Link creates the ChildDirs and ChildFiles links in all EmbeddedDir's | ||||||
|  | func (e *EmbeddedBox) Link() { | ||||||
|  | 	for path, ed := range e.Dirs { | ||||||
|  | 		fmt.Println(path) | ||||||
|  | 		ed.ChildDirs = make([]*EmbeddedDir, 0) | ||||||
|  | 		ed.ChildFiles = make([]*EmbeddedFile, 0) | ||||||
|  | 	} | ||||||
|  | 	for path, ed := range e.Dirs { | ||||||
|  | 		parentDirpath, _ := filepath.Split(path) | ||||||
|  | 		if strings.HasSuffix(parentDirpath, "/") { | ||||||
|  | 			parentDirpath = parentDirpath[:len(parentDirpath)-1] | ||||||
|  | 		} | ||||||
|  | 		parentDir := e.Dirs[parentDirpath] | ||||||
|  | 		if parentDir == nil { | ||||||
|  | 			panic("parentDir `" + parentDirpath + "` is missing in embedded box") | ||||||
|  | 		} | ||||||
|  | 		parentDir.ChildDirs = append(parentDir.ChildDirs, ed) | ||||||
|  | 	} | ||||||
|  | 	for path, ef := range e.Files { | ||||||
|  | 		dirpath, _ := filepath.Split(path) | ||||||
|  | 		if strings.HasSuffix(dirpath, "/") { | ||||||
|  | 			dirpath = dirpath[:len(dirpath)-1] | ||||||
|  | 		} | ||||||
|  | 		dir := e.Dirs[dirpath] | ||||||
|  | 		if dir == nil { | ||||||
|  | 			panic("dir `" + dirpath + "` is missing in embedded box") | ||||||
|  | 		} | ||||||
|  | 		dir.ChildFiles = append(dir.ChildFiles, ef) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EmbeddedDir is instanced in the code generated by the rice tool and contains all necicary information about an embedded file | ||||||
|  | type EmbeddedDir struct { | ||||||
|  | 	Filename   string | ||||||
|  | 	DirModTime time.Time | ||||||
|  | 	ChildDirs  []*EmbeddedDir  // direct childs, as returned by virtualDir.Readdir() | ||||||
|  | 	ChildFiles []*EmbeddedFile // direct childs, as returned by virtualDir.Readdir() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EmbeddedFile is instanced in the code generated by the rice tool and contains all necicary information about an embedded file | ||||||
|  | type EmbeddedFile struct { | ||||||
|  | 	Filename    string // filename | ||||||
|  | 	FileModTime time.Time | ||||||
|  | 	Content     string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EmbeddedBoxes is a public register of embedded boxes | ||||||
|  | var EmbeddedBoxes = make(map[string]*EmbeddedBox) | ||||||
|  |  | ||||||
|  | // RegisterEmbeddedBox registers an EmbeddedBox | ||||||
|  | func RegisterEmbeddedBox(name string, box *EmbeddedBox) { | ||||||
|  | 	if _, exists := EmbeddedBoxes[name]; exists { | ||||||
|  | 		panic(fmt.Sprintf("EmbeddedBox with name `%s` exists already", name)) | ||||||
|  | 	} | ||||||
|  | 	EmbeddedBoxes[name] = box | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								vendor/github.com/GeertJohan/go.rice/example/example.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								vendor/github.com/GeertJohan/go.rice/example/example.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"text/template" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.rice" | ||||||
|  | 	"github.com/davecgh/go-spew/spew" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	conf := rice.Config{ | ||||||
|  | 		LocateOrder: []rice.LocateMethod{rice.LocateEmbedded, rice.LocateAppended, rice.LocateFS}, | ||||||
|  | 	} | ||||||
|  | 	box, err := conf.FindBox("example-files") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("error opening rice.Box: %s\n", err) | ||||||
|  | 	} | ||||||
|  | 	// spew.Dump(box) | ||||||
|  |  | ||||||
|  | 	contentString, err := box.String("file.txt") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("could not read file contents as string: %s\n", err) | ||||||
|  | 	} | ||||||
|  | 	log.Printf("Read some file contents as string:\n%s\n", contentString) | ||||||
|  |  | ||||||
|  | 	contentBytes, err := box.Bytes("file.txt") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("could not read file contents as byteSlice: %s\n", err) | ||||||
|  | 	} | ||||||
|  | 	log.Printf("Read some file contents as byteSlice:\n%s\n", hex.Dump(contentBytes)) | ||||||
|  |  | ||||||
|  | 	file, err := box.Open("file.txt") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("could not open file: %s\n", err) | ||||||
|  | 	} | ||||||
|  | 	spew.Dump(file) | ||||||
|  |  | ||||||
|  | 	// find/create a rice.Box | ||||||
|  | 	templateBox, err := rice.FindBox("example-templates") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	// get file contents as string | ||||||
|  | 	templateString, err := templateBox.String("message.tmpl") | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	// parse and execute the template | ||||||
|  | 	tmplMessage, err := template.New("message").Parse(templateString) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"}) | ||||||
|  |  | ||||||
|  | 	http.Handle("/", http.FileServer(box.HTTPBox())) | ||||||
|  | 	go func() { | ||||||
|  | 		fmt.Println("Serving files on :8080, press ctrl-C to exit") | ||||||
|  | 		err := http.ListenAndServe(":8080", nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("error serving files: %v", err) | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	select {} | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								vendor/github.com/GeertJohan/go.rice/file.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								vendor/github.com/GeertJohan/go.rice/file.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces | ||||||
|  | type File struct { | ||||||
|  | 	// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File | ||||||
|  | 	// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir | ||||||
|  |  | ||||||
|  | 	// real file on disk | ||||||
|  | 	realF *os.File | ||||||
|  |  | ||||||
|  | 	// when embedded (go) | ||||||
|  | 	virtualF *virtualFile | ||||||
|  | 	virtualD *virtualDir | ||||||
|  |  | ||||||
|  | 	// when appended (zip) | ||||||
|  | 	appendedF          *appendedFile | ||||||
|  | 	appendedFileReader *bytes.Reader | ||||||
|  | 	// TODO: is appendedFileReader subject of races? Might need a lock here.. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close is like (*os.File).Close() | ||||||
|  | // Visit http://golang.org/pkg/os/#File.Close for more information | ||||||
|  | func (f *File) Close() error { | ||||||
|  | 	if f.appendedF != nil { | ||||||
|  | 		if f.appendedFileReader == nil { | ||||||
|  | 			return errors.New("already closed") | ||||||
|  | 		} | ||||||
|  | 		f.appendedFileReader = nil | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if f.virtualF != nil { | ||||||
|  | 		return f.virtualF.close() | ||||||
|  | 	} | ||||||
|  | 	if f.virtualD != nil { | ||||||
|  | 		return f.virtualD.close() | ||||||
|  | 	} | ||||||
|  | 	return f.realF.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Stat is like (*os.File).Stat() | ||||||
|  | // Visit http://golang.org/pkg/os/#File.Stat for more information | ||||||
|  | func (f *File) Stat() (os.FileInfo, error) { | ||||||
|  | 	if f.appendedF != nil { | ||||||
|  | 		if f.appendedF.dir { | ||||||
|  | 			return f.appendedF.dirInfo, nil | ||||||
|  | 		} | ||||||
|  | 		if f.appendedFileReader == nil { | ||||||
|  | 			return nil, errors.New("file is closed") | ||||||
|  | 		} | ||||||
|  | 		return f.appendedF.zipFile.FileInfo(), nil | ||||||
|  | 	} | ||||||
|  | 	if f.virtualF != nil { | ||||||
|  | 		return f.virtualF.stat() | ||||||
|  | 	} | ||||||
|  | 	if f.virtualD != nil { | ||||||
|  | 		return f.virtualD.stat() | ||||||
|  | 	} | ||||||
|  | 	return f.realF.Stat() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Readdir is like (*os.File).Readdir() | ||||||
|  | // Visit http://golang.org/pkg/os/#File.Readdir for more information | ||||||
|  | func (f *File) Readdir(count int) ([]os.FileInfo, error) { | ||||||
|  | 	if f.appendedF != nil { | ||||||
|  | 		if f.appendedF.dir { | ||||||
|  | 			fi := make([]os.FileInfo, 0, len(f.appendedF.children)) | ||||||
|  | 			for _, childAppendedFile := range f.appendedF.children { | ||||||
|  | 				if childAppendedFile.dir { | ||||||
|  | 					fi = append(fi, childAppendedFile.dirInfo) | ||||||
|  | 				} else { | ||||||
|  | 					fi = append(fi, childAppendedFile.zipFile.FileInfo()) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return fi, nil | ||||||
|  | 		} | ||||||
|  | 		//++ TODO: is os.ErrInvalid the correct error for Readdir on file? | ||||||
|  | 		return nil, os.ErrInvalid | ||||||
|  | 	} | ||||||
|  | 	if f.virtualF != nil { | ||||||
|  | 		return f.virtualF.readdir(count) | ||||||
|  | 	} | ||||||
|  | 	if f.virtualD != nil { | ||||||
|  | 		return f.virtualD.readdir(count) | ||||||
|  | 	} | ||||||
|  | 	return f.realF.Readdir(count) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read is like (*os.File).Read() | ||||||
|  | // Visit http://golang.org/pkg/os/#File.Read for more information | ||||||
|  | func (f *File) Read(bts []byte) (int, error) { | ||||||
|  | 	if f.appendedF != nil { | ||||||
|  | 		if f.appendedFileReader == nil { | ||||||
|  | 			return 0, &os.PathError{ | ||||||
|  | 				Op:   "read", | ||||||
|  | 				Path: filepath.Base(f.appendedF.zipFile.Name), | ||||||
|  | 				Err:  errors.New("file is closed"), | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if f.appendedF.dir { | ||||||
|  | 			return 0, &os.PathError{ | ||||||
|  | 				Op:   "read", | ||||||
|  | 				Path: filepath.Base(f.appendedF.zipFile.Name), | ||||||
|  | 				Err:  errors.New("is a directory"), | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return f.appendedFileReader.Read(bts) | ||||||
|  | 	} | ||||||
|  | 	if f.virtualF != nil { | ||||||
|  | 		return f.virtualF.read(bts) | ||||||
|  | 	} | ||||||
|  | 	if f.virtualD != nil { | ||||||
|  | 		return f.virtualD.read(bts) | ||||||
|  | 	} | ||||||
|  | 	return f.realF.Read(bts) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Seek is like (*os.File).Seek() | ||||||
|  | // Visit http://golang.org/pkg/os/#File.Seek for more information | ||||||
|  | func (f *File) Seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	if f.appendedF != nil { | ||||||
|  | 		if f.appendedFileReader == nil { | ||||||
|  | 			return 0, &os.PathError{ | ||||||
|  | 				Op:   "seek", | ||||||
|  | 				Path: filepath.Base(f.appendedF.zipFile.Name), | ||||||
|  | 				Err:  errors.New("file is closed"), | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return f.appendedFileReader.Seek(offset, whence) | ||||||
|  | 	} | ||||||
|  | 	if f.virtualF != nil { | ||||||
|  | 		return f.virtualF.seek(offset, whence) | ||||||
|  | 	} | ||||||
|  | 	if f.virtualD != nil { | ||||||
|  | 		return f.virtualD.seek(offset, whence) | ||||||
|  | 	} | ||||||
|  | 	return f.realF.Seek(offset, whence) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/GeertJohan/go.rice/http.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/GeertJohan/go.rice/http.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer. | ||||||
|  | //   e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox())) | ||||||
|  | type HTTPBox struct { | ||||||
|  | 	*Box | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HTTPBox creates a new HTTPBox from an existing Box | ||||||
|  | func (b *Box) HTTPBox() *HTTPBox { | ||||||
|  | 	return &HTTPBox{b} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open returns a File using the http.File interface | ||||||
|  | func (hb *HTTPBox) Open(name string) (http.File, error) { | ||||||
|  | 	return hb.Box.Open(name) | ||||||
|  | } | ||||||
							
								
								
									
										172
									
								
								vendor/github.com/GeertJohan/go.rice/rice/append.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								vendor/github.com/GeertJohan/go.rice/rice/append.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"archive/zip" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/daaku/go.zipexe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func operationAppend(pkgs []*build.Package) { | ||||||
|  | 	if runtime.GOOS == "windows" { | ||||||
|  | 		_, err := exec.LookPath("zip") | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("#### WARNING ! ####") | ||||||
|  | 			fmt.Println("`rice append` is known not to work under windows because the `zip` command is not available. Please let me know if you got this to work (and how).") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// MARKED FOR DELETION | ||||||
|  | 	// This is actually not required, the append command now has the option --exec required. | ||||||
|  | 	// // check if package is a command | ||||||
|  | 	// if !pkg.IsCommand() { | ||||||
|  | 	// 	fmt.Println("Error: can not append to non-main package. Please follow instructions at github.com/GeertJohan/go.rice") | ||||||
|  | 	// 	os.Exit(1) | ||||||
|  | 	// } | ||||||
|  |  | ||||||
|  | 	// create tmp zipfile | ||||||
|  | 	tmpZipfileName := filepath.Join(os.TempDir(), fmt.Sprintf("ricebox-%d-%s.zip", time.Now().Unix(), randomString(10))) | ||||||
|  | 	verbosef("Will create tmp zipfile: %s\n", tmpZipfileName) | ||||||
|  | 	tmpZipfile, err := os.Create(tmpZipfileName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error creating tmp zipfile: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		tmpZipfile.Close() | ||||||
|  | 		os.Remove(tmpZipfileName) | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// find abs path for binary file | ||||||
|  | 	binfileName, err := filepath.Abs(flags.Append.Executable) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error finding absolute path for executable to append: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	verbosef("Will append to file: %s\n", binfileName) | ||||||
|  |  | ||||||
|  | 	// check that command doesn't already have zip appended | ||||||
|  | 	if rd, _ := zipexe.Open(binfileName); rd != nil { | ||||||
|  | 		fmt.Printf("Cannot append to already appended executable. Please remove %s and build a fresh one.\n", binfileName) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// open binfile | ||||||
|  | 	binfile, err := os.OpenFile(binfileName, os.O_WRONLY, os.ModeAppend) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error: unable to open executable file: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// create zip.Writer | ||||||
|  | 	zipWriter := zip.NewWriter(tmpZipfile) | ||||||
|  |  | ||||||
|  | 	for _, pkg := range pkgs { | ||||||
|  | 		// find boxes for this command | ||||||
|  | 		boxMap := findBoxes(pkg) | ||||||
|  |  | ||||||
|  | 		// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ? | ||||||
|  | 		if len(boxMap) == 0 { | ||||||
|  | 			fmt.Printf("no calls to rice.FindBox() or rice.MustFindBox() found in import path `%s`\n", pkg.ImportPath) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		verbosef("\n") | ||||||
|  |  | ||||||
|  | 		for boxname := range boxMap { | ||||||
|  | 			appendedBoxName := strings.Replace(boxname, `/`, `-`, -1) | ||||||
|  |  | ||||||
|  | 			// walk box path's and insert files | ||||||
|  | 			boxPath := filepath.Clean(filepath.Join(pkg.Dir, boxname)) | ||||||
|  | 			filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 				if info == nil { | ||||||
|  | 					fmt.Printf("Error: box \"%s\" not found on disk\n", path) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				// create zipFilename | ||||||
|  | 				zipFileName := filepath.Join(appendedBoxName, strings.TrimPrefix(path, boxPath)) | ||||||
|  | 				// write directories as empty file with comment "dir" | ||||||
|  | 				if info.IsDir() { | ||||||
|  | 					_, err := zipWriter.CreateHeader(&zip.FileHeader{ | ||||||
|  | 						Name:    zipFileName, | ||||||
|  | 						Comment: "dir", | ||||||
|  | 					}) | ||||||
|  | 					if err != nil { | ||||||
|  | 						fmt.Printf("Error creating dir in tmp zip: %s\n", err) | ||||||
|  | 						os.Exit(1) | ||||||
|  | 					} | ||||||
|  | 					return nil | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// create zipFileWriter | ||||||
|  | 				zipFileHeader, err := zip.FileInfoHeader(info) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("Error creating zip FileHeader: %v\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				zipFileHeader.Name = zipFileName | ||||||
|  | 				zipFileWriter, err := zipWriter.CreateHeader(zipFileHeader) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("Error creating file in tmp zip: %s\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				srcFile, err := os.Open(path) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("Error opening file to append: %s\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				_, err = io.Copy(zipFileWriter, srcFile) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("Error copying file contents to zip: %s\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				srcFile.Close() | ||||||
|  |  | ||||||
|  | 				return nil | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = zipWriter.Close() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error closing tmp zipfile: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = tmpZipfile.Sync() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error syncing tmp zipfile: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	_, err = tmpZipfile.Seek(0, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error seeking tmp zipfile: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	_, err = binfile.Seek(0, 2) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error seeking bin file: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = io.Copy(binfile, tmpZipfile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error appending zipfile to executable: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	zipA := exec.Command("zip", "-A", binfileName) | ||||||
|  | 	err = zipA.Run() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("Error setting zip offset: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								vendor/github.com/GeertJohan/go.rice/rice/clean.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/GeertJohan/go.rice/rice/clean.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func operationClean(pkg *build.Package) { | ||||||
|  | 	filepath.Walk(pkg.Dir, func(filename string, info os.FileInfo, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error walking pkg dir to clean files: %v\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		if info.IsDir() { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		verbosef("checking file '%s'\n", filename) | ||||||
|  | 		if filepath.Base(filename) == "rice-box.go" || | ||||||
|  | 			strings.HasSuffix(filename, ".rice-box.go") || | ||||||
|  | 			strings.HasSuffix(filename, ".rice-box.syso") { | ||||||
|  | 			err := os.Remove(filename) | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("error removing file (%s): %s\n", filename, err) | ||||||
|  | 				os.Exit(-1) | ||||||
|  | 			} | ||||||
|  | 			verbosef("removed file '%s'\n", filename) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										158
									
								
								vendor/github.com/GeertJohan/go.rice/rice/embed-go.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								vendor/github.com/GeertJohan/go.rice/rice/embed-go.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"go/format" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const boxFilename = "rice-box.go" | ||||||
|  |  | ||||||
|  | func operationEmbedGo(pkg *build.Package) { | ||||||
|  |  | ||||||
|  | 	boxMap := findBoxes(pkg) | ||||||
|  |  | ||||||
|  | 	// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ? | ||||||
|  | 	if len(boxMap) == 0 { | ||||||
|  | 		fmt.Println("no calls to rice.FindBox() found") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	verbosef("\n") | ||||||
|  | 	var boxes []*boxDataType | ||||||
|  |  | ||||||
|  | 	for boxname := range boxMap { | ||||||
|  | 		// find path and filename for this box | ||||||
|  | 		boxPath := filepath.Join(pkg.Dir, boxname) | ||||||
|  |  | ||||||
|  | 		// Check to see if the path for the box is a symbolic link.  If so, simply | ||||||
|  | 		// box what the symbolic link points to.  Note: the filepath.Walk function | ||||||
|  | 		// will NOT follow any nested symbolic links.  This only handles the case | ||||||
|  | 		// where the root of the box is a symbolic link. | ||||||
|  | 		symPath, serr := os.Readlink(boxPath) | ||||||
|  | 		if serr == nil { | ||||||
|  | 			boxPath = symPath | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// verbose info | ||||||
|  | 		verbosef("embedding box '%s' to '%s'\n", boxname, boxFilename) | ||||||
|  |  | ||||||
|  | 		// read box metadata | ||||||
|  | 		boxInfo, ierr := os.Stat(boxPath) | ||||||
|  | 		if ierr != nil { | ||||||
|  | 			fmt.Printf("Error: unable to access box at %s\n", boxPath) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// create box datastructure (used by template) | ||||||
|  | 		box := &boxDataType{ | ||||||
|  | 			BoxName: boxname, | ||||||
|  | 			UnixNow: boxInfo.ModTime().Unix(), | ||||||
|  | 			Files:   make([]*fileDataType, 0), | ||||||
|  | 			Dirs:    make(map[string]*dirDataType), | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !boxInfo.IsDir() { | ||||||
|  | 			fmt.Printf("Error: Box %s must point to a directory but points to %s instead\n", | ||||||
|  | 				boxname, boxPath) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// fill box datastructure with file data | ||||||
|  | 		filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("error walking box: %s\n", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			filename := strings.TrimPrefix(path, boxPath) | ||||||
|  | 			filename = strings.Replace(filename, "\\", "/", -1) | ||||||
|  | 			filename = strings.TrimPrefix(filename, "/") | ||||||
|  | 			if info.IsDir() { | ||||||
|  | 				dirData := &dirDataType{ | ||||||
|  | 					Identifier: "dir" + nextIdentifier(), | ||||||
|  | 					FileName:   filename, | ||||||
|  | 					ModTime:    info.ModTime().Unix(), | ||||||
|  | 					ChildFiles: make([]*fileDataType, 0), | ||||||
|  | 					ChildDirs:  make([]*dirDataType, 0), | ||||||
|  | 				} | ||||||
|  | 				verbosef("\tincludes dir: '%s'\n", dirData.FileName) | ||||||
|  | 				box.Dirs[dirData.FileName] = dirData | ||||||
|  |  | ||||||
|  | 				// add tree entry (skip for root, it'll create a recursion) | ||||||
|  | 				if dirData.FileName != "" { | ||||||
|  | 					pathParts := strings.Split(dirData.FileName, "/") | ||||||
|  | 					parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")] | ||||||
|  | 					parentDir.ChildDirs = append(parentDir.ChildDirs, dirData) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				fileData := &fileDataType{ | ||||||
|  | 					Identifier: "file" + nextIdentifier(), | ||||||
|  | 					FileName:   filename, | ||||||
|  | 					ModTime:    info.ModTime().Unix(), | ||||||
|  | 				} | ||||||
|  | 				verbosef("\tincludes file: '%s'\n", fileData.FileName) | ||||||
|  | 				fileData.Content, err = ioutil.ReadFile(path) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("error reading file content while walking box: %s\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				box.Files = append(box.Files, fileData) | ||||||
|  |  | ||||||
|  | 				// add tree entry | ||||||
|  | 				pathParts := strings.Split(fileData.FileName, "/") | ||||||
|  | 				parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")] | ||||||
|  | 				if parentDir == nil { | ||||||
|  | 					fmt.Printf("Error: parent of %s is not within the box\n", path) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				parentDir.ChildFiles = append(parentDir.ChildFiles, fileData) | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 		boxes = append(boxes, box) | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	embedSourceUnformated := bytes.NewBuffer(make([]byte, 0)) | ||||||
|  |  | ||||||
|  | 	// execute template to buffer | ||||||
|  | 	err := tmplEmbeddedBox.Execute( | ||||||
|  | 		embedSourceUnformated, | ||||||
|  | 		embedFileDataType{pkg.Name, boxes}, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error writing embedded box to file (template execute): %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// format the source code | ||||||
|  | 	embedSource, err := format.Source(embedSourceUnformated.Bytes()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error formatting embedSource: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// create go file for box | ||||||
|  | 	boxFile, err := os.Create(filepath.Join(pkg.Dir, boxFilename)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error creating embedded box file: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	defer boxFile.Close() | ||||||
|  |  | ||||||
|  | 	// write source to file | ||||||
|  | 	_, err = io.Copy(boxFile, bytes.NewBuffer(embedSource)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("error writing embedSource to file: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										204
									
								
								vendor/github.com/GeertJohan/go.rice/rice/embed-syso.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								vendor/github.com/GeertJohan/go.rice/rice/embed-syso.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/gob" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | 	"text/template" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | 	"github.com/akavel/rsrc/coff" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type sizedReader struct { | ||||||
|  | 	*bytes.Reader | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s sizedReader) Size() int64 { | ||||||
|  | 	return int64(s.Len()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var tmplEmbeddedSysoHelper *template.Template | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	var err error | ||||||
|  | 	tmplEmbeddedSysoHelper, err = template.New("embeddedSysoHelper").Parse(`package {{.Package}} | ||||||
|  | // ############# GENERATED CODE ##################### | ||||||
|  | // ## This file was generated by the rice tool. | ||||||
|  | // ## Do not edit unless you know what you're doing. | ||||||
|  | // ################################################## | ||||||
|  |  | ||||||
|  | // extern char _bricebox_{{.Symname}}[], _ericebox_{{.Symname}}; | ||||||
|  | // int get_{{.Symname}}_length() { | ||||||
|  | // 	return &_ericebox_{{.Symname}} - _bricebox_{{.Symname}}; | ||||||
|  | // } | ||||||
|  | import "C" | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/gob" | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | 	"unsafe" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	ptr := unsafe.Pointer(&C._bricebox_{{.Symname}}) | ||||||
|  | 	bts := C.GoBytes(ptr, C.get_{{.Symname}}_length()) | ||||||
|  | 	embeddedBox := &embedded.EmbeddedBox{} | ||||||
|  | 	err := gob.NewDecoder(bytes.NewReader(bts)).Decode(embeddedBox) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("error decoding embedded box: "+err.Error()) | ||||||
|  | 	} | ||||||
|  | 	embeddedBox.Link() | ||||||
|  | 	embedded.RegisterEmbeddedBox(embeddedBox.Name, embeddedBox) | ||||||
|  | }`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("could not parse template embeddedSysoHelper: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type embeddedSysoHelperData struct { | ||||||
|  | 	Package string | ||||||
|  | 	Symname string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func operationEmbedSyso(pkg *build.Package) { | ||||||
|  |  | ||||||
|  | 	regexpSynameReplacer := regexp.MustCompile(`[^a-z0-9_]`) | ||||||
|  |  | ||||||
|  | 	boxMap := findBoxes(pkg) | ||||||
|  |  | ||||||
|  | 	// notify user when no calls to rice.FindBox are made (is this an error and therefore os.Exit(1) ? | ||||||
|  | 	if len(boxMap) == 0 { | ||||||
|  | 		fmt.Println("no calls to rice.FindBox() found") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	verbosef("\n") | ||||||
|  |  | ||||||
|  | 	for boxname := range boxMap { | ||||||
|  | 		// find path and filename for this box | ||||||
|  | 		boxPath := filepath.Join(pkg.Dir, boxname) | ||||||
|  | 		boxFilename := strings.Replace(boxname, "/", "-", -1) | ||||||
|  | 		boxFilename = strings.Replace(boxFilename, "..", "back", -1) | ||||||
|  | 		boxFilename = strings.Replace(boxFilename, ".", "-", -1) | ||||||
|  |  | ||||||
|  | 		// verbose info | ||||||
|  | 		verbosef("embedding box '%s'\n", boxname) | ||||||
|  | 		verbosef("\tto file %s\n", boxFilename) | ||||||
|  |  | ||||||
|  | 		// read box metadata | ||||||
|  | 		boxInfo, ierr := os.Stat(boxPath) | ||||||
|  | 		if ierr != nil { | ||||||
|  | 			fmt.Printf("Error: unable to access box at %s\n", boxPath) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// create box datastructure (used by template) | ||||||
|  | 		box := &embedded.EmbeddedBox{ | ||||||
|  | 			Name:      boxname, | ||||||
|  | 			Time:      boxInfo.ModTime(), | ||||||
|  | 			EmbedType: embedded.EmbedTypeSyso, | ||||||
|  | 			Files:     make(map[string]*embedded.EmbeddedFile), | ||||||
|  | 			Dirs:      make(map[string]*embedded.EmbeddedDir), | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// fill box datastructure with file data | ||||||
|  | 		filepath.Walk(boxPath, func(path string, info os.FileInfo, err error) error { | ||||||
|  | 			if err != nil { | ||||||
|  | 				fmt.Printf("error walking box: %s\n", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			filename := strings.TrimPrefix(path, boxPath) | ||||||
|  | 			filename = strings.Replace(filename, "\\", "/", -1) | ||||||
|  | 			filename = strings.TrimPrefix(filename, "/") | ||||||
|  | 			if info.IsDir() { | ||||||
|  | 				embeddedDir := &embedded.EmbeddedDir{ | ||||||
|  | 					Filename:   filename, | ||||||
|  | 					DirModTime: info.ModTime(), | ||||||
|  | 				} | ||||||
|  | 				verbosef("\tincludes dir: '%s'\n", embeddedDir.Filename) | ||||||
|  | 				box.Dirs[embeddedDir.Filename] = embeddedDir | ||||||
|  |  | ||||||
|  | 				// add tree entry (skip for root, it'll create a recursion) | ||||||
|  | 				if embeddedDir.Filename != "" { | ||||||
|  | 					pathParts := strings.Split(embeddedDir.Filename, "/") | ||||||
|  | 					parentDir := box.Dirs[strings.Join(pathParts[:len(pathParts)-1], "/")] | ||||||
|  | 					parentDir.ChildDirs = append(parentDir.ChildDirs, embeddedDir) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				embeddedFile := &embedded.EmbeddedFile{ | ||||||
|  | 					Filename:    filename, | ||||||
|  | 					FileModTime: info.ModTime(), | ||||||
|  | 					Content:     "", | ||||||
|  | 				} | ||||||
|  | 				verbosef("\tincludes file: '%s'\n", embeddedFile.Filename) | ||||||
|  | 				contentBytes, err := ioutil.ReadFile(path) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Printf("error reading file content while walking box: %s\n", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 				embeddedFile.Content = string(contentBytes) | ||||||
|  | 				box.Files[embeddedFile.Filename] = embeddedFile | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		// encode embedded box to gob file | ||||||
|  | 		boxGobBuf := &bytes.Buffer{} | ||||||
|  | 		err := gob.NewEncoder(boxGobBuf).Encode(box) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error encoding box to gob: %v\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		verbosef("gob-encoded embeddedBox is %d bytes large\n", boxGobBuf.Len()) | ||||||
|  |  | ||||||
|  | 		// write coff | ||||||
|  | 		symname := regexpSynameReplacer.ReplaceAllString(boxname, "_") | ||||||
|  | 		createCoffSyso(boxname, symname, "386", boxGobBuf.Bytes()) | ||||||
|  | 		createCoffSyso(boxname, symname, "amd64", boxGobBuf.Bytes()) | ||||||
|  |  | ||||||
|  | 		// write go | ||||||
|  | 		sysoHelperData := embeddedSysoHelperData{ | ||||||
|  | 			Package: pkg.Name, | ||||||
|  | 			Symname: symname, | ||||||
|  | 		} | ||||||
|  | 		fileSysoHelper, err := os.Create(boxFilename + ".rice-box.go") | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error creating syso helper: %v\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		err = tmplEmbeddedSysoHelper.Execute(fileSysoHelper, sysoHelperData) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error executing tmplEmbeddedSysoHelper: %v\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createCoffSyso(boxFilename string, symname string, arch string, data []byte) { | ||||||
|  | 	boxCoff := coff.NewRDATA() | ||||||
|  | 	switch arch { | ||||||
|  | 	case "386": | ||||||
|  | 	case "amd64": | ||||||
|  | 		boxCoff.FileHeader.Machine = 0x8664 | ||||||
|  | 	default: | ||||||
|  | 		panic("invalid arch") | ||||||
|  | 	} | ||||||
|  | 	boxCoff.AddData("_bricebox_"+symname, sizedReader{bytes.NewReader(data)}) | ||||||
|  | 	boxCoff.AddData("_ericebox_"+symname, io.NewSectionReader(strings.NewReader("\000\000"), 0, 2)) // TODO: why? copied from rsrc, which copied it from as-generated | ||||||
|  | 	boxCoff.Freeze() | ||||||
|  | 	err := writeCoff(boxCoff, boxFilename+"_"+arch+".rice-box.syso") | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("error writing %s coff/.syso: %v\n", arch, err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										150
									
								
								vendor/github.com/GeertJohan/go.rice/rice/find.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								vendor/github.com/GeertJohan/go.rice/rice/find.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/build" | ||||||
|  | 	"go/parser" | ||||||
|  | 	"go/token" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func badArgument(fileset *token.FileSet, p token.Pos) { | ||||||
|  | 	pos := fileset.Position(p) | ||||||
|  | 	filename := pos.Filename | ||||||
|  | 	base, err := os.Getwd() | ||||||
|  | 	if err == nil { | ||||||
|  | 		rpath, perr := filepath.Rel(base, pos.Filename) | ||||||
|  | 		if perr == nil { | ||||||
|  | 			filename = rpath | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	msg := fmt.Sprintf("%s:%d: Error: found call to rice.FindBox, "+ | ||||||
|  | 		"but argument must be a string literal.\n", filename, pos.Line) | ||||||
|  | 	fmt.Println(msg) | ||||||
|  | 	os.Exit(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func findBoxes(pkg *build.Package) map[string]bool { | ||||||
|  | 	// create map of boxes to embed | ||||||
|  | 	var boxMap = make(map[string]bool) | ||||||
|  |  | ||||||
|  | 	// create one list of files for this package | ||||||
|  | 	filenames := make([]string, 0, len(pkg.GoFiles)+len(pkg.CgoFiles)) | ||||||
|  | 	filenames = append(filenames, pkg.GoFiles...) | ||||||
|  | 	filenames = append(filenames, pkg.CgoFiles...) | ||||||
|  |  | ||||||
|  | 	// loop over files, search for rice.FindBox(..) calls | ||||||
|  | 	for _, filename := range filenames { | ||||||
|  | 		// find full filepath | ||||||
|  | 		fullpath := filepath.Join(pkg.Dir, filename) | ||||||
|  | 		if strings.HasSuffix(filename, "rice-box.go") { | ||||||
|  | 			// Ignore *.rice-box.go files | ||||||
|  | 			verbosef("skipping file %q\n", fullpath) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		verbosef("scanning file %q\n", fullpath) | ||||||
|  |  | ||||||
|  | 		fset := token.NewFileSet() | ||||||
|  | 		f, err := parser.ParseFile(fset, fullpath, nil, 0) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println(err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		var riceIsImported bool | ||||||
|  | 		ricePkgName := "rice" | ||||||
|  | 		for _, imp := range f.Imports { | ||||||
|  | 			if strings.HasSuffix(imp.Path.Value, "go.rice\"") { | ||||||
|  | 				if imp.Name != nil { | ||||||
|  | 					ricePkgName = imp.Name.Name | ||||||
|  | 				} | ||||||
|  | 				riceIsImported = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !riceIsImported { | ||||||
|  | 			// Rice wasn't imported, so we won't find a box. | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if ricePkgName == "_" { | ||||||
|  | 			// Rice pkg is unnamed, so we won't find a box. | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Inspect AST, looking for calls to (Must)?FindBox. | ||||||
|  | 		// First parameter of the func must be a basic literal. | ||||||
|  | 		// Identifiers won't be resolved. | ||||||
|  | 		var nextIdentIsBoxFunc bool | ||||||
|  | 		var nextBasicLitParamIsBoxName bool | ||||||
|  | 		var boxCall token.Pos | ||||||
|  | 		var variableToRemember string | ||||||
|  | 		var validVariablesForBoxes map[string]bool = make(map[string]bool) | ||||||
|  |  | ||||||
|  | 		ast.Inspect(f, func(node ast.Node) bool { | ||||||
|  | 			if node == nil { | ||||||
|  | 				return false | ||||||
|  | 			} | ||||||
|  | 			switch x := node.(type) { | ||||||
|  | 			// this case fixes the var := func() style assignments, not assignments to vars declared separately from the assignment. | ||||||
|  | 			case *ast.AssignStmt: | ||||||
|  | 				var assign = node.(*ast.AssignStmt) | ||||||
|  | 				name, found := assign.Lhs[0].(*ast.Ident) | ||||||
|  | 				if found { | ||||||
|  | 					variableToRemember = name.Name | ||||||
|  | 					composite, first := assign.Rhs[0].(*ast.CompositeLit) | ||||||
|  | 					if first { | ||||||
|  | 						riceSelector, second := composite.Type.(*ast.SelectorExpr) | ||||||
|  |  | ||||||
|  | 						if second { | ||||||
|  | 							callCorrect := riceSelector.Sel.Name == "Config" | ||||||
|  | 							packageName, third := riceSelector.X.(*ast.Ident) | ||||||
|  |  | ||||||
|  | 							if third && callCorrect && packageName.Name == ricePkgName { | ||||||
|  | 								validVariablesForBoxes[name.Name] = true | ||||||
|  | 								verbosef("\tfound variable, saving to scan for boxes: %q\n", name.Name) | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			case *ast.Ident: | ||||||
|  | 				if nextIdentIsBoxFunc || ricePkgName == "." { | ||||||
|  | 					nextIdentIsBoxFunc = false | ||||||
|  | 					if x.Name == "FindBox" || x.Name == "MustFindBox" { | ||||||
|  | 						nextBasicLitParamIsBoxName = true | ||||||
|  | 						boxCall = x.Pos() | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					if x.Name == ricePkgName || validVariablesForBoxes[x.Name] { | ||||||
|  | 						nextIdentIsBoxFunc = true | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			case *ast.BasicLit: | ||||||
|  | 				if nextBasicLitParamIsBoxName { | ||||||
|  | 					if x.Kind == token.STRING { | ||||||
|  | 						nextBasicLitParamIsBoxName = false | ||||||
|  | 						// trim "" or `` | ||||||
|  | 						name := x.Value[1 : len(x.Value)-1] | ||||||
|  | 						boxMap[name] = true | ||||||
|  | 						verbosef("\tfound box %q\n", name) | ||||||
|  | 					} else { | ||||||
|  | 						badArgument(fset, boxCall) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			default: | ||||||
|  | 				if nextIdentIsBoxFunc { | ||||||
|  | 					nextIdentIsBoxFunc = false | ||||||
|  | 				} | ||||||
|  | 				if nextBasicLitParamIsBoxName { | ||||||
|  | 					badArgument(fset, boxCall) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			return true | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return boxMap | ||||||
|  | } | ||||||
							
								
								
									
										80
									
								
								vendor/github.com/GeertJohan/go.rice/rice/flags.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								vendor/github.com/GeertJohan/go.rice/rice/flags.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	goflags "github.com/jessevdk/go-flags" // rename import to `goflags` (file scope) so we can use `var flags` (package scope) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // flags | ||||||
|  | var flags struct { | ||||||
|  | 	Verbose     bool     `long:"verbose" short:"v" description:"Show verbose debug information"` | ||||||
|  | 	ImportPaths []string `long:"import-path" short:"i" description:"Import path(s) to use. Using PWD when left empty. Specify multiple times for more import paths to append"` | ||||||
|  |  | ||||||
|  | 	Append struct { | ||||||
|  | 		Executable string `long:"exec" description:"Executable to append" required:"true"` | ||||||
|  | 	} `command:"append"` | ||||||
|  |  | ||||||
|  | 	EmbedGo   struct{} `command:"embed-go" alias:"embed"` | ||||||
|  | 	EmbedSyso struct{} `command:"embed-syso"` | ||||||
|  | 	Clean     struct{} `command:"clean"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // flags parser | ||||||
|  | var flagsParser *goflags.Parser | ||||||
|  |  | ||||||
|  | // initFlags parses the given flags. | ||||||
|  | // when the user asks for help (-h or --help): the application exists with status 0 | ||||||
|  | // when unexpected flags is given: the application exits with status 1 | ||||||
|  | func parseArguments() { | ||||||
|  | 	// create flags parser in global var, for flagsParser.Active.Name (operation) | ||||||
|  | 	flagsParser = goflags.NewParser(&flags, goflags.Default) | ||||||
|  |  | ||||||
|  | 	// parse flags | ||||||
|  | 	args, err := flagsParser.Parse() | ||||||
|  | 	if err != nil { | ||||||
|  | 		// assert the err to be a flags.Error | ||||||
|  | 		flagError := err.(*goflags.Error) | ||||||
|  | 		if flagError.Type == goflags.ErrHelp { | ||||||
|  | 			// user asked for help on flags. | ||||||
|  | 			// program can exit successfully | ||||||
|  | 			os.Exit(0) | ||||||
|  | 		} | ||||||
|  | 		if flagError.Type == goflags.ErrUnknownFlag { | ||||||
|  | 			fmt.Println("Use --help to view available options.") | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		if flagError.Type == goflags.ErrRequired { | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		fmt.Printf("Error parsing flags: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// error on left-over arguments | ||||||
|  | 	if len(args) > 0 { | ||||||
|  | 		fmt.Printf("Unexpected arguments: %s\nUse --help to view available options.", args) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// default ImportPath to pwd when not set | ||||||
|  | 	if len(flags.ImportPaths) == 0 { | ||||||
|  | 		pwd, err := os.Getwd() | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error getting pwd: %s\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		verbosef("using pwd as import path\n") | ||||||
|  | 		// find non-absolute path for this pwd | ||||||
|  | 		pkg, err := build.ImportDir(pwd, build.FindOnly) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Printf("error using current directory as import path: %s\n", err) | ||||||
|  | 			os.Exit(1) | ||||||
|  | 		} | ||||||
|  | 		flags.ImportPaths = append(flags.ImportPaths, pkg.ImportPath) | ||||||
|  | 		verbosef("using import paths: %s\n", flags.ImportPaths) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								vendor/github.com/GeertJohan/go.rice/rice/identifier.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								vendor/github.com/GeertJohan/go.rice/rice/identifier.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.incremental" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var identifierCount incremental.Uint64 | ||||||
|  |  | ||||||
|  | func nextIdentifier() string { | ||||||
|  | 	num := identifierCount.Next() | ||||||
|  | 	return strconv.FormatUint(num, 36) // 0123456789abcdefghijklmnopqrstuvwxyz | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								vendor/github.com/GeertJohan/go.rice/rice/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								vendor/github.com/GeertJohan/go.rice/rice/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/build" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	// parser arguments | ||||||
|  | 	parseArguments() | ||||||
|  |  | ||||||
|  | 	// find package for path | ||||||
|  | 	var pkgs []*build.Package | ||||||
|  | 	for _, importPath := range flags.ImportPaths { | ||||||
|  | 		pkg := pkgForPath(importPath) | ||||||
|  | 		pkgs = append(pkgs, pkg) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// switch on the operation to perform | ||||||
|  | 	switch flagsParser.Active.Name { | ||||||
|  | 	case "embed", "embed-go": | ||||||
|  | 		for _, pkg := range pkgs { | ||||||
|  | 			operationEmbedGo(pkg) | ||||||
|  | 		} | ||||||
|  | 	case "embed-syso": | ||||||
|  | 		log.Println("WARNING: embedding .syso is experimental..") | ||||||
|  | 		for _, pkg := range pkgs { | ||||||
|  | 			operationEmbedSyso(pkg) | ||||||
|  | 		} | ||||||
|  | 	case "append": | ||||||
|  | 		operationAppend(pkgs) | ||||||
|  | 	case "clean": | ||||||
|  | 		for _, pkg := range pkgs { | ||||||
|  | 			operationClean(pkg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// all done | ||||||
|  | 	verbosef("\n") | ||||||
|  | 	verbosef("rice finished successfully\n") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // helper function to get *build.Package for given path | ||||||
|  | func pkgForPath(path string) *build.Package { | ||||||
|  | 	// get pwd for relative imports | ||||||
|  | 	pwd, err := os.Getwd() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("error getting pwd (required for relative imports): %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// read full package information | ||||||
|  | 	pkg, err := build.Import(path, pwd, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("error reading package: %s\n", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return pkg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func verbosef(format string, stuff ...interface{}) { | ||||||
|  | 	if flags.Verbose { | ||||||
|  | 		log.Printf(format, stuff...) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								vendor/github.com/GeertJohan/go.rice/rice/templates.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								vendor/github.com/GeertJohan/go.rice/rice/templates.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"text/template" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var tmplEmbeddedBox *template.Template | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	// parse embedded box template | ||||||
|  | 	tmplEmbeddedBox, err = template.New("embeddedBox").Parse(`package {{.Package}} | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | {{range .Boxes}} | ||||||
|  | func init() { | ||||||
|  |  | ||||||
|  | 	// define files | ||||||
|  | 	{{range .Files}}{{.Identifier}} := &embedded.EmbeddedFile{ | ||||||
|  | 		Filename:    ` + "`" + `{{.FileName}}` + "`" + `, | ||||||
|  | 		FileModTime: time.Unix({{.ModTime}}, 0), | ||||||
|  | 		Content:     string({{.Content | printf "%q"}}),  | ||||||
|  | 	} | ||||||
|  | 	{{end}} | ||||||
|  |  | ||||||
|  | 	// define dirs | ||||||
|  | 	{{range .Dirs}}{{.Identifier}} := &embedded.EmbeddedDir{ | ||||||
|  | 		Filename:    ` + "`" + `{{.FileName}}` + "`" + `, | ||||||
|  | 		DirModTime: time.Unix({{.ModTime}}, 0), | ||||||
|  | 		ChildFiles:  []*embedded.EmbeddedFile{ | ||||||
|  | 			{{range .ChildFiles}}{{.Identifier}}, // {{.FileName}} | ||||||
|  | 			{{end}} | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	{{end}} | ||||||
|  |  | ||||||
|  | 	// link ChildDirs | ||||||
|  | 	{{range .Dirs}}{{.Identifier}}.ChildDirs = []*embedded.EmbeddedDir{ | ||||||
|  | 		{{range .ChildDirs}}{{.Identifier}}, // {{.FileName}} | ||||||
|  | 		{{end}} | ||||||
|  | 	} | ||||||
|  | 	{{end}} | ||||||
|  |  | ||||||
|  | 	// register embeddedBox | ||||||
|  | 	embedded.RegisterEmbeddedBox(` + "`" + `{{.BoxName}}` + "`" + `, &embedded.EmbeddedBox{ | ||||||
|  | 		Name: ` + "`" + `{{.BoxName}}` + "`" + `, | ||||||
|  | 		Time: time.Unix({{.UnixNow}}, 0), | ||||||
|  | 		Dirs: map[string]*embedded.EmbeddedDir{ | ||||||
|  | 			{{range .Dirs}}"{{.FileName}}": {{.Identifier}}, | ||||||
|  | 			{{end}} | ||||||
|  | 		}, | ||||||
|  | 		Files: map[string]*embedded.EmbeddedFile{ | ||||||
|  | 			{{range .Files}}"{{.FileName}}": {{.Identifier}}, | ||||||
|  | 			{{end}} | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | {{end}}`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Printf("error parsing embedded box template: %s\n", err) | ||||||
|  | 		os.Exit(-1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type embedFileDataType struct { | ||||||
|  | 	Package string | ||||||
|  | 	Boxes   []*boxDataType | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type boxDataType struct { | ||||||
|  | 	BoxName string | ||||||
|  | 	UnixNow int64 | ||||||
|  | 	Files   []*fileDataType | ||||||
|  | 	Dirs    map[string]*dirDataType | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type fileDataType struct { | ||||||
|  | 	Identifier string | ||||||
|  | 	FileName   string | ||||||
|  | 	Content    []byte | ||||||
|  | 	ModTime    int64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type dirDataType struct { | ||||||
|  | 	Identifier string | ||||||
|  | 	FileName   string | ||||||
|  | 	Content    []byte | ||||||
|  | 	ModTime    int64 | ||||||
|  | 	ChildDirs  []*dirDataType | ||||||
|  | 	ChildFiles []*fileDataType | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								vendor/github.com/GeertJohan/go.rice/rice/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/GeertJohan/go.rice/rice/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"math/rand" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // randomString generates a pseudo-random alpha-numeric string with given length. | ||||||
|  | func randomString(length int) string { | ||||||
|  | 	rand.Seed(time.Now().UnixNano()) | ||||||
|  | 	k := make([]rune, length) | ||||||
|  | 	for i := 0; i < length; i++ { | ||||||
|  | 		c := rand.Intn(35) | ||||||
|  | 		if c < 10 { | ||||||
|  | 			c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9') | ||||||
|  | 		} else { | ||||||
|  | 			c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z') | ||||||
|  | 		} | ||||||
|  | 		k[i] = rune(c) | ||||||
|  | 	} | ||||||
|  | 	return string(k) | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								vendor/github.com/GeertJohan/go.rice/rice/writecoff.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/GeertJohan/go.rice/rice/writecoff.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  |  | ||||||
|  | 	"github.com/akavel/rsrc/binutil" | ||||||
|  | 	"github.com/akavel/rsrc/coff" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // copied from github.com/akavel/rsrc | ||||||
|  | // LICENSE: MIT | ||||||
|  | // Copyright 2013-2014 The rsrc Authors. (https://github.com/akavel/rsrc/blob/master/AUTHORS) | ||||||
|  | func writeCoff(coff *coff.Coff, fnameout string) error { | ||||||
|  | 	out, err := os.Create(fnameout) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer out.Close() | ||||||
|  | 	w := binutil.Writer{W: out} | ||||||
|  |  | ||||||
|  | 	// write the resulting file to disk | ||||||
|  | 	binutil.Walk(coff, func(v reflect.Value, path string) error { | ||||||
|  | 		if binutil.Plain(v.Kind()) { | ||||||
|  | 			w.WriteLE(v.Interface()) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		vv, ok := v.Interface().(binutil.SizedReader) | ||||||
|  | 		if ok { | ||||||
|  | 			w.WriteFromSized(vv) | ||||||
|  | 			return binutil.WALK_SKIP | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	if w.Err != nil { | ||||||
|  | 		return fmt.Errorf("Error writing output file: %s", w.Err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								vendor/github.com/GeertJohan/go.rice/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/GeertJohan/go.rice/sort.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import "os" | ||||||
|  |  | ||||||
|  | // SortByName allows an array of os.FileInfo objects | ||||||
|  | // to be easily sorted by filename using sort.Sort(SortByName(array)) | ||||||
|  | type SortByName []os.FileInfo | ||||||
|  |  | ||||||
|  | func (f SortByName) Len() int           { return len(f) } | ||||||
|  | func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() } | ||||||
|  | func (f SortByName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] } | ||||||
|  |  | ||||||
|  | // SortByModified allows an array of os.FileInfo objects | ||||||
|  | // to be easily sorted by modified date using sort.Sort(SortByModified(array)) | ||||||
|  | type SortByModified []os.FileInfo | ||||||
|  |  | ||||||
|  | func (f SortByModified) Len() int           { return len(f) } | ||||||
|  | func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() } | ||||||
|  | func (f SortByModified) Swap(i, j int)      { f[i], f[j] = f[j], f[i] } | ||||||
							
								
								
									
										252
									
								
								vendor/github.com/GeertJohan/go.rice/virtual.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								vendor/github.com/GeertJohan/go.rice/virtual.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"sort" | ||||||
|  |  | ||||||
|  | 	"github.com/GeertJohan/go.rice/embedded" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | //++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File | ||||||
|  |  | ||||||
|  | // Error indicating some function is not implemented yet (but available to satisfy an interface) | ||||||
|  | var ErrNotImplemented = errors.New("not implemented yet") | ||||||
|  |  | ||||||
|  | // virtualFile is a 'stateful' virtual file. | ||||||
|  | // virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'. | ||||||
|  | // virtualFile is only internally visible and should be exposed through rice.File | ||||||
|  | type virtualFile struct { | ||||||
|  | 	*embedded.EmbeddedFile       // the actual embedded file, embedded to obtain methods | ||||||
|  | 	offset                 int64 // read position on the virtual file | ||||||
|  | 	closed                 bool  // closed when true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // create a new virtualFile for given EmbeddedFile | ||||||
|  | func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile { | ||||||
|  | 	vf := &virtualFile{ | ||||||
|  | 		EmbeddedFile: ef, | ||||||
|  | 		offset:       0, | ||||||
|  | 		closed:       false, | ||||||
|  | 	} | ||||||
|  | 	return vf | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid | ||||||
|  |  | ||||||
|  | func (vf *virtualFile) close() error { | ||||||
|  | 	if vf.closed { | ||||||
|  | 		return &os.PathError{ | ||||||
|  | 			Op:   "close", | ||||||
|  | 			Path: vf.EmbeddedFile.Filename, | ||||||
|  | 			Err:  errors.New("already closed"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	vf.EmbeddedFile = nil | ||||||
|  | 	vf.closed = true | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vf *virtualFile) stat() (os.FileInfo, error) { | ||||||
|  | 	if vf.closed { | ||||||
|  | 		return nil, &os.PathError{ | ||||||
|  | 			Op:   "stat", | ||||||
|  | 			Path: vf.EmbeddedFile.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return (*embeddedFileInfo)(vf.EmbeddedFile), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) { | ||||||
|  | 	if vf.closed { | ||||||
|  | 		return nil, &os.PathError{ | ||||||
|  | 			Op:   "readdir", | ||||||
|  | 			Path: vf.EmbeddedFile.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	//TODO: return proper error for a readdir() call on a file | ||||||
|  | 	return nil, ErrNotImplemented | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vf *virtualFile) read(bts []byte) (int, error) { | ||||||
|  | 	if vf.closed { | ||||||
|  | 		return 0, &os.PathError{ | ||||||
|  | 			Op:   "read", | ||||||
|  | 			Path: vf.EmbeddedFile.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	end := vf.offset + int64(len(bts)) | ||||||
|  |  | ||||||
|  | 	if end >= int64(len(vf.Content)) { | ||||||
|  | 		// end of file, so return what we have + EOF | ||||||
|  | 		n := copy(bts, vf.Content[vf.offset:]) | ||||||
|  | 		vf.offset = 0 | ||||||
|  | 		return n, io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	n := copy(bts, vf.Content[vf.offset:end]) | ||||||
|  | 	vf.offset += int64(n) | ||||||
|  | 	return n, nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vf *virtualFile) seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	if vf.closed { | ||||||
|  | 		return 0, &os.PathError{ | ||||||
|  | 			Op:   "seek", | ||||||
|  | 			Path: vf.EmbeddedFile.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var e error | ||||||
|  |  | ||||||
|  | 	//++ TODO: check if this is correct implementation for seek | ||||||
|  | 	switch whence { | ||||||
|  | 	case os.SEEK_SET: | ||||||
|  | 		//++ check if new offset isn't out of bounds, set e when it is, then break out of switch | ||||||
|  | 		vf.offset = offset | ||||||
|  | 	case os.SEEK_CUR: | ||||||
|  | 		//++ check if new offset isn't out of bounds, set e when it is, then break out of switch | ||||||
|  | 		vf.offset += offset | ||||||
|  | 	case os.SEEK_END: | ||||||
|  | 		//++ check if new offset isn't out of bounds, set e when it is, then break out of switch | ||||||
|  | 		vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if e != nil { | ||||||
|  | 		return 0, &os.PathError{ | ||||||
|  | 			Op:   "seek", | ||||||
|  | 			Path: vf.Filename, | ||||||
|  | 			Err:  e, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return vf.offset, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // virtualDir is a 'stateful' virtual directory. | ||||||
|  | // virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'. | ||||||
|  | // virtualDir is only internally visible and should be exposed through rice.File | ||||||
|  | type virtualDir struct { | ||||||
|  | 	*embedded.EmbeddedDir | ||||||
|  | 	offset int // readdir position on the directory | ||||||
|  | 	closed bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // create a new virtualDir for given EmbeddedDir | ||||||
|  | func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir { | ||||||
|  | 	vd := &virtualDir{ | ||||||
|  | 		EmbeddedDir: ed, | ||||||
|  | 		offset:      0, | ||||||
|  | 		closed:      false, | ||||||
|  | 	} | ||||||
|  | 	return vd | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vd *virtualDir) close() error { | ||||||
|  | 	//++ TODO: needs sync mutex? | ||||||
|  | 	if vd.closed { | ||||||
|  | 		return &os.PathError{ | ||||||
|  | 			Op:   "close", | ||||||
|  | 			Path: vd.EmbeddedDir.Filename, | ||||||
|  | 			Err:  errors.New("already closed"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	vd.closed = true | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vd *virtualDir) stat() (os.FileInfo, error) { | ||||||
|  | 	if vd.closed { | ||||||
|  | 		return nil, &os.PathError{ | ||||||
|  | 			Op:   "stat", | ||||||
|  | 			Path: vd.EmbeddedDir.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return (*embeddedDirInfo)(vd.EmbeddedDir), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) { | ||||||
|  |  | ||||||
|  | 	if vd.closed { | ||||||
|  | 		return nil, &os.PathError{ | ||||||
|  | 			Op:   "readdir", | ||||||
|  | 			Path: vd.EmbeddedDir.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Build up the array of our contents | ||||||
|  | 	var files []os.FileInfo | ||||||
|  |  | ||||||
|  | 	// Add the child directories | ||||||
|  | 	for _, child := range vd.ChildDirs { | ||||||
|  | 		child.Filename = filepath.Base(child.Filename) | ||||||
|  | 		files = append(files, (*embeddedDirInfo)(child)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Add the child files | ||||||
|  | 	for _, child := range vd.ChildFiles { | ||||||
|  | 		child.Filename = filepath.Base(child.Filename) | ||||||
|  | 		files = append(files, (*embeddedFileInfo)(child)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Sort it by filename (lexical order) | ||||||
|  | 	sort.Sort(SortByName(files)) | ||||||
|  |  | ||||||
|  | 	// Return all contents if that's what is requested | ||||||
|  | 	if n <= 0 { | ||||||
|  | 		vd.offset = 0 | ||||||
|  | 		return files, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If user has requested past the end of our list | ||||||
|  | 	// return what we can and send an EOF | ||||||
|  | 	if vd.offset+n >= len(files) { | ||||||
|  | 		offset := vd.offset | ||||||
|  | 		vd.offset = 0 | ||||||
|  | 		return files[offset:], io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	offset := vd.offset | ||||||
|  | 	vd.offset += n | ||||||
|  | 	return files[offset : offset+n], nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vd *virtualDir) read(bts []byte) (int, error) { | ||||||
|  | 	if vd.closed { | ||||||
|  | 		return 0, &os.PathError{ | ||||||
|  | 			Op:   "read", | ||||||
|  | 			Path: vd.EmbeddedDir.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, &os.PathError{ | ||||||
|  | 		Op:   "read", | ||||||
|  | 		Path: vd.EmbeddedDir.Filename, | ||||||
|  | 		Err:  errors.New("is a directory"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (vd *virtualDir) seek(offset int64, whence int) (int64, error) { | ||||||
|  | 	if vd.closed { | ||||||
|  | 		return 0, &os.PathError{ | ||||||
|  | 			Op:   "seek", | ||||||
|  | 			Path: vd.EmbeddedDir.Filename, | ||||||
|  | 			Err:  errors.New("bad file descriptor"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, &os.PathError{ | ||||||
|  | 		Op:   "seek", | ||||||
|  | 		Path: vd.Filename, | ||||||
|  | 		Err:  errors.New("is a directory"), | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										122
									
								
								vendor/github.com/GeertJohan/go.rice/walk.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								vendor/github.com/GeertJohan/go.rice/walk.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | package rice | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Walk is like filepath.Walk() | ||||||
|  | // Visit http://golang.org/pkg/path/filepath/#Walk for more information | ||||||
|  | func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error { | ||||||
|  |  | ||||||
|  | 	pathFile, err := b.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer pathFile.Close() | ||||||
|  |  | ||||||
|  | 	pathInfo, err := pathFile.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if b.IsAppended() || b.IsEmbedded() { | ||||||
|  | 		return b.walk(path, pathInfo, walkFn) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We don't have any embedded or appended box so use live filesystem mode | ||||||
|  | 	return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error { | ||||||
|  |  | ||||||
|  | 		// Strip out the box name from the returned paths | ||||||
|  | 		path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator)) | ||||||
|  | 		return walkFn(path, info, err) | ||||||
|  |  | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // walk recursively descends path. | ||||||
|  | // See walk() in $GOROOT/src/pkg/path/filepath/path.go | ||||||
|  | func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { | ||||||
|  |  | ||||||
|  | 	err := walkFn(path, info, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if info.IsDir() && err == filepath.SkipDir { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !info.IsDir() { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	names, err := b.readDirNames(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return walkFn(path, info, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, name := range names { | ||||||
|  |  | ||||||
|  | 		filename := filepath.Join(path, name) | ||||||
|  | 		fileObject, err := b.Open(filename) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer fileObject.Close() | ||||||
|  |  | ||||||
|  | 		fileInfo, err := fileObject.Stat() | ||||||
|  | 		if err != nil { | ||||||
|  | 			if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			err = b.walk(filename, fileInfo, walkFn) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if !fileInfo.IsDir() || err != filepath.SkipDir { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // readDirNames reads the directory named by path and returns a sorted list of directory entries. | ||||||
|  | // See readDirNames() in $GOROOT/pkg/path/filepath/path.go | ||||||
|  | func (b *Box) readDirNames(path string) ([]string, error) { | ||||||
|  |  | ||||||
|  | 	f, err := b.Open(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer f.Close() | ||||||
|  |  | ||||||
|  | 	stat, err := f.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !stat.IsDir() { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	infos, err := f.Readdir(0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var names []string | ||||||
|  |  | ||||||
|  | 	for _, info := range infos { | ||||||
|  | 		names = append(names, info.Name()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sort.Strings(names) | ||||||
|  | 	return names, nil | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								vendor/github.com/Philipp15b/go-steam/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/Philipp15b/go-steam/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | Copyright (c) 2014 The go-steam Authors. All rights reserved. | ||||||
|  |  | ||||||
|  | Redistribution and use in source and binary forms, with or without | ||||||
|  | modification, are permitted provided that the following conditions are | ||||||
|  | met: | ||||||
|  |  | ||||||
|  |    * Redistributions of source code must retain the above copyright | ||||||
|  | notice, this list of conditions and the following disclaimer. | ||||||
|  |    * Redistributions in binary form must reproduce the above | ||||||
|  | copyright notice, this list of conditions and the following disclaimer | ||||||
|  | in the documentation and/or other materials provided with the | ||||||
|  | distribution. | ||||||
|  |    * The names of its contributors may not be used to endorse or promote | ||||||
|  | products derived from this software without specific prior written permission. | ||||||
|  |  | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||||
|  | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||||
|  | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||||
|  | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||||
|  | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||||
|  | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
							
								
								
									
										178
									
								
								vendor/github.com/Philipp15b/go-steam/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								vendor/github.com/Philipp15b/go-steam/auth.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/sha1" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/protobuf" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/steamlang" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/steamid" | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Auth struct { | ||||||
|  | 	client  *Client | ||||||
|  | 	details *LogOnDetails | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SentryHash []byte | ||||||
|  |  | ||||||
|  | type LogOnDetails struct { | ||||||
|  | 	Username       string | ||||||
|  | 	Password       string | ||||||
|  | 	AuthCode       string | ||||||
|  | 	TwoFactorCode  string | ||||||
|  | 	SentryFileHash SentryHash | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Log on with the given details. You must always specify username and | ||||||
|  | // password. For the first login, don't set an authcode or a hash and you'll receive an error | ||||||
|  | // and Steam will send you an authcode. Then you have to login again, this time with the authcode. | ||||||
|  | // Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows | ||||||
|  | // you to login without using an authcode in the future. | ||||||
|  | // | ||||||
|  | // If you don't use Steam Guard, username and password are enough. | ||||||
|  | func (a *Auth) LogOn(details *LogOnDetails) { | ||||||
|  | 	if len(details.Username) == 0 || len(details.Password) == 0 { | ||||||
|  | 		panic("Username and password must be set!") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	logon := new(CMsgClientLogon) | ||||||
|  | 	logon.AccountName = &details.Username | ||||||
|  | 	logon.Password = &details.Password | ||||||
|  | 	if details.AuthCode != "" { | ||||||
|  | 		logon.AuthCode = proto.String(details.AuthCode) | ||||||
|  | 	} | ||||||
|  | 	if details.TwoFactorCode != "" { | ||||||
|  | 		logon.TwoFactorCode = proto.String(details.TwoFactorCode) | ||||||
|  | 	} | ||||||
|  | 	logon.ClientLanguage = proto.String("english") | ||||||
|  | 	logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol) | ||||||
|  | 	logon.ShaSentryfile = details.SentryFileHash | ||||||
|  |  | ||||||
|  | 	atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual)))) | ||||||
|  |  | ||||||
|  | 	a.client.Write(NewClientMsgProtobuf(EMsg_ClientLogon, logon)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) HandlePacket(packet *Packet) { | ||||||
|  | 	switch packet.EMsg { | ||||||
|  | 	case EMsg_ClientLogOnResponse: | ||||||
|  | 		a.handleLogOnResponse(packet) | ||||||
|  | 	case EMsg_ClientNewLoginKey: | ||||||
|  | 		a.handleLoginKey(packet) | ||||||
|  | 	case EMsg_ClientSessionToken: | ||||||
|  | 	case EMsg_ClientLoggedOff: | ||||||
|  | 		a.handleLoggedOff(packet) | ||||||
|  | 	case EMsg_ClientUpdateMachineAuth: | ||||||
|  | 		a.handleUpdateMachineAuth(packet) | ||||||
|  | 	case EMsg_ClientAccountInfo: | ||||||
|  | 		a.handleAccountInfo(packet) | ||||||
|  | 	case EMsg_ClientWalletInfoUpdate: | ||||||
|  | 	case EMsg_ClientRequestWebAPIAuthenticateUserNonceResponse: | ||||||
|  | 	case EMsg_ClientMarketingMessageUpdate: | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) handleLogOnResponse(packet *Packet) { | ||||||
|  | 	if !packet.IsProto { | ||||||
|  | 		a.client.Fatalf("Got non-proto logon response!") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	body := new(CMsgClientLogonResponse) | ||||||
|  | 	msg := packet.ReadProtoMsg(body) | ||||||
|  |  | ||||||
|  | 	result := EResult(body.GetEresult()) | ||||||
|  | 	if result == EResult_OK { | ||||||
|  | 		atomic.StoreInt32(&a.client.sessionId, msg.Header.Proto.GetClientSessionid()) | ||||||
|  | 		atomic.StoreUint64(&a.client.steamId, msg.Header.Proto.GetSteamid()) | ||||||
|  | 		a.client.Web.webLoginKey = *body.WebapiAuthenticateUserNonce | ||||||
|  |  | ||||||
|  | 		go a.client.heartbeatLoop(time.Duration(body.GetOutOfGameHeartbeatSeconds())) | ||||||
|  |  | ||||||
|  | 		a.client.Emit(&LoggedOnEvent{ | ||||||
|  | 			Result:                    EResult(body.GetEresult()), | ||||||
|  | 			ExtendedResult:            EResult(body.GetEresultExtended()), | ||||||
|  | 			OutOfGameSecsPerHeartbeat: body.GetOutOfGameHeartbeatSeconds(), | ||||||
|  | 			InGameSecsPerHeartbeat:    body.GetInGameHeartbeatSeconds(), | ||||||
|  | 			PublicIp:                  body.GetPublicIp(), | ||||||
|  | 			ServerTime:                body.GetRtime32ServerTime(), | ||||||
|  | 			AccountFlags:              EAccountFlags(body.GetAccountFlags()), | ||||||
|  | 			ClientSteamId:             SteamId(body.GetClientSuppliedSteamid()), | ||||||
|  | 			EmailDomain:               body.GetEmailDomain(), | ||||||
|  | 			CellId:                    body.GetCellId(), | ||||||
|  | 			CellIdPingThreshold:       body.GetCellIdPingThreshold(), | ||||||
|  | 			Steam2Ticket:              body.GetSteam2Ticket(), | ||||||
|  | 			UsePics:                   body.GetUsePics(), | ||||||
|  | 			WebApiUserNonce:           body.GetWebapiAuthenticateUserNonce(), | ||||||
|  | 			IpCountryCode:             body.GetIpCountryCode(), | ||||||
|  | 			VanityUrl:                 body.GetVanityUrl(), | ||||||
|  | 			NumLoginFailuresToMigrate: body.GetCountLoginfailuresToMigrate(), | ||||||
|  | 			NumDisconnectsToMigrate:   body.GetCountDisconnectsToMigrate(), | ||||||
|  | 		}) | ||||||
|  | 	} else if result == EResult_Fail || result == EResult_ServiceUnavailable || result == EResult_TryAnotherCM { | ||||||
|  | 		// some error on Steam's side, we'll get an EOF later | ||||||
|  | 	} else { | ||||||
|  | 		a.client.Emit(&LogOnFailedEvent{ | ||||||
|  | 			Result: EResult(body.GetEresult()), | ||||||
|  | 		}) | ||||||
|  | 		a.client.Disconnect() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) handleLoginKey(packet *Packet) { | ||||||
|  | 	body := new(CMsgClientNewLoginKey) | ||||||
|  | 	packet.ReadProtoMsg(body) | ||||||
|  | 	a.client.Write(NewClientMsgProtobuf(EMsg_ClientNewLoginKeyAccepted, &CMsgClientNewLoginKeyAccepted{ | ||||||
|  | 		UniqueId: proto.Uint32(body.GetUniqueId()), | ||||||
|  | 	})) | ||||||
|  | 	a.client.Emit(&LoginKeyEvent{ | ||||||
|  | 		UniqueId: body.GetUniqueId(), | ||||||
|  | 		LoginKey: body.GetLoginKey(), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) handleLoggedOff(packet *Packet) { | ||||||
|  | 	result := EResult_Invalid | ||||||
|  | 	if packet.IsProto { | ||||||
|  | 		body := new(CMsgClientLoggedOff) | ||||||
|  | 		packet.ReadProtoMsg(body) | ||||||
|  | 		result = EResult(body.GetEresult()) | ||||||
|  | 	} else { | ||||||
|  | 		body := new(MsgClientLoggedOff) | ||||||
|  | 		packet.ReadClientMsg(body) | ||||||
|  | 		result = body.Result | ||||||
|  | 	} | ||||||
|  | 	a.client.Emit(&LoggedOffEvent{Result: result}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) handleUpdateMachineAuth(packet *Packet) { | ||||||
|  | 	body := new(CMsgClientUpdateMachineAuth) | ||||||
|  | 	packet.ReadProtoMsg(body) | ||||||
|  | 	hash := sha1.New() | ||||||
|  | 	hash.Write(packet.Data) | ||||||
|  | 	sha := hash.Sum(nil) | ||||||
|  |  | ||||||
|  | 	msg := NewClientMsgProtobuf(EMsg_ClientUpdateMachineAuthResponse, &CMsgClientUpdateMachineAuthResponse{ | ||||||
|  | 		ShaFile: sha, | ||||||
|  | 	}) | ||||||
|  | 	msg.SetTargetJobId(packet.SourceJobId) | ||||||
|  | 	a.client.Write(msg) | ||||||
|  |  | ||||||
|  | 	a.client.Emit(&MachineAuthUpdateEvent{sha}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) handleAccountInfo(packet *Packet) { | ||||||
|  | 	body := new(CMsgClientAccountInfo) | ||||||
|  | 	packet.ReadProtoMsg(body) | ||||||
|  | 	a.client.Emit(&AccountInfoEvent{ | ||||||
|  | 		PersonaName:          body.GetPersonaName(), | ||||||
|  | 		Country:              body.GetIpCountry(), | ||||||
|  | 		CountAuthedComputers: body.GetCountAuthedComputers(), | ||||||
|  | 		AccountFlags:         EAccountFlags(body.GetAccountFlags()), | ||||||
|  | 		FacebookId:           body.GetFacebookId(), | ||||||
|  | 		FacebookName:         body.GetFacebookName(), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								vendor/github.com/Philipp15b/go-steam/auth_events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/Philipp15b/go-steam/auth_events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/steamlang" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/steamid" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type LoggedOnEvent struct { | ||||||
|  | 	Result                    EResult | ||||||
|  | 	ExtendedResult            EResult | ||||||
|  | 	OutOfGameSecsPerHeartbeat int32 | ||||||
|  | 	InGameSecsPerHeartbeat    int32 | ||||||
|  | 	PublicIp                  uint32 | ||||||
|  | 	ServerTime                uint32 | ||||||
|  | 	AccountFlags              EAccountFlags | ||||||
|  | 	ClientSteamId             SteamId `json:",string"` | ||||||
|  | 	EmailDomain               string | ||||||
|  | 	CellId                    uint32 | ||||||
|  | 	CellIdPingThreshold       uint32 | ||||||
|  | 	Steam2Ticket              []byte | ||||||
|  | 	UsePics                   bool | ||||||
|  | 	WebApiUserNonce           string | ||||||
|  | 	IpCountryCode             string | ||||||
|  | 	VanityUrl                 string | ||||||
|  | 	NumLoginFailuresToMigrate int32 | ||||||
|  | 	NumDisconnectsToMigrate   int32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LogOnFailedEvent struct { | ||||||
|  | 	Result EResult | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LoginKeyEvent struct { | ||||||
|  | 	UniqueId uint32 | ||||||
|  | 	LoginKey string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LoggedOffEvent struct { | ||||||
|  | 	Result EResult | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MachineAuthUpdateEvent struct { | ||||||
|  | 	Hash []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AccountInfoEvent struct { | ||||||
|  | 	PersonaName          string | ||||||
|  | 	Country              string | ||||||
|  | 	CountAuthedComputers int32 | ||||||
|  | 	AccountFlags         EAccountFlags | ||||||
|  | 	FacebookId           uint64 `json:",string"` | ||||||
|  | 	FacebookName         string | ||||||
|  | } | ||||||
							
								
								
									
										383
									
								
								vendor/github.com/Philipp15b/go-steam/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								vendor/github.com/Philipp15b/go-steam/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,383 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"compress/gzip" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"fmt" | ||||||
|  | 	"hash/crc32" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/Philipp15b/go-steam/cryptoutil" | ||||||
|  | 	"github.com/Philipp15b/go-steam/netutil" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/protobuf" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/steamlang" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/steamid" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Represents a client to the Steam network. | ||||||
|  | // Always poll events from the channel returned by Events() or receiving messages will stop. | ||||||
|  | // All access, unless otherwise noted, should be threadsafe. | ||||||
|  | // | ||||||
|  | // When a FatalErrorEvent is emitted, the connection is automatically closed. The same client can be used to reconnect. | ||||||
|  | // Other errors don't have any effect. | ||||||
|  | type Client struct { | ||||||
|  | 	// these need to be 64 bit aligned for sync/atomic on 32bit | ||||||
|  | 	sessionId    int32 | ||||||
|  | 	_            uint32 | ||||||
|  | 	steamId      uint64 | ||||||
|  | 	currentJobId uint64 | ||||||
|  |  | ||||||
|  | 	Auth          *Auth | ||||||
|  | 	Social        *Social | ||||||
|  | 	Web           *Web | ||||||
|  | 	Notifications *Notifications | ||||||
|  | 	Trading       *Trading | ||||||
|  | 	GC            *GameCoordinator | ||||||
|  |  | ||||||
|  | 	events        chan interface{} | ||||||
|  | 	handlers      []PacketHandler | ||||||
|  | 	handlersMutex sync.RWMutex | ||||||
|  |  | ||||||
|  | 	tempSessionKey []byte | ||||||
|  |  | ||||||
|  | 	ConnectionTimeout time.Duration | ||||||
|  |  | ||||||
|  | 	mutex     sync.RWMutex // guarding conn and writeChan | ||||||
|  | 	conn      connection | ||||||
|  | 	writeChan chan IMsg | ||||||
|  | 	writeBuf  *bytes.Buffer | ||||||
|  | 	heartbeat *time.Ticker | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type PacketHandler interface { | ||||||
|  | 	HandlePacket(*Packet) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewClient() *Client { | ||||||
|  | 	client := &Client{ | ||||||
|  | 		events:   make(chan interface{}, 3), | ||||||
|  | 		writeBuf: new(bytes.Buffer), | ||||||
|  | 	} | ||||||
|  | 	client.Auth = &Auth{client: client} | ||||||
|  | 	client.RegisterPacketHandler(client.Auth) | ||||||
|  | 	client.Social = newSocial(client) | ||||||
|  | 	client.RegisterPacketHandler(client.Social) | ||||||
|  | 	client.Web = &Web{client: client} | ||||||
|  | 	client.RegisterPacketHandler(client.Web) | ||||||
|  | 	client.Notifications = newNotifications(client) | ||||||
|  | 	client.RegisterPacketHandler(client.Notifications) | ||||||
|  | 	client.Trading = &Trading{client: client} | ||||||
|  | 	client.RegisterPacketHandler(client.Trading) | ||||||
|  | 	client.GC = newGC(client) | ||||||
|  | 	client.RegisterPacketHandler(client.GC) | ||||||
|  | 	return client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get the event channel. By convention all events are pointers, except for errors. | ||||||
|  | // It is never closed. | ||||||
|  | func (c *Client) Events() <-chan interface{} { | ||||||
|  | 	return c.events | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Emit(event interface{}) { | ||||||
|  | 	c.events <- event | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Emits a FatalErrorEvent formatted with fmt.Errorf and disconnects. | ||||||
|  | func (c *Client) Fatalf(format string, a ...interface{}) { | ||||||
|  | 	c.Emit(FatalErrorEvent(fmt.Errorf(format, a...))) | ||||||
|  | 	c.Disconnect() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Emits an error formatted with fmt.Errorf. | ||||||
|  | func (c *Client) Errorf(format string, a ...interface{}) { | ||||||
|  | 	c.Emit(fmt.Errorf(format, a...)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Registers a PacketHandler that receives all incoming packets. | ||||||
|  | func (c *Client) RegisterPacketHandler(handler PacketHandler) { | ||||||
|  | 	c.handlersMutex.Lock() | ||||||
|  | 	defer c.handlersMutex.Unlock() | ||||||
|  | 	c.handlers = append(c.handlers, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) GetNextJobId() JobId { | ||||||
|  | 	return JobId(atomic.AddUint64(&c.currentJobId, 1)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) SteamId() SteamId { | ||||||
|  | 	return SteamId(atomic.LoadUint64(&c.steamId)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) SessionId() int32 { | ||||||
|  | 	return atomic.LoadInt32(&c.sessionId) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Connected() bool { | ||||||
|  | 	c.mutex.RLock() | ||||||
|  | 	defer c.mutex.RUnlock() | ||||||
|  | 	return c.conn != nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Connects to a random Steam server and returns its address. | ||||||
|  | // If this client is already connected, it is disconnected first. | ||||||
|  | // This method tries to use an address from the Steam Directory and falls | ||||||
|  | // back to the built-in server list if the Steam Directory can't be reached. | ||||||
|  | // If you want to connect to a specific server, use `ConnectTo`. | ||||||
|  | func (c *Client) Connect() *netutil.PortAddr { | ||||||
|  | 	var server *netutil.PortAddr | ||||||
|  | 	if steamDirectoryCache.IsInitialized() { | ||||||
|  | 		server = steamDirectoryCache.GetRandomCM() | ||||||
|  | 	} else { | ||||||
|  | 		server = GetRandomCM() | ||||||
|  | 	} | ||||||
|  | 	c.ConnectTo(server) | ||||||
|  | 	return server | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Connects to a specific server. | ||||||
|  | // You may want to use one of the `GetRandom*CM()` functions in this package. | ||||||
|  | // If this client is already connected, it is disconnected first. | ||||||
|  | func (c *Client) ConnectTo(addr *netutil.PortAddr) { | ||||||
|  | 	c.ConnectToBind(addr, nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Connects to a specific server, and binds to a specified local IP | ||||||
|  | // If this client is already connected, it is disconnected first. | ||||||
|  | func (c *Client) ConnectToBind(addr *netutil.PortAddr, local *net.TCPAddr) { | ||||||
|  | 	c.Disconnect() | ||||||
|  |  | ||||||
|  | 	conn, err := dialTCP(local, addr.ToTCPAddr()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		c.Fatalf("Connect failed: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.conn = conn | ||||||
|  | 	c.writeChan = make(chan IMsg, 5) | ||||||
|  |  | ||||||
|  | 	go c.readLoop() | ||||||
|  | 	go c.writeLoop() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) Disconnect() { | ||||||
|  | 	c.mutex.Lock() | ||||||
|  | 	defer c.mutex.Unlock() | ||||||
|  |  | ||||||
|  | 	if c.conn == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.conn.Close() | ||||||
|  | 	c.conn = nil | ||||||
|  | 	if c.heartbeat != nil { | ||||||
|  | 		c.heartbeat.Stop() | ||||||
|  | 	} | ||||||
|  | 	close(c.writeChan) | ||||||
|  | 	c.Emit(&DisconnectedEvent{}) | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Adds a message to the send queue. Modifications to the given message after | ||||||
|  | // writing are not allowed (possible race conditions). | ||||||
|  | // | ||||||
|  | // Writes to this client when not connected are ignored. | ||||||
|  | func (c *Client) Write(msg IMsg) { | ||||||
|  | 	if cm, ok := msg.(IClientMsg); ok { | ||||||
|  | 		cm.SetSessionId(c.SessionId()) | ||||||
|  | 		cm.SetSteamId(c.SteamId()) | ||||||
|  | 	} | ||||||
|  | 	c.mutex.RLock() | ||||||
|  | 	defer c.mutex.RUnlock() | ||||||
|  | 	if c.conn == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.writeChan <- msg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) readLoop() { | ||||||
|  | 	for { | ||||||
|  | 		// This *should* be atomic on most platforms, but the Go spec doesn't guarantee it | ||||||
|  | 		c.mutex.RLock() | ||||||
|  | 		conn := c.conn | ||||||
|  | 		c.mutex.RUnlock() | ||||||
|  | 		if conn == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		packet, err := conn.Read() | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Fatalf("Error reading from the connection: %v", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		c.handlePacket(packet) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) writeLoop() { | ||||||
|  | 	for { | ||||||
|  | 		c.mutex.RLock() | ||||||
|  | 		conn := c.conn | ||||||
|  | 		c.mutex.RUnlock() | ||||||
|  | 		if conn == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		msg, ok := <-c.writeChan | ||||||
|  | 		if !ok { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		err := msg.Serialize(c.writeBuf) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.writeBuf.Reset() | ||||||
|  | 			c.Fatalf("Error serializing message %v: %v", msg, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		err = conn.Write(c.writeBuf.Bytes()) | ||||||
|  |  | ||||||
|  | 		c.writeBuf.Reset() | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Fatalf("Error writing message %v: %v", msg, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) heartbeatLoop(seconds time.Duration) { | ||||||
|  | 	if c.heartbeat != nil { | ||||||
|  | 		c.heartbeat.Stop() | ||||||
|  | 	} | ||||||
|  | 	c.heartbeat = time.NewTicker(seconds * time.Second) | ||||||
|  | 	for { | ||||||
|  | 		_, ok := <-c.heartbeat.C | ||||||
|  | 		if !ok { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		c.Write(NewClientMsgProtobuf(EMsg_ClientHeartBeat, new(CMsgClientHeartBeat))) | ||||||
|  | 	} | ||||||
|  | 	c.heartbeat = nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) handlePacket(packet *Packet) { | ||||||
|  | 	switch packet.EMsg { | ||||||
|  | 	case EMsg_ChannelEncryptRequest: | ||||||
|  | 		c.handleChannelEncryptRequest(packet) | ||||||
|  | 	case EMsg_ChannelEncryptResult: | ||||||
|  | 		c.handleChannelEncryptResult(packet) | ||||||
|  | 	case EMsg_Multi: | ||||||
|  | 		c.handleMulti(packet) | ||||||
|  | 	case EMsg_ClientCMList: | ||||||
|  | 		c.handleClientCMList(packet) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.handlersMutex.RLock() | ||||||
|  | 	defer c.handlersMutex.RUnlock() | ||||||
|  | 	for _, handler := range c.handlers { | ||||||
|  | 		handler.HandlePacket(packet) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) handleChannelEncryptRequest(packet *Packet) { | ||||||
|  | 	body := NewMsgChannelEncryptRequest() | ||||||
|  | 	packet.ReadMsg(body) | ||||||
|  |  | ||||||
|  | 	if body.Universe != EUniverse_Public { | ||||||
|  | 		c.Fatalf("Invalid univserse %v!", body.Universe) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.tempSessionKey = make([]byte, 32) | ||||||
|  | 	rand.Read(c.tempSessionKey) | ||||||
|  | 	encryptedKey := cryptoutil.RSAEncrypt(GetPublicKey(EUniverse_Public), c.tempSessionKey) | ||||||
|  |  | ||||||
|  | 	payload := new(bytes.Buffer) | ||||||
|  | 	payload.Write(encryptedKey) | ||||||
|  | 	binary.Write(payload, binary.LittleEndian, crc32.ChecksumIEEE(encryptedKey)) | ||||||
|  | 	payload.WriteByte(0) | ||||||
|  | 	payload.WriteByte(0) | ||||||
|  | 	payload.WriteByte(0) | ||||||
|  | 	payload.WriteByte(0) | ||||||
|  |  | ||||||
|  | 	c.Write(NewMsg(NewMsgChannelEncryptResponse(), payload.Bytes())) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) handleChannelEncryptResult(packet *Packet) { | ||||||
|  | 	body := NewMsgChannelEncryptResult() | ||||||
|  | 	packet.ReadMsg(body) | ||||||
|  |  | ||||||
|  | 	if body.Result != EResult_OK { | ||||||
|  | 		c.Fatalf("Encryption failed: %v", body.Result) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	c.conn.SetEncryptionKey(c.tempSessionKey) | ||||||
|  | 	c.tempSessionKey = nil | ||||||
|  |  | ||||||
|  | 	c.Emit(&ConnectedEvent{}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) handleMulti(packet *Packet) { | ||||||
|  | 	body := new(CMsgMulti) | ||||||
|  | 	packet.ReadProtoMsg(body) | ||||||
|  |  | ||||||
|  | 	payload := body.GetMessageBody() | ||||||
|  |  | ||||||
|  | 	if body.GetSizeUnzipped() > 0 { | ||||||
|  | 		r, err := gzip.NewReader(bytes.NewReader(payload)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Errorf("handleMulti: Error while decompressing: %v", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		payload, err = ioutil.ReadAll(r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Errorf("handleMulti: Error while decompressing: %v", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pr := bytes.NewReader(payload) | ||||||
|  | 	for pr.Len() > 0 { | ||||||
|  | 		var length uint32 | ||||||
|  | 		binary.Read(pr, binary.LittleEndian, &length) | ||||||
|  | 		packetData := make([]byte, length) | ||||||
|  | 		pr.Read(packetData) | ||||||
|  | 		p, err := NewPacket(packetData) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.Errorf("Error reading packet in Multi msg %v: %v", packet, err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		c.handlePacket(p) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) handleClientCMList(packet *Packet) { | ||||||
|  | 	body := new(CMsgClientCMList) | ||||||
|  | 	packet.ReadProtoMsg(body) | ||||||
|  |  | ||||||
|  | 	l := make([]*netutil.PortAddr, 0) | ||||||
|  | 	for i, ip := range body.GetCmAddresses() { | ||||||
|  | 		l = append(l, &netutil.PortAddr{ | ||||||
|  | 			readIp(ip), | ||||||
|  | 			uint16(body.GetCmPorts()[i]), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Emit(&ClientCMListEvent{l}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func readIp(ip uint32) net.IP { | ||||||
|  | 	r := make(net.IP, 4) | ||||||
|  | 	r[3] = byte(ip) | ||||||
|  | 	r[2] = byte(ip >> 8) | ||||||
|  | 	r[1] = byte(ip >> 16) | ||||||
|  | 	r[0] = byte(ip >> 24) | ||||||
|  | 	return r | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/Philipp15b/go-steam/client_events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/Philipp15b/go-steam/client_events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/Philipp15b/go-steam/netutil" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // When this event is emitted by the Client, the connection is automatically closed. | ||||||
|  | // This may be caused by a network error, for example. | ||||||
|  | type FatalErrorEvent error | ||||||
|  |  | ||||||
|  | type ConnectedEvent struct{} | ||||||
|  |  | ||||||
|  | type DisconnectedEvent struct{} | ||||||
|  |  | ||||||
|  | // A list of connection manager addresses to connect to in the future. | ||||||
|  | // You should always save them and then select one of these | ||||||
|  | // instead of the builtin ones for the next connection. | ||||||
|  | type ClientCMListEvent struct { | ||||||
|  | 	Addresses []*netutil.PortAddr | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								vendor/github.com/Philipp15b/go-steam/community/community.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/Philipp15b/go-steam/community/community.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | package community | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/cookiejar" | ||||||
|  | 	"net/url" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const cookiePath = "https://steamcommunity.com/" | ||||||
|  |  | ||||||
|  | func SetCookies(client *http.Client, sessionId, steamLogin, steamLoginSecure string) { | ||||||
|  | 	if client.Jar == nil { | ||||||
|  | 		client.Jar, _ = cookiejar.New(new(cookiejar.Options)) | ||||||
|  | 	} | ||||||
|  | 	base, err := url.Parse(cookiePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	client.Jar.SetCookies(base, []*http.Cookie{ | ||||||
|  | 		// It seems that, for some reason, Steam tries to URL-decode the cookie. | ||||||
|  | 		&http.Cookie{ | ||||||
|  | 			Name:  "sessionid", | ||||||
|  | 			Value: url.QueryEscape(sessionId), | ||||||
|  | 		}, | ||||||
|  | 		// steamLogin is already URL-encoded. | ||||||
|  | 		&http.Cookie{ | ||||||
|  | 			Name:  "steamLogin", | ||||||
|  | 			Value: steamLogin, | ||||||
|  | 		}, | ||||||
|  | 		&http.Cookie{ | ||||||
|  | 			Name:  "steamLoginSecure", | ||||||
|  | 			Value: steamLoginSecure, | ||||||
|  | 		}, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										127
									
								
								vendor/github.com/Philipp15b/go-steam/connection.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								vendor/github.com/Philipp15b/go-steam/connection.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/aes" | ||||||
|  | 	"crypto/cipher" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	"github.com/Philipp15b/go-steam/cryptoutil" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type connection interface { | ||||||
|  | 	Read() (*Packet, error) | ||||||
|  | 	Write([]byte) error | ||||||
|  | 	Close() error | ||||||
|  | 	SetEncryptionKey([]byte) | ||||||
|  | 	IsEncrypted() bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const tcpConnectionMagic uint32 = 0x31305456 // "VT01" | ||||||
|  |  | ||||||
|  | type tcpConnection struct { | ||||||
|  | 	conn        *net.TCPConn | ||||||
|  | 	ciph        cipher.Block | ||||||
|  | 	cipherMutex sync.RWMutex | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func dialTCP(laddr, raddr *net.TCPAddr) (*tcpConnection, error) { | ||||||
|  | 	conn, err := net.DialTCP("tcp", laddr, raddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &tcpConnection{ | ||||||
|  | 		conn: conn, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *tcpConnection) Read() (*Packet, error) { | ||||||
|  | 	// All packets begin with a packet length | ||||||
|  | 	var packetLen uint32 | ||||||
|  | 	err := binary.Read(c.conn, binary.LittleEndian, &packetLen) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// A magic value follows for validation | ||||||
|  | 	var packetMagic uint32 | ||||||
|  | 	err = binary.Read(c.conn, binary.LittleEndian, &packetMagic) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if packetMagic != tcpConnectionMagic { | ||||||
|  | 		return nil, fmt.Errorf("Invalid connection magic! Expected %d, got %d!", tcpConnectionMagic, packetMagic) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf := make([]byte, packetLen, packetLen) | ||||||
|  | 	_, err = io.ReadFull(c.conn, buf) | ||||||
|  | 	if err == io.ErrUnexpectedEOF { | ||||||
|  | 		return nil, io.EOF | ||||||
|  | 	} | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Packets after ChannelEncryptResult are encrypted | ||||||
|  | 	c.cipherMutex.RLock() | ||||||
|  | 	if c.ciph != nil { | ||||||
|  | 		buf = cryptoutil.SymmetricDecrypt(c.ciph, buf) | ||||||
|  | 	} | ||||||
|  | 	c.cipherMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	return NewPacket(buf) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Writes a message. This may only be used by one goroutine at a time. | ||||||
|  | func (c *tcpConnection) Write(message []byte) error { | ||||||
|  | 	c.cipherMutex.RLock() | ||||||
|  | 	if c.ciph != nil { | ||||||
|  | 		message = cryptoutil.SymmetricEncrypt(c.ciph, message) | ||||||
|  | 	} | ||||||
|  | 	c.cipherMutex.RUnlock() | ||||||
|  |  | ||||||
|  | 	err := binary.Write(c.conn, binary.LittleEndian, uint32(len(message))) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = binary.Write(c.conn, binary.LittleEndian, tcpConnectionMagic) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = c.conn.Write(message) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *tcpConnection) Close() error { | ||||||
|  | 	return c.conn.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *tcpConnection) SetEncryptionKey(key []byte) { | ||||||
|  | 	c.cipherMutex.Lock() | ||||||
|  | 	defer c.cipherMutex.Unlock() | ||||||
|  | 	if key == nil { | ||||||
|  | 		c.ciph = nil | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if len(key) != 32 { | ||||||
|  | 		panic("Connection AES key is not 32 bytes long!") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	c.ciph, err = aes.NewCipher(key) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *tcpConnection) IsEncrypted() bool { | ||||||
|  | 	c.cipherMutex.RLock() | ||||||
|  | 	defer c.cipherMutex.RUnlock() | ||||||
|  | 	return c.ciph != nil | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/cryptoutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/cryptoutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | package cryptoutil | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/aes" | ||||||
|  | 	"crypto/cipher" | ||||||
|  | 	"crypto/rand" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Performs an encryption using AES/CBC/PKCS7 | ||||||
|  | // with a random IV prepended using AES/ECB/None. | ||||||
|  | func SymmetricEncrypt(ciph cipher.Block, src []byte) []byte { | ||||||
|  | 	// get a random IV and ECB encrypt it | ||||||
|  | 	iv := make([]byte, aes.BlockSize, aes.BlockSize) | ||||||
|  | 	_, err := rand.Read(iv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	encryptedIv := make([]byte, aes.BlockSize, aes.BlockSize) | ||||||
|  | 	newECBEncrypter(ciph).CryptBlocks(encryptedIv, iv) | ||||||
|  |  | ||||||
|  | 	// pad it, copy the IV to the first 16 bytes and encrypt the rest with CBC | ||||||
|  | 	encrypted := padPKCS7WithIV(src) | ||||||
|  | 	copy(encrypted, encryptedIv) | ||||||
|  | 	cipher.NewCBCEncrypter(ciph, iv).CryptBlocks(encrypted[aes.BlockSize:], encrypted[aes.BlockSize:]) | ||||||
|  | 	return encrypted | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Decrypts data from the reader using AES/CBC/PKCS7 with an IV | ||||||
|  | // prepended using AES/ECB/None. The src slice may not be used anymore. | ||||||
|  | func SymmetricDecrypt(ciph cipher.Block, src []byte) []byte { | ||||||
|  | 	iv := src[:aes.BlockSize] | ||||||
|  | 	newECBDecrypter(ciph).CryptBlocks(iv, iv) | ||||||
|  |  | ||||||
|  | 	data := src[aes.BlockSize:] | ||||||
|  | 	cipher.NewCBCDecrypter(ciph, iv).CryptBlocks(data, data) | ||||||
|  |  | ||||||
|  | 	return unpadPKCS7(data) | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/ecb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/ecb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | package cryptoutil | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/cipher" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // From this code review: https://codereview.appspot.com/7860047/ | ||||||
|  | // by fasmat for the Go crypto/cipher package | ||||||
|  |  | ||||||
|  | type ecb struct { | ||||||
|  | 	b         cipher.Block | ||||||
|  | 	blockSize int | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newECB(b cipher.Block) *ecb { | ||||||
|  | 	return &ecb{ | ||||||
|  | 		b:         b, | ||||||
|  | 		blockSize: b.BlockSize(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ecbEncrypter ecb | ||||||
|  |  | ||||||
|  | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book | ||||||
|  | // mode, using the given Block. | ||||||
|  | func newECBEncrypter(b cipher.Block) cipher.BlockMode { | ||||||
|  | 	return (*ecbEncrypter)(newECB(b)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ecbEncrypter) BlockSize() int { return x.blockSize } | ||||||
|  |  | ||||||
|  | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { | ||||||
|  | 	if len(src)%x.blockSize != 0 { | ||||||
|  | 		panic("cryptoutil/ecb: input not full blocks") | ||||||
|  | 	} | ||||||
|  | 	if len(dst) < len(src) { | ||||||
|  | 		panic("cryptoutil/ecb: output smaller than input") | ||||||
|  | 	} | ||||||
|  | 	for len(src) > 0 { | ||||||
|  | 		x.b.Encrypt(dst, src[:x.blockSize]) | ||||||
|  | 		src = src[x.blockSize:] | ||||||
|  | 		dst = dst[x.blockSize:] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ecbDecrypter ecb | ||||||
|  |  | ||||||
|  | // newECBDecrypter returns a BlockMode which decrypts in electronic code book | ||||||
|  | // mode, using the given Block. | ||||||
|  | func newECBDecrypter(b cipher.Block) cipher.BlockMode { | ||||||
|  | 	return (*ecbDecrypter)(newECB(b)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x *ecbDecrypter) BlockSize() int { return x.blockSize } | ||||||
|  |  | ||||||
|  | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { | ||||||
|  | 	if len(src)%x.blockSize != 0 { | ||||||
|  | 		panic("cryptoutil/ecb: input not full blocks") | ||||||
|  | 	} | ||||||
|  | 	if len(dst) < len(src) { | ||||||
|  | 		panic("cryptoutil/ecb: output smaller than input") | ||||||
|  | 	} | ||||||
|  | 	for len(src) > 0 { | ||||||
|  | 		x.b.Decrypt(dst, src[:x.blockSize]) | ||||||
|  | 		src = src[x.blockSize:] | ||||||
|  | 		dst = dst[x.blockSize:] | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/pkcs7.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/pkcs7.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | package cryptoutil | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/aes" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Returns a new byte array padded with PKCS7 and prepended | ||||||
|  | // with empty space of the AES block size (16 bytes) for the IV. | ||||||
|  | func padPKCS7WithIV(src []byte) []byte { | ||||||
|  | 	missing := aes.BlockSize - (len(src) % aes.BlockSize) | ||||||
|  | 	newSize := len(src) + aes.BlockSize + missing | ||||||
|  | 	dest := make([]byte, newSize, newSize) | ||||||
|  | 	copy(dest[aes.BlockSize:], src) | ||||||
|  |  | ||||||
|  | 	padding := byte(missing) | ||||||
|  | 	for i := newSize - missing; i < newSize; i++ { | ||||||
|  | 		dest[i] = padding | ||||||
|  | 	} | ||||||
|  | 	return dest | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func unpadPKCS7(src []byte) []byte { | ||||||
|  | 	padLen := src[len(src)-1] | ||||||
|  | 	return src[:len(src)-int(padLen)] | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/rsa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/Philipp15b/go-steam/cryptoutil/rsa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | package cryptoutil | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/rsa" | ||||||
|  | 	"crypto/sha1" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"errors" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Parses a DER encoded RSA public key | ||||||
|  | func ParseASN1RSAPublicKey(derBytes []byte) (*rsa.PublicKey, error) { | ||||||
|  | 	key, err := x509.ParsePKIXPublicKey(derBytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	pubKey, ok := key.(*rsa.PublicKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, errors.New("not an RSA public key") | ||||||
|  | 	} | ||||||
|  | 	return pubKey, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Encrypts a message with the given public key using RSA-OAEP and the sha1 hash function. | ||||||
|  | func RSAEncrypt(pub *rsa.PublicKey, msg []byte) []byte { | ||||||
|  | 	b, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, pub, msg, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								vendor/github.com/Philipp15b/go-steam/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vendor/github.com/Philipp15b/go-steam/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | /* | ||||||
|  | This package allows you to automate actions on Valve's Steam network. It is a Go port of SteamKit. | ||||||
|  |  | ||||||
|  | To login, you'll have to create a new Client first. Then connect to the Steam network | ||||||
|  | and wait for a ConnectedCallback. Then you may call the Login method in the Auth module | ||||||
|  | with your login information. This is covered in more detail in the method's documentation. After you've | ||||||
|  | received the LoggedOnEvent, you should set your persona state to online to receive friend lists etc. | ||||||
|  |  | ||||||
|  | Example code | ||||||
|  |  | ||||||
|  | You can also find a running example in the `gsbot` package. | ||||||
|  |  | ||||||
|  | 	package main | ||||||
|  |  | ||||||
|  | 	import ( | ||||||
|  | 		"io/ioutil" | ||||||
|  | 		"log" | ||||||
|  |  | ||||||
|  | 		"github.com/Philipp15b/go-steam" | ||||||
|  | 		"github.com/Philipp15b/go-steam/protocol/steamlang" | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	func main() { | ||||||
|  | 		myLoginInfo := new(steam.LogOnDetails) | ||||||
|  | 		myLoginInfo.Username = "Your username" | ||||||
|  | 		myLoginInfo.Password = "Your password" | ||||||
|  |  | ||||||
|  | 		client := steam.NewClient() | ||||||
|  | 		client.Connect() | ||||||
|  | 		for event := range client.Events() { | ||||||
|  | 			switch e := event.(type) { | ||||||
|  | 			case *steam.ConnectedEvent: | ||||||
|  | 				client.Auth.LogOn(myLoginInfo) | ||||||
|  | 			case *steam.MachineAuthUpdateEvent: | ||||||
|  | 				ioutil.WriteFile("sentry", e.Hash, 0666) | ||||||
|  | 			case *steam.LoggedOnEvent: | ||||||
|  | 				client.Social.SetPersonaState(steamlang.EPersonaState_Online) | ||||||
|  | 			case steam.FatalErrorEvent: | ||||||
|  | 				log.Print(e) | ||||||
|  | 			case error: | ||||||
|  | 				log.Print(e) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Events | ||||||
|  |  | ||||||
|  | go-steam emits events that can be read via Client.Events(). Although the channel has the type interface{}, | ||||||
|  | only types from this package ending with "Event" and errors will be emitted. | ||||||
|  |  | ||||||
|  | */ | ||||||
|  | package steam | ||||||
							
								
								
									
										3651
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/base.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3651
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/base.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										18413
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_client.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18413
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_client.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6123
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_client_fantasy.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6123
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_client_fantasy.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										10997
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_common.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10997
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/dota_common.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										4441
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/econ.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4441
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/econ.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1825
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/gcsdk.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1825
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/gcsdk.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										579
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/system.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										579
									
								
								vendor/github.com/Philipp15b/go-steam/dota/protocol/protobuf/system.pb.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,579 @@ | |||||||
|  | // Code generated by protoc-gen-go. | ||||||
|  | // source: gcsystemmsgs.proto | ||||||
|  | // DO NOT EDIT! | ||||||
|  |  | ||||||
|  | package protobuf | ||||||
|  |  | ||||||
|  | import proto "github.com/golang/protobuf/proto" | ||||||
|  | import fmt "fmt" | ||||||
|  | import math "math" | ||||||
|  |  | ||||||
|  | // Reference imports to suppress errors if they are not otherwise used. | ||||||
|  | var _ = proto.Marshal | ||||||
|  | var _ = fmt.Errorf | ||||||
|  | var _ = math.Inf | ||||||
|  |  | ||||||
|  | // This is a compile-time assertion to ensure that this generated file | ||||||
|  | // is compatible with the proto package protobuf is being compiled against. | ||||||
|  | const _ = proto.ProtoPackageIsVersion1 | ||||||
|  |  | ||||||
|  | type EGCSystemMsg int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	EGCSystemMsg_k_EGCMsgInvalid                           EGCSystemMsg = 0 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMulti                             EGCSystemMsg = 1 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGenericReply                      EGCSystemMsg = 10 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSystemBase                        EGCSystemMsg = 50 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgAchievementAwarded                EGCSystemMsg = 51 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgConCommand                        EGCSystemMsg = 52 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgStartPlaying                      EGCSystemMsg = 53 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgStopPlaying                       EGCSystemMsg = 54 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgStartGameserver                   EGCSystemMsg = 55 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgStopGameserver                    EGCSystemMsg = 56 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWGRequest                         EGCSystemMsg = 57 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWGResponse                        EGCSystemMsg = 58 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetUserGameStatsSchema            EGCSystemMsg = 59 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetUserGameStatsSchemaResponse    EGCSystemMsg = 60 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetUserStatsDEPRECATED            EGCSystemMsg = 61 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetUserStatsResponse              EGCSystemMsg = 62 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgAppInfoUpdated                    EGCSystemMsg = 63 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgValidateSession                   EGCSystemMsg = 64 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgValidateSessionResponse           EGCSystemMsg = 65 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgLookupAccountFromInput            EGCSystemMsg = 66 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSendHTTPRequest                   EGCSystemMsg = 67 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSendHTTPRequestResponse           EGCSystemMsg = 68 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgPreTestSetup                      EGCSystemMsg = 69 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgRecordSupportAction               EGCSystemMsg = 70 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetAccountDetails_DEPRECATED      EGCSystemMsg = 71 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgReceiveInterAppMessage            EGCSystemMsg = 73 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgFindAccounts                      EGCSystemMsg = 74 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgPostAlert                         EGCSystemMsg = 75 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetLicenses                       EGCSystemMsg = 76 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetUserStats                      EGCSystemMsg = 77 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetCommands                       EGCSystemMsg = 78 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetCommandsResponse               EGCSystemMsg = 79 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgAddFreeLicense                    EGCSystemMsg = 80 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgAddFreeLicenseResponse            EGCSystemMsg = 81 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetIPLocation                     EGCSystemMsg = 82 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetIPLocationResponse             EGCSystemMsg = 83 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSystemStatsSchema                 EGCSystemMsg = 84 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetSystemStats                    EGCSystemMsg = 85 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetSystemStatsResponse            EGCSystemMsg = 86 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSendEmail                         EGCSystemMsg = 87 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSendEmailResponse                 EGCSystemMsg = 88 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetEmailTemplate                  EGCSystemMsg = 89 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetEmailTemplateResponse          EGCSystemMsg = 90 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGrantGuestPass                    EGCSystemMsg = 91 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGrantGuestPassResponse            EGCSystemMsg = 92 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetAccountDetails                 EGCSystemMsg = 93 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetAccountDetailsResponse         EGCSystemMsg = 94 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPersonaNames                   EGCSystemMsg = 95 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPersonaNamesResponse           EGCSystemMsg = 96 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMultiplexMsg                      EGCSystemMsg = 97 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWebAPIRegisterInterfaces          EGCSystemMsg = 101 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWebAPIJobRequest                  EGCSystemMsg = 102 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWebAPIJobRequestHttpResponse      EGCSystemMsg = 104 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgWebAPIJobRequestForwardResponse   EGCSystemMsg = 105 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedGet                      EGCSystemMsg = 200 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedGetResponse              EGCSystemMsg = 201 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedSet                      EGCSystemMsg = 202 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedDelete                   EGCSystemMsg = 203 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedStats                    EGCSystemMsg = 204 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMemCachedStatsResponse            EGCSystemMsg = 205 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSQLStats                          EGCSystemMsg = 210 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSQLStatsResponse                  EGCSystemMsg = 211 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetDirectory                EGCSystemMsg = 220 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetDirectoryResponse        EGCSystemMsg = 221 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetWebAPIRouting            EGCSystemMsg = 222 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetWebAPIRoutingResponse    EGCSystemMsg = 223 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetClientMsgRouting         EGCSystemMsg = 224 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgMasterSetClientMsgRoutingResponse EGCSystemMsg = 225 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSetOptions                        EGCSystemMsg = 226 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSetOptionsResponse                EGCSystemMsg = 227 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgSystemBase2                       EGCSystemMsg = 500 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPurchaseTrustStatus            EGCSystemMsg = 501 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPurchaseTrustStatusResponse    EGCSystemMsg = 502 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgUpdateSession                     EGCSystemMsg = 503 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGCAccountVacStatusChange          EGCSystemMsg = 504 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgCheckFriendship                   EGCSystemMsg = 505 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgCheckFriendshipResponse           EGCSystemMsg = 506 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPartnerAccountLink             EGCSystemMsg = 507 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetPartnerAccountLinkResponse     EGCSystemMsg = 508 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgVSReportedSuspiciousActivity      EGCSystemMsg = 509 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgDPPartnerMicroTxns                EGCSystemMsg = 512 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgDPPartnerMicroTxnsResponse        EGCSystemMsg = 513 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetIPASN                          EGCSystemMsg = 514 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetIPASNResponse                  EGCSystemMsg = 515 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetAppFriendsList                 EGCSystemMsg = 516 | ||||||
|  | 	EGCSystemMsg_k_EGCMsgGetAppFriendsListResponse         EGCSystemMsg = 517 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var EGCSystemMsg_name = map[int32]string{ | ||||||
|  | 	0:   "k_EGCMsgInvalid", | ||||||
|  | 	1:   "k_EGCMsgMulti", | ||||||
|  | 	10:  "k_EGCMsgGenericReply", | ||||||
|  | 	50:  "k_EGCMsgSystemBase", | ||||||
|  | 	51:  "k_EGCMsgAchievementAwarded", | ||||||
|  | 	52:  "k_EGCMsgConCommand", | ||||||
|  | 	53:  "k_EGCMsgStartPlaying", | ||||||
|  | 	54:  "k_EGCMsgStopPlaying", | ||||||
|  | 	55:  "k_EGCMsgStartGameserver", | ||||||
|  | 	56:  "k_EGCMsgStopGameserver", | ||||||
|  | 	57:  "k_EGCMsgWGRequest", | ||||||
|  | 	58:  "k_EGCMsgWGResponse", | ||||||
|  | 	59:  "k_EGCMsgGetUserGameStatsSchema", | ||||||
|  | 	60:  "k_EGCMsgGetUserGameStatsSchemaResponse", | ||||||
|  | 	61:  "k_EGCMsgGetUserStatsDEPRECATED", | ||||||
|  | 	62:  "k_EGCMsgGetUserStatsResponse", | ||||||
|  | 	63:  "k_EGCMsgAppInfoUpdated", | ||||||
|  | 	64:  "k_EGCMsgValidateSession", | ||||||
|  | 	65:  "k_EGCMsgValidateSessionResponse", | ||||||
|  | 	66:  "k_EGCMsgLookupAccountFromInput", | ||||||
|  | 	67:  "k_EGCMsgSendHTTPRequest", | ||||||
|  | 	68:  "k_EGCMsgSendHTTPRequestResponse", | ||||||
|  | 	69:  "k_EGCMsgPreTestSetup", | ||||||
|  | 	70:  "k_EGCMsgRecordSupportAction", | ||||||
|  | 	71:  "k_EGCMsgGetAccountDetails_DEPRECATED", | ||||||
|  | 	73:  "k_EGCMsgReceiveInterAppMessage", | ||||||
|  | 	74:  "k_EGCMsgFindAccounts", | ||||||
|  | 	75:  "k_EGCMsgPostAlert", | ||||||
|  | 	76:  "k_EGCMsgGetLicenses", | ||||||
|  | 	77:  "k_EGCMsgGetUserStats", | ||||||
|  | 	78:  "k_EGCMsgGetCommands", | ||||||
|  | 	79:  "k_EGCMsgGetCommandsResponse", | ||||||
|  | 	80:  "k_EGCMsgAddFreeLicense", | ||||||
|  | 	81:  "k_EGCMsgAddFreeLicenseResponse", | ||||||
|  | 	82:  "k_EGCMsgGetIPLocation", | ||||||
|  | 	83:  "k_EGCMsgGetIPLocationResponse", | ||||||
|  | 	84:  "k_EGCMsgSystemStatsSchema", | ||||||
|  | 	85:  "k_EGCMsgGetSystemStats", | ||||||
|  | 	86:  "k_EGCMsgGetSystemStatsResponse", | ||||||
|  | 	87:  "k_EGCMsgSendEmail", | ||||||
|  | 	88:  "k_EGCMsgSendEmailResponse", | ||||||
|  | 	89:  "k_EGCMsgGetEmailTemplate", | ||||||
|  | 	90:  "k_EGCMsgGetEmailTemplateResponse", | ||||||
|  | 	91:  "k_EGCMsgGrantGuestPass", | ||||||
|  | 	92:  "k_EGCMsgGrantGuestPassResponse", | ||||||
|  | 	93:  "k_EGCMsgGetAccountDetails", | ||||||
|  | 	94:  "k_EGCMsgGetAccountDetailsResponse", | ||||||
|  | 	95:  "k_EGCMsgGetPersonaNames", | ||||||
|  | 	96:  "k_EGCMsgGetPersonaNamesResponse", | ||||||
|  | 	97:  "k_EGCMsgMultiplexMsg", | ||||||
|  | 	101: "k_EGCMsgWebAPIRegisterInterfaces", | ||||||
|  | 	102: "k_EGCMsgWebAPIJobRequest", | ||||||
|  | 	104: "k_EGCMsgWebAPIJobRequestHttpResponse", | ||||||
|  | 	105: "k_EGCMsgWebAPIJobRequestForwardResponse", | ||||||
|  | 	200: "k_EGCMsgMemCachedGet", | ||||||
|  | 	201: "k_EGCMsgMemCachedGetResponse", | ||||||
|  | 	202: "k_EGCMsgMemCachedSet", | ||||||
|  | 	203: "k_EGCMsgMemCachedDelete", | ||||||
|  | 	204: "k_EGCMsgMemCachedStats", | ||||||
|  | 	205: "k_EGCMsgMemCachedStatsResponse", | ||||||
|  | 	210: "k_EGCMsgSQLStats", | ||||||
|  | 	211: "k_EGCMsgSQLStatsResponse", | ||||||
|  | 	220: "k_EGCMsgMasterSetDirectory", | ||||||
|  | 	221: "k_EGCMsgMasterSetDirectoryResponse", | ||||||
|  | 	222: "k_EGCMsgMasterSetWebAPIRouting", | ||||||
|  | 	223: "k_EGCMsgMasterSetWebAPIRoutingResponse", | ||||||
|  | 	224: "k_EGCMsgMasterSetClientMsgRouting", | ||||||
|  | 	225: "k_EGCMsgMasterSetClientMsgRoutingResponse", | ||||||
|  | 	226: "k_EGCMsgSetOptions", | ||||||
|  | 	227: "k_EGCMsgSetOptionsResponse", | ||||||
|  | 	500: "k_EGCMsgSystemBase2", | ||||||
|  | 	501: "k_EGCMsgGetPurchaseTrustStatus", | ||||||
|  | 	502: "k_EGCMsgGetPurchaseTrustStatusResponse", | ||||||
|  | 	503: "k_EGCMsgUpdateSession", | ||||||
|  | 	504: "k_EGCMsgGCAccountVacStatusChange", | ||||||
|  | 	505: "k_EGCMsgCheckFriendship", | ||||||
|  | 	506: "k_EGCMsgCheckFriendshipResponse", | ||||||
|  | 	507: "k_EGCMsgGetPartnerAccountLink", | ||||||
|  | 	508: "k_EGCMsgGetPartnerAccountLinkResponse", | ||||||
|  | 	509: "k_EGCMsgVSReportedSuspiciousActivity", | ||||||
|  | 	512: "k_EGCMsgDPPartnerMicroTxns", | ||||||
|  | 	513: "k_EGCMsgDPPartnerMicroTxnsResponse", | ||||||
|  | 	514: "k_EGCMsgGetIPASN", | ||||||
|  | 	515: "k_EGCMsgGetIPASNResponse", | ||||||
|  | 	516: "k_EGCMsgGetAppFriendsList", | ||||||
|  | 	517: "k_EGCMsgGetAppFriendsListResponse", | ||||||
|  | } | ||||||
|  | var EGCSystemMsg_value = map[string]int32{ | ||||||
|  | 	"k_EGCMsgInvalid":                           0, | ||||||
|  | 	"k_EGCMsgMulti":                             1, | ||||||
|  | 	"k_EGCMsgGenericReply":                      10, | ||||||
|  | 	"k_EGCMsgSystemBase":                        50, | ||||||
|  | 	"k_EGCMsgAchievementAwarded":                51, | ||||||
|  | 	"k_EGCMsgConCommand":                        52, | ||||||
|  | 	"k_EGCMsgStartPlaying":                      53, | ||||||
|  | 	"k_EGCMsgStopPlaying":                       54, | ||||||
|  | 	"k_EGCMsgStartGameserver":                   55, | ||||||
|  | 	"k_EGCMsgStopGameserver":                    56, | ||||||
|  | 	"k_EGCMsgWGRequest":                         57, | ||||||
|  | 	"k_EGCMsgWGResponse":                        58, | ||||||
|  | 	"k_EGCMsgGetUserGameStatsSchema":            59, | ||||||
|  | 	"k_EGCMsgGetUserGameStatsSchemaResponse":    60, | ||||||
|  | 	"k_EGCMsgGetUserStatsDEPRECATED":            61, | ||||||
|  | 	"k_EGCMsgGetUserStatsResponse":              62, | ||||||
|  | 	"k_EGCMsgAppInfoUpdated":                    63, | ||||||
|  | 	"k_EGCMsgValidateSession":                   64, | ||||||
|  | 	"k_EGCMsgValidateSessionResponse":           65, | ||||||
|  | 	"k_EGCMsgLookupAccountFromInput":            66, | ||||||
|  | 	"k_EGCMsgSendHTTPRequest":                   67, | ||||||
|  | 	"k_EGCMsgSendHTTPRequestResponse":           68, | ||||||
|  | 	"k_EGCMsgPreTestSetup":                      69, | ||||||
|  | 	"k_EGCMsgRecordSupportAction":               70, | ||||||
|  | 	"k_EGCMsgGetAccountDetails_DEPRECATED":      71, | ||||||
|  | 	"k_EGCMsgReceiveInterAppMessage":            73, | ||||||
|  | 	"k_EGCMsgFindAccounts":                      74, | ||||||
|  | 	"k_EGCMsgPostAlert":                         75, | ||||||
|  | 	"k_EGCMsgGetLicenses":                       76, | ||||||
|  | 	"k_EGCMsgGetUserStats":                      77, | ||||||
|  | 	"k_EGCMsgGetCommands":                       78, | ||||||
|  | 	"k_EGCMsgGetCommandsResponse":               79, | ||||||
|  | 	"k_EGCMsgAddFreeLicense":                    80, | ||||||
|  | 	"k_EGCMsgAddFreeLicenseResponse":            81, | ||||||
|  | 	"k_EGCMsgGetIPLocation":                     82, | ||||||
|  | 	"k_EGCMsgGetIPLocationResponse":             83, | ||||||
|  | 	"k_EGCMsgSystemStatsSchema":                 84, | ||||||
|  | 	"k_EGCMsgGetSystemStats":                    85, | ||||||
|  | 	"k_EGCMsgGetSystemStatsResponse":            86, | ||||||
|  | 	"k_EGCMsgSendEmail":                         87, | ||||||
|  | 	"k_EGCMsgSendEmailResponse":                 88, | ||||||
|  | 	"k_EGCMsgGetEmailTemplate":                  89, | ||||||
|  | 	"k_EGCMsgGetEmailTemplateResponse":          90, | ||||||
|  | 	"k_EGCMsgGrantGuestPass":                    91, | ||||||
|  | 	"k_EGCMsgGrantGuestPassResponse":            92, | ||||||
|  | 	"k_EGCMsgGetAccountDetails":                 93, | ||||||
|  | 	"k_EGCMsgGetAccountDetailsResponse":         94, | ||||||
|  | 	"k_EGCMsgGetPersonaNames":                   95, | ||||||
|  | 	"k_EGCMsgGetPersonaNamesResponse":           96, | ||||||
|  | 	"k_EGCMsgMultiplexMsg":                      97, | ||||||
|  | 	"k_EGCMsgWebAPIRegisterInterfaces":          101, | ||||||
|  | 	"k_EGCMsgWebAPIJobRequest":                  102, | ||||||
|  | 	"k_EGCMsgWebAPIJobRequestHttpResponse":      104, | ||||||
|  | 	"k_EGCMsgWebAPIJobRequestForwardResponse":   105, | ||||||
|  | 	"k_EGCMsgMemCachedGet":                      200, | ||||||
|  | 	"k_EGCMsgMemCachedGetResponse":              201, | ||||||
|  | 	"k_EGCMsgMemCachedSet":                      202, | ||||||
|  | 	"k_EGCMsgMemCachedDelete":                   203, | ||||||
|  | 	"k_EGCMsgMemCachedStats":                    204, | ||||||
|  | 	"k_EGCMsgMemCachedStatsResponse":            205, | ||||||
|  | 	"k_EGCMsgSQLStats":                          210, | ||||||
|  | 	"k_EGCMsgSQLStatsResponse":                  211, | ||||||
|  | 	"k_EGCMsgMasterSetDirectory":                220, | ||||||
|  | 	"k_EGCMsgMasterSetDirectoryResponse":        221, | ||||||
|  | 	"k_EGCMsgMasterSetWebAPIRouting":            222, | ||||||
|  | 	"k_EGCMsgMasterSetWebAPIRoutingResponse":    223, | ||||||
|  | 	"k_EGCMsgMasterSetClientMsgRouting":         224, | ||||||
|  | 	"k_EGCMsgMasterSetClientMsgRoutingResponse": 225, | ||||||
|  | 	"k_EGCMsgSetOptions":                        226, | ||||||
|  | 	"k_EGCMsgSetOptionsResponse":                227, | ||||||
|  | 	"k_EGCMsgSystemBase2":                       500, | ||||||
|  | 	"k_EGCMsgGetPurchaseTrustStatus":            501, | ||||||
|  | 	"k_EGCMsgGetPurchaseTrustStatusResponse":    502, | ||||||
|  | 	"k_EGCMsgUpdateSession":                     503, | ||||||
|  | 	"k_EGCMsgGCAccountVacStatusChange":          504, | ||||||
|  | 	"k_EGCMsgCheckFriendship":                   505, | ||||||
|  | 	"k_EGCMsgCheckFriendshipResponse":           506, | ||||||
|  | 	"k_EGCMsgGetPartnerAccountLink":             507, | ||||||
|  | 	"k_EGCMsgGetPartnerAccountLinkResponse":     508, | ||||||
|  | 	"k_EGCMsgVSReportedSuspiciousActivity":      509, | ||||||
|  | 	"k_EGCMsgDPPartnerMicroTxns":                512, | ||||||
|  | 	"k_EGCMsgDPPartnerMicroTxnsResponse":        513, | ||||||
|  | 	"k_EGCMsgGetIPASN":                          514, | ||||||
|  | 	"k_EGCMsgGetIPASNResponse":                  515, | ||||||
|  | 	"k_EGCMsgGetAppFriendsList":                 516, | ||||||
|  | 	"k_EGCMsgGetAppFriendsListResponse":         517, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x EGCSystemMsg) Enum() *EGCSystemMsg { | ||||||
|  | 	p := new(EGCSystemMsg) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | func (x EGCSystemMsg) String() string { | ||||||
|  | 	return proto.EnumName(EGCSystemMsg_name, int32(x)) | ||||||
|  | } | ||||||
|  | func (x *EGCSystemMsg) UnmarshalJSON(data []byte) error { | ||||||
|  | 	value, err := proto.UnmarshalJSONEnum(EGCSystemMsg_value, data, "EGCSystemMsg") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	*x = EGCSystemMsg(value) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (EGCSystemMsg) EnumDescriptor() ([]byte, []int) { return system_fileDescriptor0, []int{0} } | ||||||
|  |  | ||||||
|  | type ESOMsg int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	ESOMsg_k_ESOMsg_Create                   ESOMsg = 21 | ||||||
|  | 	ESOMsg_k_ESOMsg_Update                   ESOMsg = 22 | ||||||
|  | 	ESOMsg_k_ESOMsg_Destroy                  ESOMsg = 23 | ||||||
|  | 	ESOMsg_k_ESOMsg_CacheSubscribed          ESOMsg = 24 | ||||||
|  | 	ESOMsg_k_ESOMsg_CacheUnsubscribed        ESOMsg = 25 | ||||||
|  | 	ESOMsg_k_ESOMsg_UpdateMultiple           ESOMsg = 26 | ||||||
|  | 	ESOMsg_k_ESOMsg_CacheSubscriptionRefresh ESOMsg = 28 | ||||||
|  | 	ESOMsg_k_ESOMsg_CacheSubscribedUpToDate  ESOMsg = 29 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ESOMsg_name = map[int32]string{ | ||||||
|  | 	21: "k_ESOMsg_Create", | ||||||
|  | 	22: "k_ESOMsg_Update", | ||||||
|  | 	23: "k_ESOMsg_Destroy", | ||||||
|  | 	24: "k_ESOMsg_CacheSubscribed", | ||||||
|  | 	25: "k_ESOMsg_CacheUnsubscribed", | ||||||
|  | 	26: "k_ESOMsg_UpdateMultiple", | ||||||
|  | 	28: "k_ESOMsg_CacheSubscriptionRefresh", | ||||||
|  | 	29: "k_ESOMsg_CacheSubscribedUpToDate", | ||||||
|  | } | ||||||
|  | var ESOMsg_value = map[string]int32{ | ||||||
|  | 	"k_ESOMsg_Create":                   21, | ||||||
|  | 	"k_ESOMsg_Update":                   22, | ||||||
|  | 	"k_ESOMsg_Destroy":                  23, | ||||||
|  | 	"k_ESOMsg_CacheSubscribed":          24, | ||||||
|  | 	"k_ESOMsg_CacheUnsubscribed":        25, | ||||||
|  | 	"k_ESOMsg_UpdateMultiple":           26, | ||||||
|  | 	"k_ESOMsg_CacheSubscriptionRefresh": 28, | ||||||
|  | 	"k_ESOMsg_CacheSubscribedUpToDate":  29, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x ESOMsg) Enum() *ESOMsg { | ||||||
|  | 	p := new(ESOMsg) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | func (x ESOMsg) String() string { | ||||||
|  | 	return proto.EnumName(ESOMsg_name, int32(x)) | ||||||
|  | } | ||||||
|  | func (x *ESOMsg) UnmarshalJSON(data []byte) error { | ||||||
|  | 	value, err := proto.UnmarshalJSONEnum(ESOMsg_value, data, "ESOMsg") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	*x = ESOMsg(value) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (ESOMsg) EnumDescriptor() ([]byte, []int) { return system_fileDescriptor0, []int{1} } | ||||||
|  |  | ||||||
|  | type EGCBaseClientMsg int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCPingRequest            EGCBaseClientMsg = 3001 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCPingResponse           EGCBaseClientMsg = 3002 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCClientWelcome          EGCBaseClientMsg = 4004 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCServerWelcome          EGCBaseClientMsg = 4005 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCClientHello            EGCBaseClientMsg = 4006 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCServerHello            EGCBaseClientMsg = 4007 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCClientConnectionStatus EGCBaseClientMsg = 4009 | ||||||
|  | 	EGCBaseClientMsg_k_EMsgGCServerConnectionStatus EGCBaseClientMsg = 4010 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var EGCBaseClientMsg_name = map[int32]string{ | ||||||
|  | 	3001: "k_EMsgGCPingRequest", | ||||||
|  | 	3002: "k_EMsgGCPingResponse", | ||||||
|  | 	4004: "k_EMsgGCClientWelcome", | ||||||
|  | 	4005: "k_EMsgGCServerWelcome", | ||||||
|  | 	4006: "k_EMsgGCClientHello", | ||||||
|  | 	4007: "k_EMsgGCServerHello", | ||||||
|  | 	4009: "k_EMsgGCClientConnectionStatus", | ||||||
|  | 	4010: "k_EMsgGCServerConnectionStatus", | ||||||
|  | } | ||||||
|  | var EGCBaseClientMsg_value = map[string]int32{ | ||||||
|  | 	"k_EMsgGCPingRequest":            3001, | ||||||
|  | 	"k_EMsgGCPingResponse":           3002, | ||||||
|  | 	"k_EMsgGCClientWelcome":          4004, | ||||||
|  | 	"k_EMsgGCServerWelcome":          4005, | ||||||
|  | 	"k_EMsgGCClientHello":            4006, | ||||||
|  | 	"k_EMsgGCServerHello":            4007, | ||||||
|  | 	"k_EMsgGCClientConnectionStatus": 4009, | ||||||
|  | 	"k_EMsgGCServerConnectionStatus": 4010, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x EGCBaseClientMsg) Enum() *EGCBaseClientMsg { | ||||||
|  | 	p := new(EGCBaseClientMsg) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | func (x EGCBaseClientMsg) String() string { | ||||||
|  | 	return proto.EnumName(EGCBaseClientMsg_name, int32(x)) | ||||||
|  | } | ||||||
|  | func (x *EGCBaseClientMsg) UnmarshalJSON(data []byte) error { | ||||||
|  | 	value, err := proto.UnmarshalJSONEnum(EGCBaseClientMsg_value, data, "EGCBaseClientMsg") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	*x = EGCBaseClientMsg(value) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (EGCBaseClientMsg) EnumDescriptor() ([]byte, []int) { return system_fileDescriptor0, []int{2} } | ||||||
|  |  | ||||||
|  | type EGCToGCMsg int32 | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	EGCToGCMsg_k_EGCToGCMsgMasterAck                   EGCToGCMsg = 150 | ||||||
|  | 	EGCToGCMsg_k_EGCToGCMsgMasterAckResponse           EGCToGCMsg = 151 | ||||||
|  | 	EGCToGCMsg_k_EGCToGCMsgRouted                      EGCToGCMsg = 152 | ||||||
|  | 	EGCToGCMsg_k_EGCToGCMsgRoutedReply                 EGCToGCMsg = 153 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCUpdateSubGCSessionInfo          EGCToGCMsg = 154 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCRequestSubGCSessionInfo         EGCToGCMsg = 155 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCRequestSubGCSessionInfoResponse EGCToGCMsg = 156 | ||||||
|  | 	EGCToGCMsg_k_EGCToGCMsgMasterStartupComplete       EGCToGCMsg = 157 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCToGCSOCacheSubscribe            EGCToGCMsg = 158 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCToGCSOCacheUnsubscribe          EGCToGCMsg = 159 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCToGCLoadSessionSOCache          EGCToGCMsg = 160 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCToGCLoadSessionSOCacheResponse  EGCToGCMsg = 161 | ||||||
|  | 	EGCToGCMsg_k_EMsgGCToGCUpdateSessionStats          EGCToGCMsg = 162 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var EGCToGCMsg_name = map[int32]string{ | ||||||
|  | 	150: "k_EGCToGCMsgMasterAck", | ||||||
|  | 	151: "k_EGCToGCMsgMasterAckResponse", | ||||||
|  | 	152: "k_EGCToGCMsgRouted", | ||||||
|  | 	153: "k_EGCToGCMsgRoutedReply", | ||||||
|  | 	154: "k_EMsgGCUpdateSubGCSessionInfo", | ||||||
|  | 	155: "k_EMsgGCRequestSubGCSessionInfo", | ||||||
|  | 	156: "k_EMsgGCRequestSubGCSessionInfoResponse", | ||||||
|  | 	157: "k_EGCToGCMsgMasterStartupComplete", | ||||||
|  | 	158: "k_EMsgGCToGCSOCacheSubscribe", | ||||||
|  | 	159: "k_EMsgGCToGCSOCacheUnsubscribe", | ||||||
|  | 	160: "k_EMsgGCToGCLoadSessionSOCache", | ||||||
|  | 	161: "k_EMsgGCToGCLoadSessionSOCacheResponse", | ||||||
|  | 	162: "k_EMsgGCToGCUpdateSessionStats", | ||||||
|  | } | ||||||
|  | var EGCToGCMsg_value = map[string]int32{ | ||||||
|  | 	"k_EGCToGCMsgMasterAck":                   150, | ||||||
|  | 	"k_EGCToGCMsgMasterAckResponse":           151, | ||||||
|  | 	"k_EGCToGCMsgRouted":                      152, | ||||||
|  | 	"k_EGCToGCMsgRoutedReply":                 153, | ||||||
|  | 	"k_EMsgGCUpdateSubGCSessionInfo":          154, | ||||||
|  | 	"k_EMsgGCRequestSubGCSessionInfo":         155, | ||||||
|  | 	"k_EMsgGCRequestSubGCSessionInfoResponse": 156, | ||||||
|  | 	"k_EGCToGCMsgMasterStartupComplete":       157, | ||||||
|  | 	"k_EMsgGCToGCSOCacheSubscribe":            158, | ||||||
|  | 	"k_EMsgGCToGCSOCacheUnsubscribe":          159, | ||||||
|  | 	"k_EMsgGCToGCLoadSessionSOCache":          160, | ||||||
|  | 	"k_EMsgGCToGCLoadSessionSOCacheResponse":  161, | ||||||
|  | 	"k_EMsgGCToGCUpdateSessionStats":          162, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (x EGCToGCMsg) Enum() *EGCToGCMsg { | ||||||
|  | 	p := new(EGCToGCMsg) | ||||||
|  | 	*p = x | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | func (x EGCToGCMsg) String() string { | ||||||
|  | 	return proto.EnumName(EGCToGCMsg_name, int32(x)) | ||||||
|  | } | ||||||
|  | func (x *EGCToGCMsg) UnmarshalJSON(data []byte) error { | ||||||
|  | 	value, err := proto.UnmarshalJSONEnum(EGCToGCMsg_value, data, "EGCToGCMsg") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	*x = EGCToGCMsg(value) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | func (EGCToGCMsg) EnumDescriptor() ([]byte, []int) { return system_fileDescriptor0, []int{3} } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	proto.RegisterEnum("EGCSystemMsg", EGCSystemMsg_name, EGCSystemMsg_value) | ||||||
|  | 	proto.RegisterEnum("ESOMsg", ESOMsg_name, ESOMsg_value) | ||||||
|  | 	proto.RegisterEnum("EGCBaseClientMsg", EGCBaseClientMsg_name, EGCBaseClientMsg_value) | ||||||
|  | 	proto.RegisterEnum("EGCToGCMsg", EGCToGCMsg_name, EGCToGCMsg_value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var system_fileDescriptor0 = []byte{ | ||||||
|  | 	// 1475 bytes of a gzipped FileDescriptorProto | ||||||
|  | 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x57, 0x59, 0x73, 0x1b, 0xc5, | ||||||
|  | 	0x13, 0xcf, 0x96, 0xfc, 0xff, 0x3f, 0x4c, 0x41, 0xd1, 0x99, 0xc4, 0x47, 0x12, 0x27, 0x4a, 0x42, | ||||||
|  | 	0x0e, 0x62, 0xa8, 0x3c, 0x84, 0xfb, 0x46, 0x91, 0x64, 0x5b, 0x41, 0x8e, 0x15, 0x4b, 0xb6, 0xb9, | ||||||
|  | 	0xcd, 0x7a, 0x35, 0xb6, 0xb6, 0x2c, 0xed, 0x2c, 0x33, 0xbb, 0x26, 0x7e, 0x0b, 0xd7, 0x57, 0xe0, | ||||||
|  | 	0xbe, 0x8b, 0xa3, 0xe0, 0x1b, 0xc0, 0x27, 0xe0, 0x7c, 0x81, 0x57, 0xee, 0x7c, 0x01, 0x1e, 0xb8, | ||||||
|  | 	0x21, 0x55, 0xf4, 0xee, 0xce, 0xce, 0xce, 0x4a, 0xb2, 0x79, 0x93, 0xe6, 0xd7, 0xdd, 0xd3, 0xdd, | ||||||
|  | 	0xd3, 0xfd, 0xeb, 0x5e, 0x42, 0xd7, 0x1d, 0xb9, 0x25, 0x03, 0xd6, 0xeb, 0xc9, 0x75, 0x79, 0xda, | ||||||
|  | 	0x17, 0x3c, 0xe0, 0x53, 0x97, 0x47, 0xc9, 0x55, 0xd5, 0x99, 0x72, 0x33, 0x3e, 0x9f, 0x93, 0xeb, | ||||||
|  | 	0x74, 0x0f, 0xb9, 0x66, 0x63, 0x05, 0x4f, 0xf0, 0x77, 0xcd, 0xdb, 0xb4, 0xbb, 0x6e, 0x1b, 0x76, | ||||||
|  | 	0xd1, 0xdd, 0xe4, 0xea, 0xf4, 0x70, 0x2e, 0xec, 0x06, 0x2e, 0x58, 0x74, 0x82, 0xec, 0x4d, 0x8f, | ||||||
|  | 	0x66, 0x98, 0xc7, 0x84, 0xeb, 0x2c, 0x30, 0xbf, 0xbb, 0x05, 0x84, 0x8e, 0x11, 0x9a, 0x22, 0x89, | ||||||
|  | 	0xd9, 0xb3, 0xb6, 0x64, 0x70, 0x86, 0x1e, 0x22, 0xfb, 0xd3, 0xf3, 0x92, 0xd3, 0x71, 0xd9, 0x26, | ||||||
|  | 	0xeb, 0x31, 0x2f, 0x28, 0x3d, 0x69, 0x8b, 0x36, 0x6b, 0xc3, 0x8d, 0xa6, 0x5e, 0x99, 0x7b, 0x65, | ||||||
|  | 	0xde, 0xeb, 0xd9, 0x5e, 0x1b, 0x6e, 0x32, 0x6f, 0x6a, 0x06, 0xb6, 0x08, 0x1a, 0x5d, 0x7b, 0xcb, | ||||||
|  | 	0xf5, 0xd6, 0xe1, 0x66, 0x3a, 0x4e, 0xf6, 0x64, 0x08, 0xf7, 0x53, 0xe0, 0x16, 0x7a, 0x80, 0x8c, | ||||||
|  | 	0xe7, 0x54, 0x66, 0xec, 0x1e, 0x93, 0x4c, 0x6c, 0x32, 0x01, 0xb7, 0xd2, 0xfd, 0x64, 0xcc, 0xd4, | ||||||
|  | 	0x32, 0xb0, 0xdb, 0xe8, 0x28, 0xd9, 0x9d, 0x62, 0xcb, 0x33, 0x0b, 0xec, 0x89, 0x90, 0xc9, 0x00, | ||||||
|  | 	0x6e, 0x37, 0x5d, 0x8b, 0x8e, 0xa5, 0xcf, 0x3d, 0x0c, 0xe9, 0x0e, 0x7a, 0x94, 0x1c, 0xca, 0x92, | ||||||
|  | 	0x10, 0x2c, 0xa2, 0x99, 0xc8, 0x1a, 0x5e, 0x19, 0xc8, 0xa6, 0xd3, 0x61, 0x3d, 0x1b, 0xee, 0xa4, | ||||||
|  | 	0x53, 0xe4, 0xc4, 0xce, 0x32, 0xda, 0xde, 0x5d, 0x43, 0xec, 0xc5, 0x72, 0x95, 0x6a, 0x63, 0xa1, | ||||||
|  | 	0x5a, 0x2e, 0xb5, 0xaa, 0x15, 0xb8, 0x9b, 0x1e, 0x26, 0x93, 0xc3, 0x64, 0xb4, 0x95, 0x7b, 0xcc, | ||||||
|  | 	0x00, 0x4b, 0xbe, 0x5f, 0xf3, 0xd6, 0xf8, 0xa2, 0xdf, 0xb6, 0x03, 0x4c, 0xf2, 0xbd, 0x66, 0x66, | ||||||
|  | 	0x96, 0xa2, 0xc7, 0xc5, 0xe3, 0x26, 0x93, 0xd2, 0xe5, 0x1e, 0xdc, 0x47, 0xaf, 0x25, 0xc5, 0x6d, | ||||||
|  | 	0x40, 0x6d, 0xbd, 0x64, 0xfa, 0x58, 0xe7, 0x7c, 0x23, 0xf4, 0x4b, 0x8e, 0xc3, 0x43, 0x2f, 0x98, | ||||||
|  | 	0x16, 0xbc, 0x57, 0xf3, 0xfc, 0x30, 0x80, 0xb3, 0xb9, 0xfc, 0x33, 0xaf, 0x3d, 0xdb, 0x6a, 0x35, | ||||||
|  | 	0xd2, 0x64, 0x96, 0xcd, 0x5b, 0xfa, 0x40, 0x7d, 0x4b, 0xc5, 0x7c, 0xf4, 0x86, 0x60, 0x2d, 0x04, | ||||||
|  | 	0x9b, 0x2c, 0x08, 0x7d, 0xa8, 0xd2, 0x22, 0x39, 0x90, 0x22, 0x0b, 0xcc, 0xe1, 0xa2, 0xdd, 0x0c, | ||||||
|  | 	0x7d, 0x9f, 0x8b, 0xa0, 0xe4, 0x04, 0x51, 0x14, 0xd3, 0xf4, 0x3a, 0x72, 0xcc, 0x48, 0x90, 0xf2, | ||||||
|  | 	0xae, 0xc2, 0x02, 0xdb, 0xed, 0xca, 0x15, 0x23, 0x95, 0x33, 0x66, 0x28, 0x68, 0x8a, 0xb9, 0x9b, | ||||||
|  | 	0xac, 0xe6, 0x05, 0x4c, 0x60, 0xd2, 0xe6, 0x30, 0x6c, 0x7b, 0x9d, 0x41, 0xcd, 0x74, 0x64, 0xda, | ||||||
|  | 	0xf5, 0xda, 0xca, 0x9c, 0x84, 0x73, 0x66, 0xad, 0x34, 0xb8, 0x0c, 0x4a, 0x5d, 0x26, 0x02, 0xb8, | ||||||
|  | 	0xdf, 0x2c, 0x4a, 0xbc, 0xbe, 0xee, 0x3a, 0x0c, 0x23, 0x92, 0x50, 0xcf, 0x77, 0x4c, 0xf6, 0x70, | ||||||
|  | 	0x30, 0xd7, 0xa7, 0xa2, 0x2a, 0x5f, 0xc2, 0x79, 0x33, 0x56, 0x03, 0xd0, 0x69, 0x9a, 0xcf, 0x3d, | ||||||
|  | 	0x75, 0xbb, 0x3d, 0x2d, 0x18, 0x53, 0x17, 0x42, 0xc3, 0x8c, 0x2e, 0x8f, 0x69, 0xfd, 0x0b, 0x74, | ||||||
|  | 	0x1f, 0x19, 0x35, 0x2e, 0xa8, 0x35, 0xea, 0xdc, 0xb1, 0xe3, 0x34, 0x2e, 0xd0, 0x23, 0xe4, 0xe0, | ||||||
|  | 	0x50, 0x48, 0x6b, 0x37, 0xe9, 0x41, 0xb2, 0x2f, 0xdf, 0xe9, 0x66, 0xe5, 0xb7, 0x4c, 0xe7, 0xd0, | ||||||
|  | 	0x82, 0x21, 0x01, 0x8b, 0x7d, 0x95, 0x6e, 0x60, 0xda, 0xfc, 0x92, 0x99, 0xe0, 0xa8, 0x50, 0xaa, | ||||||
|  | 	0x3d, 0x7c, 0x41, 0x58, 0xce, 0xdd, 0x9a, 0x1e, 0x6b, 0xad, 0x07, 0xe8, 0x24, 0x99, 0x30, 0x2c, | ||||||
|  | 	0xc7, 0x68, 0x8b, 0xf5, 0xfc, 0x2e, 0x16, 0x33, 0x3c, 0x48, 0x8f, 0x91, 0xc3, 0xdb, 0xa1, 0xda, | ||||||
|  | 	0xc6, 0x43, 0x39, 0xcf, 0x85, 0xed, 0x05, 0x33, 0x51, 0x75, 0x36, 0x6c, 0x29, 0xe1, 0xe1, 0x9c, | ||||||
|  | 	0xe7, 0x39, 0x4c, 0xeb, 0x3f, 0x62, 0xba, 0x38, 0x50, 0x82, 0xf0, 0x28, 0x3d, 0x4e, 0x8e, 0x6c, | ||||||
|  | 	0x0b, 0x6b, 0x2b, 0x8f, 0x99, 0x5d, 0x84, 0x62, 0x0d, 0x26, 0x24, 0xf7, 0xec, 0xf3, 0x11, 0x5d, | ||||||
|  | 	0xc1, 0x8a, 0xd9, 0x45, 0x7d, 0xa0, 0xb6, 0xf0, 0xb8, 0x59, 0x72, 0x31, 0x6f, 0xfb, 0x5d, 0x76, | ||||||
|  | 	0x11, 0x7f, 0x83, 0x6d, 0xe6, 0x61, 0x99, 0xad, 0x96, 0x1a, 0xb5, 0x05, 0xb6, 0xee, 0xe2, 0x23, | ||||||
|  | 	0x88, 0xb8, 0x03, 0xd6, 0x6c, 0x07, 0x2f, 0x61, 0x66, 0x2e, 0x13, 0xa9, 0x73, 0x7c, 0x35, 0x6d, | ||||||
|  | 	0xe4, 0x35, 0xb3, 0xd1, 0xfa, 0xd1, 0xd9, 0x20, 0xf0, 0xb5, 0x1f, 0x1d, 0x7a, 0x3d, 0x39, 0xb9, | ||||||
|  | 	0x9d, 0xe4, 0x34, 0x17, 0xd1, 0x04, 0xd0, 0xc2, 0x2e, 0xd6, 0x64, 0xe6, 0x34, 0xeb, 0x95, 0x6d, | ||||||
|  | 	0x2c, 0xa7, 0x36, 0x86, 0x08, 0x9f, 0x58, 0x58, 0x93, 0x93, 0xc3, 0x20, 0xad, 0xfc, 0xa9, 0x35, | ||||||
|  | 	0x54, 0x1b, 0xa9, 0x03, 0x3e, 0xb3, 0x30, 0x9a, 0xf1, 0x01, 0xa8, 0xc2, 0xba, 0x0c, 0x0b, 0xe3, | ||||||
|  | 	0x73, 0x0b, 0xb3, 0x3d, 0x36, 0xa8, 0x18, 0x57, 0xeb, 0x17, 0x16, 0x66, 0xfb, 0xd0, 0x70, 0x50, | ||||||
|  | 	0x5f, 0xfd, 0xa5, 0x85, 0xf5, 0x0a, 0xba, 0x30, 0x2f, 0xd4, 0x13, 0xdd, 0xaf, 0x2c, 0x2c, 0x86, | ||||||
|  | 	0x89, 0xfe, 0x63, 0xad, 0xf5, 0xb5, 0x85, 0x3d, 0xae, 0xc7, 0xe2, 0x9c, 0x1d, 0xbd, 0x00, 0x7a, | ||||||
|  | 	0x5b, 0x71, 0x05, 0x73, 0x02, 0x2e, 0xb6, 0xe0, 0x1b, 0x8b, 0x9e, 0x24, 0x47, 0xb7, 0x17, 0xd0, | ||||||
|  | 	0x96, 0xbe, 0xcd, 0x3b, 0x99, 0x0a, 0xaa, 0xc7, 0xe5, 0x61, 0x10, 0x4d, 0xc6, 0xef, 0x2c, 0x7c, | ||||||
|  | 	0x8a, 0x13, 0x3b, 0x0b, 0x69, 0x8b, 0xdf, 0x5b, 0xf4, 0x44, 0x56, 0xa8, 0x5a, 0xb8, 0xdc, 0x75, | ||||||
|  | 	0x71, 0x6c, 0x47, 0x94, 0xa9, 0x8c, 0xfe, 0x60, 0xd1, 0xd3, 0xe4, 0xd4, 0x7f, 0xca, 0x69, 0xbb, | ||||||
|  | 	0x3f, 0x5a, 0x48, 0x78, 0xd9, 0x8a, 0xc0, 0x82, 0x79, 0x3f, 0xe2, 0x15, 0x09, 0x3f, 0xe5, 0x92, | ||||||
|  | 	0x91, 0x01, 0x5a, 0xf3, 0x72, 0xb4, 0x76, 0xec, 0x19, 0x5c, 0x2e, 0xce, 0xc0, 0x2f, 0x05, 0x33, | ||||||
|  | 	0xfa, 0xa8, 0x21, 0x42, 0xe1, 0x74, 0x10, 0x6a, 0x89, 0x10, 0x47, 0x07, 0xe6, 0x3c, 0x94, 0xf0, | ||||||
|  | 	0x6b, 0xc1, 0x8c, 0x7e, 0xb8, 0x90, 0xbe, 0xeb, 0xb7, 0x02, 0xb2, 0x80, 0x26, 0xc7, 0x64, 0x80, | ||||||
|  | 	0xa6, 0x93, 0xf2, 0xf7, 0x02, 0xb6, 0x70, 0xc6, 0x23, 0x65, 0xd5, 0xc1, 0x4b, 0xb6, 0x93, 0x18, | ||||||
|  | 	0x29, 0x77, 0x6c, 0x0f, 0x87, 0xc7, 0x1f, 0x05, 0xb3, 0xe4, 0xca, 0x1d, 0xe6, 0x6c, 0x4c, 0x0b, | ||||||
|  | 	0x4c, 0x4a, 0x5b, 0x76, 0x5c, 0x1f, 0xfe, 0x2c, 0x60, 0x13, 0x16, 0xb7, 0x41, 0xb5, 0x1b, 0x7f, | ||||||
|  | 	0x15, 0x90, 0x70, 0x4c, 0x22, 0x6e, 0xe0, 0x3a, 0x83, 0xeb, 0x96, 0xba, 0xb2, 0xee, 0x7a, 0x1b, | ||||||
|  | 	0xf0, 0x77, 0x01, 0x97, 0x8c, 0xe3, 0x3b, 0xca, 0x68, 0x7b, 0xff, 0x14, 0xe8, 0xa9, 0xac, 0x6d, | ||||||
|  | 	0x97, 0x9a, 0xb8, 0xb4, 0xe1, 0xec, 0xc4, 0x62, 0x0e, 0xa5, 0xef, 0x3a, 0x2e, 0x0f, 0x65, 0x34, | ||||||
|  | 	0x47, 0x37, 0xdd, 0x60, 0x0b, 0xae, 0x14, 0xcc, 0xe7, 0xa8, 0x34, 0x94, 0xd5, 0x39, 0xd7, 0x11, | ||||||
|  | 	0xbc, 0x75, 0x11, 0xdf, 0xeb, 0xd2, 0x88, 0x59, 0x9b, 0x83, 0x02, 0xfa, 0xd2, 0xa7, 0x46, 0xcc, | ||||||
|  | 	0xde, 0x88, 0xa7, 0x49, 0xa9, 0x79, 0x1e, 0x9e, 0x1e, 0x31, 0x7b, 0x23, 0x3d, 0xd6, 0x5a, 0xcf, | ||||||
|  | 	0x8c, 0xe0, 0xca, 0x98, 0xe3, 0x51, 0xdf, 0x57, 0x19, 0xaa, 0x23, 0x55, 0xc1, 0xb3, 0x23, 0x66, | ||||||
|  | 	0x7d, 0x0e, 0xe0, 0xda, 0xce, 0x73, 0x23, 0x53, 0x3f, 0x5b, 0xe4, 0xff, 0xd5, 0xe6, 0x7c, 0xb6, | ||||||
|  | 	0xdf, 0xc6, 0xbf, 0x57, 0xca, 0x82, 0x45, 0x53, 0x61, 0x34, 0x77, 0x98, 0x3c, 0x35, 0x8c, 0xd1, | ||||||
|  | 	0xbd, 0xb1, 0xcb, 0xc9, 0x61, 0x05, 0x99, 0x4a, 0xf0, 0x2d, 0x18, 0x57, 0x94, 0xa8, 0xf4, 0x23, | ||||||
|  | 	0x1e, 0x68, 0x86, 0xab, 0xd2, 0x11, 0xee, 0x2a, 0xae, 0x57, 0x13, 0x6a, 0xc7, 0x35, 0xd0, 0x45, | ||||||
|  | 	0x4f, 0x66, 0xf8, 0x3e, 0x45, 0xe9, 0xe6, 0x45, 0x29, 0x2f, 0xc3, 0x7e, 0x35, 0x16, 0x06, 0x4d, | ||||||
|  | 	0xfb, 0xc9, 0xd8, 0x5d, 0x13, 0x4c, 0x76, 0x60, 0x52, 0x51, 0xf7, 0x50, 0x0f, 0x16, 0xfd, 0x16, | ||||||
|  | 	0xaf, 0x44, 0xde, 0x1f, 0x9c, 0xba, 0x62, 0x11, 0xc0, 0xcc, 0x44, 0xed, 0xa1, 0x3b, 0x51, 0x75, | ||||||
|  | 	0x4f, 0x5c, 0xb3, 0x8d, 0xb8, 0x25, 0x13, 0x2a, 0xff, 0x68, 0x5c, 0xd1, 0xa6, 0x81, 0xa8, 0xe4, | ||||||
|  | 	0x7d, 0x3c, 0xae, 0xda, 0x20, 0x86, 0x12, 0x4b, 0xcb, 0xac, 0xeb, 0xf0, 0x1e, 0x83, 0x77, 0x8a, | ||||||
|  | 	0x26, 0xd6, 0x8c, 0x77, 0xe8, 0x14, 0x7b, 0xb7, 0x68, 0x5e, 0x96, 0xe8, 0xcd, 0xb2, 0x6e, 0x97, | ||||||
|  | 	0xc3, 0x7b, 0x39, 0x24, 0xd1, 0x4a, 0x90, 0xf7, 0x8b, 0xaa, 0x89, 0x0d, 0x1d, 0xfc, 0x12, 0xf0, | ||||||
|  | 	0x58, 0xbc, 0xd9, 0xa9, 0x26, 0xfe, 0x20, 0x27, 0x94, 0xa8, 0x0f, 0x08, 0x7d, 0x58, 0x9c, 0xba, | ||||||
|  | 	0x5c, 0x20, 0x04, 0xe3, 0x6f, 0xf1, 0xb8, 0x3a, 0x74, 0x2f, 0xab, 0xff, 0x09, 0x4b, 0x95, 0x9c, | ||||||
|  | 	0x0d, 0x78, 0xde, 0xd2, 0x0d, 0xd6, 0x8f, 0xe9, 0x24, 0xbc, 0x90, 0x31, 0x96, 0x92, 0x89, 0x38, | ||||||
|  | 	0x0d, 0x1f, 0xf4, 0xc5, 0x6c, 0xa8, 0xe4, 0x80, 0xe4, 0x53, 0xe8, 0x25, 0xcb, 0x74, 0x55, 0x51, | ||||||
|  | 	0x48, 0xb8, 0x1a, 0x79, 0x1d, 0xf3, 0x48, 0xb4, 0x99, 0xc3, 0xcb, 0x96, 0xa2, 0x81, 0x58, 0x48, | ||||||
|  | 	0xbd, 0xc8, 0x80, 0xd4, 0x2b, 0x16, 0xbd, 0x21, 0x9e, 0xa1, 0x3b, 0x49, 0x69, 0x7f, 0x5f, 0xcd, | ||||||
|  | 	0x98, 0x3b, 0x17, 0x53, 0xfc, 0x2d, 0x14, 0xfa, 0xb8, 0x47, 0xfa, 0xf1, 0xd4, 0x7b, 0x2d, 0x9d, | ||||||
|  | 	0xa8, 0xb1, 0xd5, 0x48, 0xb4, 0x39, 0x9f, 0xaf, 0x28, 0x78, 0x3d, 0x17, 0x83, 0x21, 0x62, 0x14, | ||||||
|  | 	0x36, 0xbc, 0x31, 0x20, 0x54, 0xe7, 0x76, 0x5b, 0x79, 0xa6, 0xe4, 0xe1, 0xcd, 0x74, 0xf6, 0xec, | ||||||
|  | 	0x20, 0xa4, 0x23, 0x78, 0x6b, 0xc0, 0x62, 0x8e, 0x81, 0x93, 0xd9, 0xfa, 0xb6, 0x75, 0xf6, 0x7f, | ||||||
|  | 	0xb3, 0xd6, 0x25, 0x6b, 0xd7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x05, 0xab, 0xaf, 0x14, 0xda, | ||||||
|  | 	0x0e, 0x00, 0x00, | ||||||
|  | } | ||||||
							
								
								
									
										188
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/inventory.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/inventory.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | |||||||
|  | /* | ||||||
|  | Includes inventory types as used in the trade package | ||||||
|  | */ | ||||||
|  | package inventory | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/Philipp15b/go-steam/jsont" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type GenericInventory map[uint32]map[uint64]*Inventory | ||||||
|  |  | ||||||
|  | func NewGenericInventory() GenericInventory { | ||||||
|  | 	iMap := make(map[uint32]map[uint64]*Inventory) | ||||||
|  | 	return GenericInventory(iMap) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get inventory for specified AppId and ContextId | ||||||
|  | func (i *GenericInventory) Get(appId uint32, contextId uint64) (*Inventory, error) { | ||||||
|  | 	iMap := (map[uint32]map[uint64]*Inventory)(*i) | ||||||
|  | 	iMap2, ok := iMap[appId] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("inventory for specified appId not found") | ||||||
|  | 	} | ||||||
|  | 	inv, ok := iMap2[contextId] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("inventory for specified contextId not found") | ||||||
|  | 	} | ||||||
|  | 	return inv, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *GenericInventory) Add(appId uint32, contextId uint64, inv *Inventory) { | ||||||
|  | 	iMap := (map[uint32]map[uint64]*Inventory)(*i) | ||||||
|  | 	iMap2, ok := iMap[appId] | ||||||
|  | 	if !ok { | ||||||
|  | 		iMap2 = make(map[uint64]*Inventory) | ||||||
|  | 		iMap[appId] = iMap2 | ||||||
|  | 	} | ||||||
|  | 	iMap2[contextId] = inv | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Inventory struct { | ||||||
|  | 	Items        Items        `json:"rgInventory"` | ||||||
|  | 	Currencies   Currencies   `json:"rgCurrency"` | ||||||
|  | 	Descriptions Descriptions `json:"rgDescriptions"` | ||||||
|  | 	AppInfo      *AppInfo     `json:"rgAppInfo"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Items key is an AssetId | ||||||
|  | type Items map[string]*Item | ||||||
|  |  | ||||||
|  | func (i *Items) ToMap() map[string]*Item { | ||||||
|  | 	return (map[string]*Item)(*i) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Items) Get(assetId uint64) (*Item, error) { | ||||||
|  | 	iMap := (map[string]*Item)(*i) | ||||||
|  | 	if item, ok := iMap[strconv.FormatUint(assetId, 10)]; ok { | ||||||
|  | 		return item, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("item not found") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *Items) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if bytes.Equal(data, []byte("[]")) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, (*map[string]*Item)(i)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Currencies map[string]*Currency | ||||||
|  |  | ||||||
|  | func (c *Currencies) ToMap() map[string]*Currency { | ||||||
|  | 	return (map[string]*Currency)(*c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Currencies) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if bytes.Equal(data, []byte("[]")) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, (*map[string]*Currency)(c)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Descriptions key format is %d_%d, first %d is ClassId, second is InstanceId | ||||||
|  | type Descriptions map[string]*Description | ||||||
|  |  | ||||||
|  | func (d *Descriptions) ToMap() map[string]*Description { | ||||||
|  | 	return (map[string]*Description)(*d) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Descriptions) Get(classId uint64, instanceId uint64) (*Description, error) { | ||||||
|  | 	dMap := (map[string]*Description)(*d) | ||||||
|  | 	descId := fmt.Sprintf("%v_%v", classId, instanceId) | ||||||
|  | 	if desc, ok := dMap[descId]; ok { | ||||||
|  | 		return desc, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("description not found") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Descriptions) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if bytes.Equal(data, []byte("[]")) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, (*map[string]*Description)(d)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Item struct { | ||||||
|  | 	Id         uint64 `json:",string"` | ||||||
|  | 	ClassId    uint64 `json:",string"` | ||||||
|  | 	InstanceId uint64 `json:",string"` | ||||||
|  | 	Amount     uint64 `json:",string"` | ||||||
|  | 	Pos        uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Currency struct { | ||||||
|  | 	Id         uint64 `json:",string"` | ||||||
|  | 	ClassId    uint64 `json:",string"` | ||||||
|  | 	IsCurrency bool   `json:"is_currency"` | ||||||
|  | 	Pos        uint32 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Description struct { | ||||||
|  | 	AppId      uint32 `json:",string"` | ||||||
|  | 	ClassId    uint64 `json:",string"` | ||||||
|  | 	InstanceId uint64 `json:",string"` | ||||||
|  |  | ||||||
|  | 	IconUrl      string `json:"icon_url"` | ||||||
|  | 	IconUrlLarge string `json:"icon_url_large"` | ||||||
|  | 	IconDragUrl  string `json:"icon_drag_url"` | ||||||
|  |  | ||||||
|  | 	Name           string | ||||||
|  | 	MarketName     string `json:"market_name"` | ||||||
|  | 	MarketHashName string `json:"market_hash_name"` | ||||||
|  |  | ||||||
|  | 	// Colors in hex, for example `B2B2B2` | ||||||
|  | 	NameColor       string `json:"name_color"` | ||||||
|  | 	BackgroundColor string `json:"background_color"` | ||||||
|  |  | ||||||
|  | 	Type string | ||||||
|  |  | ||||||
|  | 	Tradable                  jsont.UintBool | ||||||
|  | 	Marketable                jsont.UintBool | ||||||
|  | 	Commodity                 jsont.UintBool | ||||||
|  | 	MarketTradableRestriction uint32 `json:"market_tradable_restriction,string"` | ||||||
|  |  | ||||||
|  | 	Descriptions DescriptionLines | ||||||
|  | 	Actions      []*Action | ||||||
|  | 	// Application-specific data, like "def_index" and "quality" for TF2 | ||||||
|  | 	AppData map[string]string | ||||||
|  | 	Tags    []*Tag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DescriptionLines []*DescriptionLine | ||||||
|  |  | ||||||
|  | func (d *DescriptionLines) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if bytes.Equal(data, []byte(`""`)) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, (*[]*DescriptionLine)(d)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type DescriptionLine struct { | ||||||
|  | 	Value string | ||||||
|  | 	Type  *string // Is `html` for HTML descriptions | ||||||
|  | 	Color *string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Action struct { | ||||||
|  | 	Name string | ||||||
|  | 	Link string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type AppInfo struct { | ||||||
|  | 	AppId uint32 | ||||||
|  | 	Name  string | ||||||
|  | 	Icon  string | ||||||
|  | 	Link  string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Tag struct { | ||||||
|  | 	InternalName string `json:internal_name` | ||||||
|  | 	Name         string | ||||||
|  | 	Category     string | ||||||
|  | 	CategoryName string `json:category_name` | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/inventory_apps.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/inventory_apps.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | package inventory | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"github.com/Philipp15b/go-steam/steamid" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type InventoryApps map[string]*InventoryApp | ||||||
|  |  | ||||||
|  | func (i *InventoryApps) Get(appId uint32) (*InventoryApp, error) { | ||||||
|  | 	iMap := (map[string]*InventoryApp)(*i) | ||||||
|  | 	if inventoryApp, ok := iMap[strconv.FormatUint(uint64(appId), 10)]; ok { | ||||||
|  | 		return inventoryApp, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("inventory app not found") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (i *InventoryApps) ToMap() map[string]*InventoryApp { | ||||||
|  | 	return (map[string]*InventoryApp)(*i) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type InventoryApp struct { | ||||||
|  | 	AppId            uint32 | ||||||
|  | 	Name             string | ||||||
|  | 	Icon             string | ||||||
|  | 	Link             string | ||||||
|  | 	AssetCount       uint32   `json:"asset_count"` | ||||||
|  | 	InventoryLogo    string   `json:"inventory_logo"` | ||||||
|  | 	TradePermissions string   `json:"trade_permissions"` | ||||||
|  | 	Contexts         Contexts `json:"rgContexts"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Contexts map[string]*Context | ||||||
|  |  | ||||||
|  | func (c *Contexts) Get(contextId uint64) (*Context, error) { | ||||||
|  | 	cMap := (map[string]*Context)(*c) | ||||||
|  | 	if context, ok := cMap[strconv.FormatUint(contextId, 10)]; ok { | ||||||
|  | 		return context, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, fmt.Errorf("context not found") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Contexts) ToMap() map[string]*Context { | ||||||
|  | 	return (map[string]*Context)(*c) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Context struct { | ||||||
|  | 	ContextId  uint64 `json:"id,string"` | ||||||
|  | 	AssetCount uint32 `json:"asset_count"` | ||||||
|  | 	Name       string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetInventoryApps(client *http.Client, steamId steamid.SteamId) (InventoryApps, error) { | ||||||
|  | 	resp, err := http.Get("http://steamcommunity.com/profiles/" + steamId.ToString() + "/inventory/") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	respBody, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	reg := regexp.MustCompile("var g_rgAppContextData = (.*?);") | ||||||
|  | 	inventoryAppsMatches := reg.FindSubmatch(respBody) | ||||||
|  | 	if inventoryAppsMatches == nil { | ||||||
|  | 		return nil, fmt.Errorf("profile inventory not found in steam response") | ||||||
|  | 	} | ||||||
|  | 	var inventoryApps InventoryApps | ||||||
|  | 	if err = json.Unmarshal(inventoryAppsMatches[1], &inventoryApps); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return inventoryApps, nil | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/own.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/own.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | package inventory | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func GetPartialOwnInventory(client *http.Client, contextId uint64, appId uint32, start *uint) (*PartialInventory, error) { | ||||||
|  | 	// TODO: the "trading" parameter can be left off to return non-tradable items too | ||||||
|  | 	url := fmt.Sprintf("http://steamcommunity.com/my/inventory/json/%d/%d?trading=1", appId, contextId) | ||||||
|  | 	if start != nil { | ||||||
|  | 		url += "&start=" + strconv.FormatUint(uint64(*start), 10) | ||||||
|  | 	} | ||||||
|  | 	req, err := http.NewRequest("GET", url, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	return DoInventoryRequest(client, req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetOwnInventory(client *http.Client, contextId uint64, appId uint32) (*Inventory, error) { | ||||||
|  | 	return GetFullInventory(func() (*PartialInventory, error) { | ||||||
|  | 		return GetPartialOwnInventory(client, contextId, appId, nil) | ||||||
|  | 	}, func(start uint) (*PartialInventory, error) { | ||||||
|  | 		return GetPartialOwnInventory(client, contextId, appId, &start) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										91
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/partial.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								vendor/github.com/Philipp15b/go-steam/economy/inventory/partial.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | package inventory | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A partial inventory as sent by the Steam API. | ||||||
|  | type PartialInventory struct { | ||||||
|  | 	Success bool | ||||||
|  | 	Error   string | ||||||
|  | 	Inventory | ||||||
|  | 	More      bool | ||||||
|  | 	MoreStart MoreStart `json:"more_start"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MoreStart uint | ||||||
|  |  | ||||||
|  | func (m *MoreStart) UnmarshalJSON(data []byte) error { | ||||||
|  | 	if bytes.Equal(data, []byte("false")) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return json.Unmarshal(data, (*uint)(m)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DoInventoryRequest(client *http.Client, req *http.Request) (*PartialInventory, error) { | ||||||
|  | 	resp, err := client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	inv := new(PartialInventory) | ||||||
|  | 	err = json.NewDecoder(resp.Body).Decode(inv) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return inv, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetFullInventory(getFirst func() (*PartialInventory, error), getNext func(start uint) (*PartialInventory, error)) (*Inventory, error) { | ||||||
|  | 	first, err := getFirst() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if !first.Success { | ||||||
|  | 		return nil, errors.New("GetFullInventory API call failed: " + first.Error) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result := &first.Inventory | ||||||
|  | 	var next *PartialInventory | ||||||
|  | 	for latest := first; latest.More; latest = next { | ||||||
|  | 		next, err := getNext(uint(latest.MoreStart)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if !next.Success { | ||||||
|  | 			return nil, errors.New("GetFullInventory API call failed: " + next.Error) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		result = Merge(result, &next.Inventory) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Merges the given Inventory into a single Inventory. | ||||||
|  | // The given slice must have at least one element. The first element of the slice is used | ||||||
|  | // and modified. | ||||||
|  | func Merge(p ...*Inventory) *Inventory { | ||||||
|  | 	inv := p[0] | ||||||
|  | 	for idx, i := range p { | ||||||
|  | 		if idx == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for key, value := range i.Items { | ||||||
|  | 			inv.Items[key] = value | ||||||
|  | 		} | ||||||
|  | 		for key, value := range i.Descriptions { | ||||||
|  | 			inv.Descriptions[key] = value | ||||||
|  | 		} | ||||||
|  | 		for key, value := range i.Currencies { | ||||||
|  | 			inv.Currencies[key] = value | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return inv | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								vendor/github.com/Philipp15b/go-steam/gamecoordinator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								vendor/github.com/Philipp15b/go-steam/gamecoordinator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | package steam | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/gamecoordinator" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/protobuf" | ||||||
|  | 	. "github.com/Philipp15b/go-steam/protocol/steamlang" | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type GameCoordinator struct { | ||||||
|  | 	client   *Client | ||||||
|  | 	handlers []GCPacketHandler | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newGC(client *Client) *GameCoordinator { | ||||||
|  | 	return &GameCoordinator{ | ||||||
|  | 		client:   client, | ||||||
|  | 		handlers: make([]GCPacketHandler, 0), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type GCPacketHandler interface { | ||||||
|  | 	HandleGCPacket(*GCPacket) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *GameCoordinator) RegisterPacketHandler(handler GCPacketHandler) { | ||||||
|  | 	g.handlers = append(g.handlers, handler) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *GameCoordinator) HandlePacket(packet *Packet) { | ||||||
|  | 	if packet.EMsg != EMsg_ClientFromGC { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	msg := new(CMsgGCClient) | ||||||
|  | 	packet.ReadProtoMsg(msg) | ||||||
|  |  | ||||||
|  | 	p, err := NewGCPacket(msg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		g.client.Errorf("Error reading GC message: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, handler := range g.handlers { | ||||||
|  | 		handler.HandleGCPacket(p) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (g *GameCoordinator) Write(msg IGCMsg) { | ||||||
|  | 	buf := new(bytes.Buffer) | ||||||
|  | 	msg.Serialize(buf) | ||||||
|  |  | ||||||
|  | 	msgType := msg.GetMsgType() | ||||||
|  | 	if msg.IsProto() { | ||||||
|  | 		msgType = msgType | 0x80000000 // mask with protoMask | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	g.client.Write(NewClientMsgProtobuf(EMsg_ClientToGC, &CMsgGCClient{ | ||||||
|  | 		Msgtype: proto.Uint32(msgType), | ||||||
|  | 		Appid:   proto.Uint32(msg.GetAppId()), | ||||||
|  | 		Payload: buf.Bytes(), | ||||||
|  | 	})) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Sets you in the given games. Specify none to quit all games. | ||||||
|  | func (g *GameCoordinator) SetGamesPlayed(appIds ...uint64) { | ||||||
|  | 	games := make([]*CMsgClientGamesPlayed_GamePlayed, 0) | ||||||
|  | 	for _, appId := range appIds { | ||||||
|  | 		games = append(games, &CMsgClientGamesPlayed_GamePlayed{ | ||||||
|  | 			GameId: proto.Uint64(appId), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	g.client.Write(NewClientMsgProtobuf(EMsg_ClientGamesPlayed, &CMsgClientGamesPlayed{ | ||||||
|  | 		GamesPlayed: games, | ||||||
|  | 	})) | ||||||
|  | } | ||||||
							
								
								
									
										295
									
								
								vendor/github.com/Philipp15b/go-steam/generator/generator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								vendor/github.com/Philipp15b/go-steam/generator/generator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | |||||||
|  | /* | ||||||
|  | This program generates the protobuf and SteamLanguage files from the SteamKit data. | ||||||
|  | */ | ||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/parser" | ||||||
|  | 	"go/token" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var printCommands = false | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	args := strings.Join(os.Args[1:], " ") | ||||||
|  |  | ||||||
|  | 	found := false | ||||||
|  | 	if strings.Contains(args, "clean") { | ||||||
|  | 		clean() | ||||||
|  | 		found = true | ||||||
|  | 	} | ||||||
|  | 	if strings.Contains(args, "steamlang") { | ||||||
|  | 		buildSteamLanguage() | ||||||
|  | 		found = true | ||||||
|  | 	} | ||||||
|  | 	if strings.Contains(args, "proto") { | ||||||
|  | 		buildProto() | ||||||
|  | 		found = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !found { | ||||||
|  | 		os.Stderr.WriteString("Invalid target!\nAvailable targets: clean, proto, steamlang\n") | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func clean() { | ||||||
|  | 	print("# Cleaning") | ||||||
|  | 	cleanGlob("../protocol/**/*.pb.go") | ||||||
|  | 	cleanGlob("../tf2/protocol/**/*.pb.go") | ||||||
|  | 	cleanGlob("../dota/protocol/**/*.pb.go") | ||||||
|  |  | ||||||
|  | 	os.Remove("../protocol/steamlang/enums.go") | ||||||
|  | 	os.Remove("../protocol/steamlang/messages.go") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func cleanGlob(pattern string) { | ||||||
|  | 	protos, _ := filepath.Glob(pattern) | ||||||
|  | 	for _, proto := range protos { | ||||||
|  | 		err := os.Remove(proto) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildSteamLanguage() { | ||||||
|  | 	print("# Building Steam Language") | ||||||
|  | 	exePath := "./GoSteamLanguageGenerator/bin/Debug/GoSteamLanguageGenerator.exe" | ||||||
|  |  | ||||||
|  | 	if runtime.GOOS != "windows" { | ||||||
|  | 		execute("mono", exePath, "./SteamKit", "../protocol/steamlang") | ||||||
|  | 	} else { | ||||||
|  | 		execute(exePath, "./SteamKit", "../protocol/steamlang") | ||||||
|  | 	} | ||||||
|  | 	execute("gofmt", "-w", "../protocol/steamlang/enums.go", "../protocol/steamlang/messages.go") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildProto() { | ||||||
|  | 	print("# Building Protobufs") | ||||||
|  |  | ||||||
|  | 	buildProtoMap("steamclient", clientProtoFiles, "../protocol/protobuf") | ||||||
|  | 	buildProtoMap("tf", tf2ProtoFiles, "../tf2/protocol/protobuf") | ||||||
|  | 	buildProtoMap("dota", dotaProtoFiles, "../dota/protocol/protobuf") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildProtoMap(srcSubdir string, files map[string]string, outDir string) { | ||||||
|  | 	os.MkdirAll(outDir, os.ModePerm) | ||||||
|  | 	for proto, out := range files { | ||||||
|  | 		full := filepath.Join(outDir, out) | ||||||
|  | 		compileProto("SteamKit/Resources/Protobufs", srcSubdir, proto, full) | ||||||
|  | 		fixProto(full) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Maps the proto files to their target files. | ||||||
|  | // See `SteamKit/Resources/Protobufs/steamclient/generate-base.bat` for reference. | ||||||
|  | var clientProtoFiles = map[string]string{ | ||||||
|  | 	"steammessages_base.proto":   "base.pb.go", | ||||||
|  | 	"encrypted_app_ticket.proto": "app_ticket.pb.go", | ||||||
|  |  | ||||||
|  | 	"steammessages_clientserver.proto":   "client_server.pb.go", | ||||||
|  | 	"steammessages_clientserver_2.proto": "client_server_2.pb.go", | ||||||
|  |  | ||||||
|  | 	"content_manifest.proto": "content_manifest.pb.go", | ||||||
|  |  | ||||||
|  | 	"steammessages_unified_base.steamclient.proto":      "unified/base.pb.go", | ||||||
|  | 	"steammessages_cloud.steamclient.proto":             "unified/cloud.pb.go", | ||||||
|  | 	"steammessages_credentials.steamclient.proto":       "unified/credentials.pb.go", | ||||||
|  | 	"steammessages_deviceauth.steamclient.proto":        "unified/deviceauth.pb.go", | ||||||
|  | 	"steammessages_gamenotifications.steamclient.proto": "unified/gamenotifications.pb.go", | ||||||
|  | 	"steammessages_offline.steamclient.proto":           "unified/offline.pb.go", | ||||||
|  | 	"steammessages_parental.steamclient.proto":          "unified/parental.pb.go", | ||||||
|  | 	"steammessages_partnerapps.steamclient.proto":       "unified/partnerapps.pb.go", | ||||||
|  | 	"steammessages_player.steamclient.proto":            "unified/player.pb.go", | ||||||
|  | 	"steammessages_publishedfile.steamclient.proto":     "unified/publishedfile.pb.go", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var tf2ProtoFiles = map[string]string{ | ||||||
|  | 	"base_gcmessages.proto":  "base.pb.go", | ||||||
|  | 	"econ_gcmessages.proto":  "econ.pb.go", | ||||||
|  | 	"gcsdk_gcmessages.proto": "gcsdk.pb.go", | ||||||
|  | 	"tf_gcmessages.proto":    "tf.pb.go", | ||||||
|  | 	"gcsystemmsgs.proto":     "system.pb.go", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var dotaProtoFiles = map[string]string{ | ||||||
|  | 	"base_gcmessages.proto":                "base.pb.go", | ||||||
|  | 	"econ_gcmessages.proto":                "econ.pb.go", | ||||||
|  | 	"gcsdk_gcmessages.proto":               "gcsdk.pb.go", | ||||||
|  | 	"dota_gcmessages_common.proto":         "dota_common.pb.go", | ||||||
|  | 	"dota_gcmessages_client.proto":         "dota_client.pb.go", | ||||||
|  | 	"dota_gcmessages_client_fantasy.proto": "dota_client_fantasy.pb.go", | ||||||
|  | 	"gcsystemmsgs.proto":                   "system.pb.go", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func compileProto(srcBase, srcSubdir, proto, target string) { | ||||||
|  | 	outDir, _ := filepath.Split(target) | ||||||
|  | 	err := os.MkdirAll(outDir, os.ModePerm) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	execute("protoc", "--go_out="+outDir, "-I="+srcBase+"/"+srcSubdir, "-I="+srcBase, filepath.Join(srcBase, srcSubdir, proto)) | ||||||
|  | 	out := strings.Replace(filepath.Join(outDir, proto), ".proto", ".pb.go", 1) | ||||||
|  | 	err = forceRename(out, target) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func forceRename(from, to string) error { | ||||||
|  | 	if from != to { | ||||||
|  | 		os.Remove(to) | ||||||
|  | 	} | ||||||
|  | 	return os.Rename(from, to) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var pkgRegex = regexp.MustCompile(`(package \w+)`) | ||||||
|  | var pkgCommentRegex = regexp.MustCompile(`(?s)(\/\*.*?\*\/\n)package`) | ||||||
|  | var unusedImportCommentRegex = regexp.MustCompile("// discarding unused import .*\n") | ||||||
|  | var fileDescriptorVarRegex = regexp.MustCompile(`fileDescriptor\d+`) | ||||||
|  |  | ||||||
|  | func fixProto(path string) { | ||||||
|  | 	// goprotobuf is really bad at dependencies, so we must fix them manually... | ||||||
|  | 	// It tries to load each dependency of a file as a seperate package (but in a very, very wrong way). | ||||||
|  | 	// Because we want some files in the same package, we'll remove those imports to local files. | ||||||
|  |  | ||||||
|  | 	file, err := ioutil.ReadFile(path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fset := token.NewFileSet() | ||||||
|  | 	f, err := parser.ParseFile(fset, path, file, parser.ImportsOnly) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("Error parsing " + path + ": " + err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	importsToRemove := make([]*ast.ImportSpec, 0) | ||||||
|  | 	for _, i := range f.Imports { | ||||||
|  | 		// We remove all local imports | ||||||
|  | 		if i.Path.Value == "\".\"" { | ||||||
|  | 			importsToRemove = append(importsToRemove, i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, itr := range importsToRemove { | ||||||
|  | 		// remove the package name from all types | ||||||
|  | 		file = bytes.Replace(file, []byte(itr.Name.Name+"."), []byte{}, -1) | ||||||
|  | 		// and remove the import itself | ||||||
|  | 		file = bytes.Replace(file, []byte(fmt.Sprintf("import %v %v\n", itr.Name.Name, itr.Path.Value)), []byte{}, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// remove the package comment because it just includes a list of all messages and | ||||||
|  | 	// collides not only with the other compiled protobuf files, but also our own documentation. | ||||||
|  | 	file = cutAllSubmatch(pkgCommentRegex, file, 1) | ||||||
|  |  | ||||||
|  | 	// remove warnings | ||||||
|  | 	file = unusedImportCommentRegex.ReplaceAllLiteral(file, []byte{}) | ||||||
|  |  | ||||||
|  | 	// fix the package name | ||||||
|  | 	file = pkgRegex.ReplaceAll(file, []byte("package "+inferPackageName(path))) | ||||||
|  |  | ||||||
|  | 	// fix the google dependency; | ||||||
|  | 	// we just reuse the one from protoc-gen-go | ||||||
|  | 	file = bytes.Replace(file, []byte("google/protobuf"), []byte("github.com/golang/protobuf/protoc-gen-go/descriptor"), -1) | ||||||
|  |  | ||||||
|  | 	// we need to prefix local variables created by protoc-gen-go so that they don't clash with others in the same package | ||||||
|  | 	filename := strings.Split(filepath.Base(path), ".")[0] | ||||||
|  | 	file = fileDescriptorVarRegex.ReplaceAllFunc(file, func(match []byte) []byte { | ||||||
|  | 		return []byte(filename + "_" + string(match)) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	err = ioutil.WriteFile(path, file, os.ModePerm) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func inferPackageName(path string) string { | ||||||
|  | 	pieces := strings.Split(path, string(filepath.Separator)) | ||||||
|  | 	return pieces[len(pieces)-2] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func cutAllSubmatch(r *regexp.Regexp, b []byte, n int) []byte { | ||||||
|  | 	i := r.FindSubmatchIndex(b) | ||||||
|  | 	return bytesCut(b, i[2*n], i[2*n+1]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Removes the given section from the byte array | ||||||
|  | func bytesCut(b []byte, from, to int) []byte { | ||||||
|  | 	buf := new(bytes.Buffer) | ||||||
|  | 	buf.Write(b[:from]) | ||||||
|  | 	buf.Write(b[to:]) | ||||||
|  | 	return buf.Bytes() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func print(text string) { os.Stdout.WriteString(text + "\n") } | ||||||
|  |  | ||||||
|  | func printerr(text string) { os.Stderr.WriteString(text + "\n") } | ||||||
|  |  | ||||||
|  | // This writer appends a "> " after every newline so that the outpout appears quoted. | ||||||
|  | type QuotedWriter struct { | ||||||
|  | 	w       io.Writer | ||||||
|  | 	started bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewQuotedWriter(w io.Writer) *QuotedWriter { | ||||||
|  | 	return &QuotedWriter{w, false} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *QuotedWriter) Write(p []byte) (n int, err error) { | ||||||
|  | 	if !w.started { | ||||||
|  | 		_, err = w.w.Write([]byte("> ")) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return n, err | ||||||
|  | 		} | ||||||
|  | 		w.started = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i, c := range p { | ||||||
|  | 		if c == '\n' { | ||||||
|  | 			nw, err := w.w.Write(p[n : i+1]) | ||||||
|  | 			n += nw | ||||||
|  | 			if err != nil { | ||||||
|  | 				return n, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			_, err = w.w.Write([]byte("> ")) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return n, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if n != len(p) { | ||||||
|  | 		nw, err := w.w.Write(p[n:len(p)]) | ||||||
|  | 		n += nw | ||||||
|  | 		return n, err | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func execute(command string, args ...string) { | ||||||
|  | 	if printCommands { | ||||||
|  | 		print(command + " " + strings.Join(args, " ")) | ||||||
|  | 	} | ||||||
|  | 	cmd := exec.Command(command, args...) | ||||||
|  | 	cmd.Stdout = NewQuotedWriter(os.Stdout) | ||||||
|  | 	cmd.Stderr = NewQuotedWriter(os.Stderr) | ||||||
|  | 	err := cmd.Run() | ||||||
|  | 	if err != nil { | ||||||
|  | 		printerr(err.Error()) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										210
									
								
								vendor/github.com/Philipp15b/go-steam/gsbot/gsbot.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								vendor/github.com/Philipp15b/go-steam/gsbot/gsbot.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | |||||||
|  | // The GsBot package contains some useful utilites for working with the | ||||||
|  | // steam package. It implements authentication with sentries, server lists and | ||||||
|  | // logging messages and events. | ||||||
|  | // | ||||||
|  | // Every module is optional and requires an instance of the GsBot struct. | ||||||
|  | // Should a module have a `HandlePacket` method, you must register it with the | ||||||
|  | // steam.Client with `RegisterPacketHandler`. Any module with a `HandleEvent` | ||||||
|  | // method must be integrated into your event loop and should be called for each | ||||||
|  | // event you receive. | ||||||
|  | package gsbot | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"math/rand" | ||||||
|  | 	"net" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"reflect" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/Philipp15b/go-steam" | ||||||
|  | 	"github.com/Philipp15b/go-steam/netutil" | ||||||
|  | 	"github.com/Philipp15b/go-steam/protocol" | ||||||
|  | 	"github.com/davecgh/go-spew/spew" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Base structure holding common data among GsBot modules. | ||||||
|  | type GsBot struct { | ||||||
|  | 	Client *steam.Client | ||||||
|  | 	Log    *log.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Creates a new GsBot with a new steam.Client where logs are written to stdout. | ||||||
|  | func Default() *GsBot { | ||||||
|  | 	return &GsBot{ | ||||||
|  | 		steam.NewClient(), | ||||||
|  | 		log.New(os.Stdout, "", 0), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This module handles authentication. It logs on automatically after a ConnectedEvent | ||||||
|  | // and saves the sentry data to a file which is also used for logon if available. | ||||||
|  | // If you're logging on for the first time Steam may require an authcode. You can then | ||||||
|  | // connect again with the new logon details. | ||||||
|  | type Auth struct { | ||||||
|  | 	bot             *GsBot | ||||||
|  | 	details         *LogOnDetails | ||||||
|  | 	sentryPath      string | ||||||
|  | 	machineAuthHash []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewAuth(bot *GsBot, details *LogOnDetails, sentryPath string) *Auth { | ||||||
|  | 	return &Auth{ | ||||||
|  | 		bot:        bot, | ||||||
|  | 		details:    details, | ||||||
|  | 		sentryPath: sentryPath, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type LogOnDetails struct { | ||||||
|  | 	Username      string | ||||||
|  | 	Password      string | ||||||
|  | 	AuthCode      string | ||||||
|  | 	TwoFactorCode string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This is called automatically after every ConnectedEvent, but must be called once again manually | ||||||
|  | // with an authcode if Steam requires it when logging on for the first time. | ||||||
|  | func (a *Auth) LogOn(details *LogOnDetails) { | ||||||
|  | 	a.details = details | ||||||
|  | 	sentry, err := ioutil.ReadFile(a.sentryPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		a.bot.Log.Printf("Error loading sentry file from path %v - This is normal if you're logging in for the first time.\n", a.sentryPath) | ||||||
|  | 	} | ||||||
|  | 	a.bot.Client.Auth.LogOn(&steam.LogOnDetails{ | ||||||
|  | 		Username:       details.Username, | ||||||
|  | 		Password:       details.Password, | ||||||
|  | 		SentryFileHash: sentry, | ||||||
|  | 		AuthCode:       details.AuthCode, | ||||||
|  | 		TwoFactorCode:  details.TwoFactorCode, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *Auth) HandleEvent(event interface{}) { | ||||||
|  | 	switch e := event.(type) { | ||||||
|  | 	case *steam.ConnectedEvent: | ||||||
|  | 		a.LogOn(a.details) | ||||||
|  | 	case *steam.LoggedOnEvent: | ||||||
|  | 		a.bot.Log.Printf("Logged on (%v) with SteamId %v and account flags %v", e.Result, e.ClientSteamId, e.AccountFlags) | ||||||
|  | 	case *steam.MachineAuthUpdateEvent: | ||||||
|  | 		a.machineAuthHash = e.Hash | ||||||
|  | 		err := ioutil.WriteFile(a.sentryPath, e.Hash, 0666) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This module saves the server list from ClientCMListEvent and uses | ||||||
|  | // it when you call `Connect()`. | ||||||
|  | type ServerList struct { | ||||||
|  | 	bot      *GsBot | ||||||
|  | 	listPath string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewServerList(bot *GsBot, listPath string) *ServerList { | ||||||
|  | 	return &ServerList{ | ||||||
|  | 		bot, | ||||||
|  | 		listPath, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ServerList) HandleEvent(event interface{}) { | ||||||
|  | 	switch e := event.(type) { | ||||||
|  | 	case *steam.ClientCMListEvent: | ||||||
|  | 		d, err := json.Marshal(e.Addresses) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 		err = ioutil.WriteFile(s.listPath, d, 0666) | ||||||
|  | 		if err != nil { | ||||||
|  | 			panic(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ServerList) Connect() (bool, error) { | ||||||
|  | 	return s.ConnectBind(nil) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ServerList) ConnectBind(laddr *net.TCPAddr) (bool, error) { | ||||||
|  | 	d, err := ioutil.ReadFile(s.listPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.bot.Log.Println("Connecting to random server.") | ||||||
|  | 		s.bot.Client.Connect() | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	var addrs []*netutil.PortAddr | ||||||
|  | 	err = json.Unmarshal(d, &addrs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	raddr := addrs[rand.Intn(len(addrs))] | ||||||
|  | 	s.bot.Log.Printf("Connecting to %v from server list\n", raddr) | ||||||
|  | 	s.bot.Client.ConnectToBind(raddr, laddr) | ||||||
|  | 	return true, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This module logs incoming packets and events to a directory. | ||||||
|  | type Debug struct { | ||||||
|  | 	packetId, eventId uint64 | ||||||
|  | 	bot               *GsBot | ||||||
|  | 	base              string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewDebug(bot *GsBot, base string) (*Debug, error) { | ||||||
|  | 	base = path.Join(base, fmt.Sprint(time.Now().Unix())) | ||||||
|  | 	err := os.MkdirAll(path.Join(base, "events"), 0700) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	err = os.MkdirAll(path.Join(base, "packets"), 0700) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &Debug{ | ||||||
|  | 		0, 0, | ||||||
|  | 		bot, | ||||||
|  | 		base, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Debug) HandlePacket(packet *protocol.Packet) { | ||||||
|  | 	d.packetId++ | ||||||
|  | 	name := path.Join(d.base, "packets", fmt.Sprintf("%d_%d_%s", time.Now().Unix(), d.packetId, packet.EMsg)) | ||||||
|  |  | ||||||
|  | 	text := packet.String() + "\n\n" + hex.Dump(packet.Data) | ||||||
|  | 	err := ioutil.WriteFile(name+".txt", []byte(text), 0666) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = ioutil.WriteFile(name+".bin", packet.Data, 0666) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Debug) HandleEvent(event interface{}) { | ||||||
|  | 	d.eventId++ | ||||||
|  | 	name := fmt.Sprintf("%d_%d_%s.txt", time.Now().Unix(), d.eventId, name(event)) | ||||||
|  | 	err := ioutil.WriteFile(path.Join(d.base, "events", name), []byte(spew.Sdump(event)), 0666) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func name(obj interface{}) string { | ||||||
|  | 	val := reflect.ValueOf(obj) | ||||||
|  | 	ind := reflect.Indirect(val) | ||||||
|  | 	if ind.IsValid() { | ||||||
|  | 		return ind.Type().Name() | ||||||
|  | 	} else { | ||||||
|  | 		return val.Type().Name() | ||||||
|  | 	} | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user