Merge pull request #26 from DmitryZagorulko/master
add gin, errors handler, go mod and validation
This commit is contained in:
commit
5e52a3ddd3
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@ config_test.yml
|
|||||||
.idea/
|
.idea/
|
||||||
/bin/*
|
/bin/*
|
||||||
*.xml
|
*.xml
|
||||||
|
/vendor
|
||||||
|
21
Makefile
21
Makefile
@ -1,20 +1,14 @@
|
|||||||
ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
SRC_DIR=$(ROOT_DIR)
|
SRC_DIR=$(ROOT_DIR)/src
|
||||||
MIGRATIONS_DIR=$(ROOT_DIR)/migrations
|
MIGRATIONS_DIR=$(ROOT_DIR)/migrations
|
||||||
CONFIG_FILE=$(ROOT_DIR)/config.yml
|
CONFIG_FILE=$(ROOT_DIR)/config.yml
|
||||||
CONFIG_TEST_FILE=$(ROOT_DIR)/config_test.yml
|
CONFIG_TEST_FILE=$(ROOT_DIR)/config_test.yml
|
||||||
BIN=$(ROOT_DIR)/bin/transport
|
BIN=$(ROOT_DIR)/bin/transport
|
||||||
REVISION=$(shell git describe --tags 2>/dev/null || git log --format="v0.0-%h" -n 1 || echo "v0.0-unknown")
|
REVISION=$(shell git describe --tags 2>/dev/null || git log --format="v0.0-%h" -n 1 || echo "v0.0-unknown")
|
||||||
|
|
||||||
ifndef GOPATH
|
|
||||||
$(error GOPATH must be defined)
|
|
||||||
endif
|
|
||||||
|
|
||||||
export GOPATH := $(GOPATH):$(ROOT_DIR)
|
|
||||||
|
|
||||||
build: deps fmt
|
build: deps fmt
|
||||||
@echo "==> Building"
|
@echo "==> Building"
|
||||||
@go build -o $(BIN) -ldflags "-X common.build=${REVISION}" .
|
@cd $(SRC_DIR) && CGO_ENABLED=0 go build -o $(BIN) -ldflags "-X common.build=${REVISION}" .
|
||||||
@echo $(BIN)
|
@echo $(BIN)
|
||||||
|
|
||||||
run: migrate
|
run: migrate
|
||||||
@ -23,23 +17,20 @@ run: migrate
|
|||||||
|
|
||||||
test: deps fmt
|
test: deps fmt
|
||||||
@echo "==> Running tests"
|
@echo "==> Running tests"
|
||||||
@cd $(SRC_DIR) && go test ./... -v -cpu 2
|
@cd $(ROOT_DIR) && go test ./... -v -cpu 2
|
||||||
|
|
||||||
jenkins_test: deps
|
jenkins_test: deps
|
||||||
@echo "==> Running tests (result in test-report.xml)"
|
@echo "==> Running tests (result in test-report.xml)"
|
||||||
@go get -v -u github.com/jstemmer/go-junit-report
|
@go get -v -u github.com/jstemmer/go-junit-report
|
||||||
@cd $(SRC_DIR) && go test ./... -v -cpu 2 -cover -race | go-junit-report -set-exit-code > $(SRC_DIR)/test-report.xml
|
@cd $(ROOT_DIR) && go test ./... -v -cpu 2 -cover -race | go-junit-report -set-exit-code > $(ROOT_DIR)/test-report.xml
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@echo "==> Running gofmt"
|
@echo "==> Running gofmt"
|
||||||
@gofmt -l -s -w $(SRC_DIR)
|
@gofmt -l -s -w $(ROOT_DIR)
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@echo "==> Installing dependencies"
|
@echo "==> Installing dependencies"
|
||||||
$(eval DEPS:=$(shell cd $(SRC_DIR) \
|
@go mod tidy
|
||||||
&& go list -f '{{join .Imports "\n"}}{{ "\n" }}{{join .TestImports "\n"}}' ./... \
|
|
||||||
| sort | uniq | tr '\r' '\n' | paste -sd ' ' -))
|
|
||||||
@go get -d -v $(DEPS)
|
|
||||||
|
|
||||||
migrate: build
|
migrate: build
|
||||||
${BIN} --config $(CONFIG_FILE) migrate -p $(MIGRATIONS_DIR)
|
${BIN} --config $(CONFIG_FILE) migrate -p $(MIGRATIONS_DIR)
|
||||||
|
@ -5,6 +5,11 @@ http_server:
|
|||||||
host: ~
|
host: ~
|
||||||
listen: :3001
|
listen: :3001
|
||||||
|
|
||||||
|
transport_info:
|
||||||
|
name: Telegram
|
||||||
|
code: mg-telegram
|
||||||
|
logo_path: /static/telegram_logo.svg
|
||||||
|
|
||||||
sentry_dsn: ~
|
sentry_dsn: ~
|
||||||
|
|
||||||
log_level: 5
|
log_level: 5
|
||||||
@ -20,3 +25,7 @@ config_aws:
|
|||||||
bucket: ~
|
bucket: ~
|
||||||
folder_name: ~
|
folder_name: ~
|
||||||
content_type: image/jpeg
|
content_type: image/jpeg
|
||||||
|
|
||||||
|
credentials:
|
||||||
|
- "/api/integration-modules/{code}"
|
||||||
|
- "/api/integration-modules/{code}/edit"
|
||||||
|
@ -5,6 +5,11 @@ http_server:
|
|||||||
host: ~
|
host: ~
|
||||||
listen: :3002
|
listen: :3002
|
||||||
|
|
||||||
|
transport_info:
|
||||||
|
name: Telegram
|
||||||
|
code: mg-telegram
|
||||||
|
logo_path: /static/telegram_logo.svg
|
||||||
|
|
||||||
sentry_dsn: ~
|
sentry_dsn: ~
|
||||||
|
|
||||||
log_level: 5
|
log_level: 5
|
||||||
@ -20,3 +25,7 @@ config_aws:
|
|||||||
bucket: ~
|
bucket: ~
|
||||||
folder_name: ~
|
folder_name: ~
|
||||||
content_type: image/jpeg
|
content_type: image/jpeg
|
||||||
|
|
||||||
|
credentials:
|
||||||
|
- "/api/integration-modules/{code}"
|
||||||
|
- "/api/integration-modules/{code}/edit"
|
||||||
|
@ -11,9 +11,11 @@ services:
|
|||||||
- ${POSTGRES_ADDRESS:-127.0.0.1:5434}:${POSTGRES_PORT:-5432}
|
- ${POSTGRES_ADDRESS:-127.0.0.1:5434}:${POSTGRES_PORT:-5432}
|
||||||
|
|
||||||
mg_telegram_test:
|
mg_telegram_test:
|
||||||
image: golang:1.9.3-stretch
|
image: golang:1.11beta3-stretch
|
||||||
working_dir: /mgtg
|
working_dir: /mgtg
|
||||||
user: ${UID:-1000}:${GID:-1000}
|
user: ${UID:-1000}:${GID:-1000}
|
||||||
|
environment:
|
||||||
|
GOCACHE: /go
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/mgtg/
|
- ./:/mgtg/
|
||||||
- ./static:/static/
|
- ./static:/static/
|
||||||
|
@ -11,9 +11,11 @@ services:
|
|||||||
- ${POSTGRES_ADDRESS:-127.0.0.1:5434}:${POSTGRES_PORT:-5432}
|
- ${POSTGRES_ADDRESS:-127.0.0.1:5434}:${POSTGRES_PORT:-5432}
|
||||||
|
|
||||||
mg_telegram:
|
mg_telegram:
|
||||||
image: golang:1.9.3-stretch
|
image: golang:1.11beta3-stretch
|
||||||
working_dir: /mgtg
|
working_dir: /mgtg
|
||||||
user: ${UID:-1000}:${GID:-1000}
|
user: ${UID:-1000}:${GID:-1000}
|
||||||
|
environment:
|
||||||
|
GOCACHE: /go
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/mgtg
|
- ./:/mgtg
|
||||||
- ./static:/static/
|
- ./static:/static/
|
||||||
|
65
go.mod
Normal file
65
go.mod
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
module github.com/retailcrm/mg-transport-telegram
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.26.0 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.4.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go v1.15.18
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20180707235734-242fa5aa1b45 // indirect
|
||||||
|
github.com/docker/distribution v2.6.2+incompatible // indirect
|
||||||
|
github.com/docker/docker v1.13.1 // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-units v0.3.3 // indirect
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180801005657-7535a8fa2ace
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180607024123-41d1d62d1df3
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.3.0
|
||||||
|
github.com/go-ini/ini v1.38.2 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0 // indirect
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180602093832-4c16a90966d1
|
||||||
|
github.com/golang-migrate/migrate v3.4.0+incompatible
|
||||||
|
github.com/golang/protobuf v1.2.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.2.0 // indirect
|
||||||
|
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20180820052304-89baedc74dd7 // indirect
|
||||||
|
github.com/h2non/gock v1.0.9
|
||||||
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
|
github.com/jinzhu/gorm v1.9.1
|
||||||
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
||||||
|
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae // indirect
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b // indirect
|
||||||
|
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||||
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
|
github.com/kr/pty v1.1.2 // indirect
|
||||||
|
github.com/lib/pq v1.0.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3 // indirect
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/retailcrm/api-client-go v1.0.4
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.2
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf // indirect
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
||||||
|
github.com/stevvooe/resumable v0.0.0-20170302213456-2aaf90b2ceea // indirect
|
||||||
|
github.com/stretchr/testify v1.2.2
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||||
|
github.com/ugorji/go v1.1.1 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
|
||||||
|
golang.org/x/net v0.0.0-20180821023952-922f4815f713 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339 // indirect
|
||||||
|
golang.org/x/text v0.3.0
|
||||||
|
google.golang.org/appengine v1.1.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||||
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
|
)
|
135
go.sum
Normal file
135
go.sum
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
|
||||||
|
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.10 h1:NrhPZI+cp3Fjmm5t/PZkVuir43JIRLZG/PSKK7atSfw=
|
||||||
|
github.com/Microsoft/go-winio v0.4.10/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
|
github.com/aws/aws-sdk-go v1.15.16 h1:h3Wt98XmE8BWKZKLNhw1bl+ABVMvdWwetYM/rs88jMY=
|
||||||
|
github.com/aws/aws-sdk-go v1.15.16/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
|
github.com/aws/aws-sdk-go v1.15.18/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg=
|
||||||
|
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20180707235734-242fa5aa1b45 h1:UW8VerkZA1zCt3uWhQ2wbMae76OLn7s7Utz8wyKtJUk=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20180707235734-242fa5aa1b45/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||||
|
github.com/docker/distribution v2.6.2+incompatible h1:4FI6af79dfCS/CYb+RRtkSHw3q1L/bnDjG1PcPZtQhM=
|
||||||
|
github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v1.13.1 h1:5VBhsO6ckUxB0A8CE5LlUJdXzik9cbEbBTQ/ggeml7M=
|
||||||
|
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||||
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180801005657-7535a8fa2ace h1:M5ZUuRO+XFqhTa9PlaqyWgfzMNWKSraCWm7z4PzM1GA=
|
||||||
|
github.com/getsentry/raven-go v0.0.0-20180801005657-7535a8fa2ace/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180607024123-41d1d62d1df3 h1:nKrMd5DcMWMxZbGzSEscZ7zsnA0vLf46rGKV1R7c4Kc=
|
||||||
|
github.com/gin-contrib/multitemplate v0.0.0-20180607024123-41d1d62d1df3/go.mod h1:62qM8p4crGvNKE413gTzn4eMFin1VOJfMDWMRzHdvqM=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
|
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||||
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
|
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
|
||||||
|
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180602093832-4c16a90966d1 h1:FlRoyZCY3snE+M9jTruqOzPwZg8KIwQBXr//t215K8E=
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180602093832-4c16a90966d1/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||||
|
github.com/golang-migrate/migrate v3.4.0+incompatible h1:9yjg5lYsbeEpWXGc80RylvPMKZ0tZEGsyO3CpYLK3jU=
|
||||||
|
github.com/golang-migrate/migrate v3.4.0+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
|
||||||
|
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20180820052304-89baedc74dd7 h1:WF7x3tAe0mEb4wf/yhSThHwZYQIjVmEGSbAH9hzOeZQ=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20180820052304-89baedc74dd7/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/h2non/gock v1.0.9 h1:17gCehSo8ZOgEsFKpQgqHiR7VLyjxdAG3lkhVvO9QZU=
|
||||||
|
github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jinzhu/gorm v1.9.1 h1:lDSDtsCt5AGGSKTs8AHlSDbbgif4G4+CKJ8ETBDVHTA=
|
||||||
|
github.com/jinzhu/gorm v1.9.1/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||||
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
|
||||||
|
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae h1:8bBMcboXYVuo0WYH+rPe5mB8obO89a993hdTZ3phTjc=
|
||||||
|
github.com/jinzhu/now v0.0.0-20180511015916-ed742868f2ae/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||||
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.2/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v0.0.0-20180523175426-90697d60dd84 h1:it29sI2IM490luSc3RAhp5WuCYnc6RtbfLVAB7nmC5M=
|
||||||
|
github.com/lib/pq v0.0.0-20180523175426-90697d60dd84/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3 h1:YFBuDro+e1UCqlJpDWGucQaO/UNhBX1GlS8Du0GNfPw=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180718012357-94122c33edd3/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/retailcrm/api-client-go v1.0.4 h1:pYlkdQhesc8MN/huU4qp9XpLLRxfr0SIICf2RmEVnoA=
|
||||||
|
github.com/retailcrm/api-client-go v1.0.4/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c=
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.2 h1:PL/IjYhsiD3LZ08YXTZGbwLmnOL1ulOUr8wGWE28g9A=
|
||||||
|
github.com/retailcrm/mg-transport-api-client-go v1.1.2/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
||||||
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
|
github.com/stevvooe/resumable v0.0.0-20170302213456-2aaf90b2ceea h1:KR90QmB10LunzqE3lvSRq0epy66wkQi2bDmkJdkkxi8=
|
||||||
|
github.com/stevvooe/resumable v0.0.0-20170302213456-2aaf90b2ceea/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||||
|
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||||
|
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
|
||||||
|
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/net v0.0.0-20180821023952-922f4815f713 h1:rMJUcaDGbG+X967I4zGKCq5laYqcGKJmpB+3jhpOhPw=
|
||||||
|
golang.org/x/net v0.0.0-20180821023952-922f4815f713/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180821044426-4ea2f632f6e9 h1:0RHCP7KEw0rDuVXXaT2gfV77uu6lTKa5aItB+EoFbQk=
|
||||||
|
golang.org/x/sys v0.0.0-20180821044426-4ea2f632f6e9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
594
routing.go
594
routing.go
@ -1,594 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"github.com/retailcrm/api-client-go/v5"
|
|
||||||
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
validPath = regexp.MustCompile(`^/(save|settings|telegram)/([a-zA-Z0-9-:_+]+)$`)
|
|
||||||
localizer *i18n.Localizer
|
|
||||||
bundle = &i18n.Bundle{DefaultLanguage: language.English}
|
|
||||||
matcher = language.NewMatcher([]language.Tag{
|
|
||||||
language.English,
|
|
||||||
language.Russian,
|
|
||||||
language.Spanish,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
|
||||||
files, err := ioutil.ReadDir("translate")
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
if !f.IsDir() {
|
|
||||||
bundle.MustLoadMessageFile("translate/" + f.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setLocale(al string) {
|
|
||||||
tag, _ := language.MatchStrings(matcher, al)
|
|
||||||
localizer = i18n.NewLocalizer(bundle, tag.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response struct
|
|
||||||
type Response struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func setWrapperRoutes() {
|
|
||||||
http.HandleFunc("/", connectHandler)
|
|
||||||
http.HandleFunc("/settings/", makeHandler(settingsHandler))
|
|
||||||
http.HandleFunc("/save/", saveHandler)
|
|
||||||
http.HandleFunc("/create/", createHandler)
|
|
||||||
http.HandleFunc("/actions/activity", activityHandler)
|
|
||||||
http.HandleFunc("/add-bot/", addBotHandler)
|
|
||||||
http.HandleFunc("/delete-bot/", deleteBotHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, tmpl string, c interface{}) {
|
|
||||||
tm, err := template.ParseFiles("templates/layout.html", "templates/"+tmpl+".html")
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tm.Execute(w, &c)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
m := validPath.FindStringSubmatch(r.URL.Path)
|
|
||||||
if m == nil {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
raven.CapturePanic(func() {
|
|
||||||
fn(w, r, m[2])
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
|
|
||||||
account := r.URL.Query()
|
|
||||||
rx := regexp.MustCompile(`/+$`)
|
|
||||||
ra := rx.ReplaceAllString(account.Get("account"), ``)
|
|
||||||
p := Connection{
|
|
||||||
APIURL: ra,
|
|
||||||
}
|
|
||||||
|
|
||||||
res := struct {
|
|
||||||
Conn *Connection
|
|
||||||
Locale map[string]interface{}
|
|
||||||
}{
|
|
||||||
&p,
|
|
||||||
map[string]interface{}{
|
|
||||||
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
|
|
||||||
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
|
|
||||||
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
renderTemplate(w, "home", &res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addBotHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var b Bot
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &b)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Token == "" {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "no_bot_token"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := getBotByToken(b.Token)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cl.ID != 0 {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "bot_already_created"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot, err := tgbotapi.NewBotAPI(b.Token)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(b.Token, err.Error())
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_token"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.Debug = config.Debug
|
|
||||||
|
|
||||||
wr, err := bot.SetWebhook(tgbotapi.NewWebhook("https://" + config.HTTPServer.Host + "/telegram/" + bot.Token))
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(b.Token, err.Error())
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !wr.Ok {
|
|
||||||
logger.Error(b.Token, wr.ErrorCode, wr.Result)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_webhook"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Name = GetBotName(bot)
|
|
||||||
|
|
||||||
ch := v1.Channel{
|
|
||||||
Type: "telegram",
|
|
||||||
Events: []string{
|
|
||||||
"message_sent",
|
|
||||||
"message_updated",
|
|
||||||
"message_deleted",
|
|
||||||
"message_read",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
c := getConnectionById(b.ConnectionID)
|
|
||||||
|
|
||||||
var client = v1.New(c.MGURL, c.MGToken)
|
|
||||||
data, status, err := client.ActivateTransportChannel(ch)
|
|
||||||
if status != http.StatusCreated {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activating_channel"}), http.StatusBadRequest)
|
|
||||||
logger.Error(c.APIURL, status, err.Error(), data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Channel = data.ChannelID
|
|
||||||
|
|
||||||
err = c.createBot(b)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonString, err := json.Marshal(b)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_adding_bot"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
w.Write(jsonString)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteBotHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var b Bot
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &b)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := getConnectionById(b.ConnectionID)
|
|
||||||
if c.MGURL == "" || c.MGToken == "" {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"}), http.StatusBadRequest)
|
|
||||||
logger.Error(b.ID, "MGURL or MGToken is empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var client = v1.New(c.MGURL, c.MGToken)
|
|
||||||
|
|
||||||
data, status, err := client.DeactivateTransportChannel(getBotChannelByToken(b.Token))
|
|
||||||
if status > http.StatusOK {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_deactivating_channel"}), http.StatusBadRequest)
|
|
||||||
logger.Error(b.ID, status, err.Error(), data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = b.deleteBot()
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(b.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func settingsHandler(w http.ResponseWriter, r *http.Request, uid string) {
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
|
|
||||||
p := getConnection(uid)
|
|
||||||
if p.ID == 0 {
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bots := p.getBotsByClientID()
|
|
||||||
|
|
||||||
res := struct {
|
|
||||||
Conn *Connection
|
|
||||||
Bots Bots
|
|
||||||
Locale map[string]interface{}
|
|
||||||
}{
|
|
||||||
p,
|
|
||||||
bots,
|
|
||||||
map[string]interface{}{
|
|
||||||
"ButtonSave": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "button_save"}),
|
|
||||||
"ApiKey": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "api_key"}),
|
|
||||||
"TabSettings": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_settings"}),
|
|
||||||
"TabBots": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "tab_bots"}),
|
|
||||||
"TableName": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_name"}),
|
|
||||||
"TableToken": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_token"}),
|
|
||||||
"AddBot": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "add_bot"}),
|
|
||||||
"TableDelete": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "table_delete"}),
|
|
||||||
"Title": localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "title"}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplate(w, "form", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Connection
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &c)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = validateCrmSettings(c)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err, code := getAPIClient(c.APIURL, c.APIKEY)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.saveConnection()
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"})))
|
|
||||||
}
|
|
||||||
|
|
||||||
func createHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Connection
|
|
||||||
|
|
||||||
err = json.Unmarshal(body, &c)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_save"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ClientID = GenerateToken()
|
|
||||||
|
|
||||||
err = validateCrmSettings(c)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cl := getConnectionByURL(c.APIURL)
|
|
||||||
if cl.ID != 0 {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "connection_already_created"}), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err, code := getAPIClient(c.APIURL, c.APIKEY)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), code)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
integration := v5.IntegrationModule{
|
|
||||||
Code: transport,
|
|
||||||
IntegrationCode: transport,
|
|
||||||
Active: true,
|
|
||||||
Name: "Telegram",
|
|
||||||
ClientID: c.ClientID,
|
|
||||||
Logo: fmt.Sprintf(
|
|
||||||
"https://%s/static/telegram_logo.svg",
|
|
||||||
config.HTTPServer.Host,
|
|
||||||
),
|
|
||||||
BaseURL: fmt.Sprintf(
|
|
||||||
"https://%s",
|
|
||||||
config.HTTPServer.Host,
|
|
||||||
),
|
|
||||||
AccountURL: fmt.Sprintf(
|
|
||||||
"https://%s/settings/%s",
|
|
||||||
config.HTTPServer.Host,
|
|
||||||
c.ClientID,
|
|
||||||
),
|
|
||||||
Actions: map[string]string{"activity": "/actions/activity"},
|
|
||||||
Integrations: &v5.Integrations{
|
|
||||||
MgTransport: &v5.MgTransport{
|
|
||||||
WebhookUrl: fmt.Sprintf(
|
|
||||||
"https://%s/webhook/",
|
|
||||||
config.HTTPServer.Host,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
data, status, errr := client.IntegrationModuleEdit(integration)
|
|
||||||
if errr.RuntimeErr != nil {
|
|
||||||
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_integration"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, status, errr.RuntimeErr, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if status >= http.StatusBadRequest {
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_activity_mg"}), http.StatusBadRequest)
|
|
||||||
logger.Error(c.APIURL, status, errr.ApiErr, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MGURL = data.Info["baseUrl"]
|
|
||||||
c.MGToken = data.Info["token"]
|
|
||||||
c.Active = true
|
|
||||||
|
|
||||||
err = c.createConnection()
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := struct {
|
|
||||||
Url string
|
|
||||||
Message string
|
|
||||||
}{
|
|
||||||
Url: "/settings/" + c.ClientID,
|
|
||||||
Message: localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "successful"}),
|
|
||||||
}
|
|
||||||
|
|
||||||
jss, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
http.Error(w, localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "error_creating_connection"}), http.StatusInternalServerError)
|
|
||||||
logger.Error(c.APIURL, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusFound)
|
|
||||||
w.Write(jss)
|
|
||||||
}
|
|
||||||
|
|
||||||
func activityHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
setLocale(r.Header.Get("Accept-Language"))
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
res := Response{Success: false}
|
|
||||||
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "set_method"})
|
|
||||||
jsonString, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(jsonString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ParseForm()
|
|
||||||
var rec v5.Activity
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(r.FormValue("activity")), &rec)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "wrong_data"})
|
|
||||||
jsonString, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(jsonString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := getConnection(r.FormValue("clientId"))
|
|
||||||
c.Active = rec.Active && !rec.Freeze
|
|
||||||
|
|
||||||
if err := c.setConnectionActivity(); err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
res.Error = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "wrong_data"})
|
|
||||||
jsonString, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write(jsonString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Success = true
|
|
||||||
jsonString, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(jsonString)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCrmSettings(c Connection) error {
|
|
||||||
if c.APIURL == "" || c.APIKEY == "" {
|
|
||||||
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "missing_url_key"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
if res, _ := regexp.MatchString(`https://?[\da-z\.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`, c.APIURL); !res {
|
|
||||||
return errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url"}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAPIClient(url, key string) (*v5.Client, error, int) {
|
|
||||||
client := v5.New(url, key)
|
|
||||||
|
|
||||||
cr, status, errr := client.APICredentials()
|
|
||||||
if errr.RuntimeErr != nil {
|
|
||||||
raven.CaptureErrorAndWait(errr.RuntimeErr, nil)
|
|
||||||
logger.Error(url, status, errr.RuntimeErr, cr)
|
|
||||||
return nil, errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "not_found_account"})), http.StatusInternalServerError
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cr.Success {
|
|
||||||
logger.Error(url, status, errr.ApiErr, cr)
|
|
||||||
return nil, errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "incorrect_url_key"})), http.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := checkCredentials(cr.Credentials); len(res) != 0 {
|
|
||||||
logger.Error(url, status, res)
|
|
||||||
return nil,
|
|
||||||
errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{
|
|
||||||
MessageID: "missing_credentials",
|
|
||||||
TemplateData: map[string]interface{}{
|
|
||||||
"Credentials": strings.Join(res, ", "),
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
http.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCredentials(credential []string) []string {
|
|
||||||
rc := []string{
|
|
||||||
"/api/integration-modules/{code}",
|
|
||||||
"/api/integration-modules/{code}/edit",
|
|
||||||
}
|
|
||||||
|
|
||||||
for kn, vn := range rc {
|
|
||||||
for _, vc := range credential {
|
|
||||||
if vn == vc {
|
|
||||||
if len(rc) == 1 {
|
|
||||||
rc = rc[:0]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rc = append(rc[:kn], rc[kn+1:]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc
|
|
||||||
}
|
|
54
run.go
54
run.go
@ -1,54 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
_ "github.com/golang-migrate/migrate/database/postgres"
|
|
||||||
_ "github.com/golang-migrate/migrate/source/file"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
parser.AddCommand("run",
|
|
||||||
"Run mg-telegram",
|
|
||||||
"Run mg-telegram.",
|
|
||||||
&RunCommand{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunCommand struct
|
|
||||||
type RunCommand struct{}
|
|
||||||
|
|
||||||
// Execute command
|
|
||||||
func (x *RunCommand) Execute(args []string) error {
|
|
||||||
config = LoadConfig(options.Config)
|
|
||||||
orm = NewDb(config)
|
|
||||||
logger = newLogger()
|
|
||||||
raven.SetDSN(config.SentryDSN)
|
|
||||||
|
|
||||||
go start()
|
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c)
|
|
||||||
for sig := range c {
|
|
||||||
switch sig {
|
|
||||||
case os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM:
|
|
||||||
orm.DB.Close()
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func start() {
|
|
||||||
setWrapperRoutes()
|
|
||||||
setTransportRoutes()
|
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
|
||||||
http.ListenAndServe(config.HTTPServer.Listen, nil)
|
|
||||||
}
|
|
@ -17,6 +17,14 @@ type TransportConfig struct {
|
|||||||
Debug bool `yaml:"debug"`
|
Debug bool `yaml:"debug"`
|
||||||
UpdateInterval int `yaml:"update_interval"`
|
UpdateInterval int `yaml:"update_interval"`
|
||||||
ConfigAWS ConfigAWS `yaml:"config_aws"`
|
ConfigAWS ConfigAWS `yaml:"config_aws"`
|
||||||
|
Credentials []string `yaml:"credentials"`
|
||||||
|
TransportInfo TransportInfo `yaml:"transport_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransportInfo struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Code string `yaml:"code"`
|
||||||
|
LogoPath string `yaml:"logo_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigAWS struct
|
// ConfigAWS struct
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||||
)
|
)
|
||||||
@ -17,7 +16,6 @@ type Orm struct {
|
|||||||
func NewDb(config *TransportConfig) *Orm {
|
func NewDb(config *TransportConfig) *Orm {
|
||||||
db, err := gorm.Open("postgres", config.Database.Connection)
|
db, err := gorm.Open("postgres", config.Database.Connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
15
src/error.go
Normal file
15
src/error.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func BadRequest(error string) (int, interface{}) {
|
||||||
|
return http.StatusBadRequest, ErrorResponse{
|
||||||
|
Error: getLocalizedMessage(error),
|
||||||
|
}
|
||||||
|
}
|
113
src/error_handler.go
Normal file
113
src/error_handler.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ErrorHandlerFunc func(recovery interface{}, c *gin.Context)
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrorHandler(handlers ...ErrorHandlerFunc) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
defer func() {
|
||||||
|
rec := recover()
|
||||||
|
for _, handler := range handlers {
|
||||||
|
handler(rec, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec != nil || len(c.Errors) > 0 {
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorResponseHandler() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
publicErrors := c.Errors.ByType(gin.ErrorTypePublic)
|
||||||
|
privateLen := len(c.Errors.ByType(gin.ErrorTypePrivate))
|
||||||
|
publicLen := len(publicErrors)
|
||||||
|
|
||||||
|
if privateLen == 0 && publicLen == 0 && recovery == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesLen := publicLen
|
||||||
|
if privateLen > 0 || recovery != nil {
|
||||||
|
messagesLen++
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := make([]string, messagesLen)
|
||||||
|
index := 0
|
||||||
|
for _, err := range publicErrors {
|
||||||
|
messages[index] = err.Error()
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateLen > 0 || recovery != nil {
|
||||||
|
messages[index] = getLocalizedMessage("error_save")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": messages})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorCaptureHandler(client *raven.Client, errorsStacktrace bool) ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
tags := map[string]string{
|
||||||
|
"endpoint": c.Request.RequestURI,
|
||||||
|
}
|
||||||
|
|
||||||
|
if recovery != nil {
|
||||||
|
stacktrace := raven.NewStacktrace(4, 3, nil)
|
||||||
|
recStr := fmt.Sprint(recovery)
|
||||||
|
err := errors.New(recStr)
|
||||||
|
go client.CaptureMessageAndWait(
|
||||||
|
recStr,
|
||||||
|
tags,
|
||||||
|
raven.NewException(err, stacktrace),
|
||||||
|
raven.NewHttp(c.Request),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, err := range c.Errors {
|
||||||
|
if errorsStacktrace {
|
||||||
|
stacktrace := NewRavenStackTrace(client, err.Err, 0)
|
||||||
|
go client.CaptureMessageAndWait(
|
||||||
|
err.Error(),
|
||||||
|
tags,
|
||||||
|
raven.NewException(err.Err, stacktrace),
|
||||||
|
raven.NewHttp(c.Request),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
go client.CaptureErrorAndWait(err.Err, tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PanicLogger() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
if recovery != nil {
|
||||||
|
logger.Error(c.Request.RequestURI, recovery)
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorLogger() ErrorHandlerFunc {
|
||||||
|
return func(recovery interface{}, c *gin.Context) {
|
||||||
|
for _, err := range c.Errors {
|
||||||
|
logger.Error(c.Request.RequestURI, err.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/locale.go
Normal file
55
src/locale.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localizer *i18n.Localizer
|
||||||
|
bundle = &i18n.Bundle{DefaultLanguage: language.English}
|
||||||
|
matcher = language.NewMatcher([]language.Tag{
|
||||||
|
language.English,
|
||||||
|
language.Russian,
|
||||||
|
language.Spanish,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadTranslateFile() {
|
||||||
|
bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
|
||||||
|
files, err := ioutil.ReadDir("translate")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if !f.IsDir() {
|
||||||
|
bundle.MustLoadMessageFile("translate/" + f.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLocale(al string) {
|
||||||
|
tag, _ := language.MatchStrings(matcher, al)
|
||||||
|
localizer = i18n.NewLocalizer(bundle, tag.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalizedMessage(messageID string) string {
|
||||||
|
return localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocale() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"ButtonSave": getLocalizedMessage("button_save"),
|
||||||
|
"ApiKey": getLocalizedMessage("api_key"),
|
||||||
|
"TabSettings": getLocalizedMessage("tab_settings"),
|
||||||
|
"TabBots": getLocalizedMessage("tab_bots"),
|
||||||
|
"TableName": getLocalizedMessage("table_name"),
|
||||||
|
"TableToken": getLocalizedMessage("table_token"),
|
||||||
|
"AddBot": getLocalizedMessage("add_bot"),
|
||||||
|
"TableDelete": getLocalizedMessage("table_delete"),
|
||||||
|
"Title": getLocalizedMessage("title"),
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ var logFormat = logging.MustStringFormatter(
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newLogger() *logging.Logger {
|
func newLogger() *logging.Logger {
|
||||||
logger := logging.MustGetLogger(transport)
|
logger := logging.MustGetLogger(config.TransportInfo.Code)
|
||||||
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
|
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
|
||||||
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
|
formatBackend := logging.NewBackendFormatter(logBackend, logFormat)
|
||||||
backend1Leveled := logging.AddModuleLevel(logBackend)
|
backend1Leveled := logging.AddModuleLevel(logBackend)
|
@ -13,14 +13,13 @@ type Options struct {
|
|||||||
Config string `short:"c" long:"config" default:"config.yml" description:"Path to configuration file"`
|
Config string `short:"c" long:"config" default:"config.yml" description:"Path to configuration file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const transport = "mg-telegram"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
config *TransportConfig
|
config *TransportConfig
|
||||||
orm *Orm
|
orm *Orm
|
||||||
logger *logging.Logger
|
logger *logging.Logger
|
||||||
options Options
|
options Options
|
||||||
parser = flags.NewParser(&options, flags.Default)
|
parser = flags.NewParser(&options, flags.Default)
|
||||||
|
tokenCounter uint32
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
@ -6,8 +6,8 @@ import "time"
|
|||||||
type Connection struct {
|
type Connection struct {
|
||||||
ID int `gorm:"primary_key"`
|
ID int `gorm:"primary_key"`
|
||||||
ClientID string `gorm:"client_id type:varchar(70);not null;unique" json:"clientId,omitempty"`
|
ClientID string `gorm:"client_id type:varchar(70);not null;unique" json:"clientId,omitempty"`
|
||||||
APIKEY string `gorm:"api_key type:varchar(100);not null" json:"api_key,omitempty"`
|
APIKEY string `gorm:"api_key type:varchar(100);not null" json:"api_key,omitempty" binding:"required"`
|
||||||
APIURL string `gorm:"api_url type:varchar(255);not null" json:"api_url,omitempty"`
|
APIURL string `gorm:"api_url type:varchar(255);not null" json:"api_url,omitempty" binding:"required,validatecrmurl"`
|
||||||
MGURL string `gorm:"mg_url type:varchar(255);not null;" json:"mg_url,omitempty"`
|
MGURL string `gorm:"mg_url type:varchar(255);not null;" json:"mg_url,omitempty"`
|
||||||
MGToken string `gorm:"mg_token type:varchar(100);not null;unique" json:"mg_token,omitempty"`
|
MGToken string `gorm:"mg_token type:varchar(100);not null;unique" json:"mg_token,omitempty"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
526
src/routing.go
Normal file
526
src/routing.go
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
|
"github.com/retailcrm/api-client-go/v5"
|
||||||
|
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func connectHandler(c *gin.Context) {
|
||||||
|
res := struct {
|
||||||
|
Conn Connection
|
||||||
|
Locale map[string]string
|
||||||
|
}{
|
||||||
|
c.MustGet("account").(Connection),
|
||||||
|
getLocale(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "home", &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addBotHandler(c *gin.Context) {
|
||||||
|
b := c.MustGet("bot").(Bot)
|
||||||
|
cl, err := getBotByToken(b.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cl.ID != 0 {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("bot_already_created"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot, err := tgbotapi.NewBotAPI(b.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("incorrect_token"))
|
||||||
|
logger.Error(b.Token, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = config.Debug
|
||||||
|
|
||||||
|
wr, err := bot.SetWebhook(tgbotapi.NewWebhook("https://" + config.HTTPServer.Host + "/telegram/" + bot.Token))
|
||||||
|
if err != nil || !wr.Ok {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("error_creating_webhook"))
|
||||||
|
logger.Error(b.Token, err.Error(), wr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Name = bot.Self.FirstName
|
||||||
|
|
||||||
|
ch := v1.Channel{
|
||||||
|
Type: "telegram",
|
||||||
|
Settings: v1.ChannelSettings{
|
||||||
|
SpamAllowed: false,
|
||||||
|
Status: v1.Status{
|
||||||
|
Delivered: v1.ChannelFeatureSend,
|
||||||
|
Read: v1.ChannelFeatureNone,
|
||||||
|
},
|
||||||
|
Text: v1.ChannelSettingsText{
|
||||||
|
Creating: v1.ChannelFeatureBoth,
|
||||||
|
Editing: v1.ChannelFeatureBoth,
|
||||||
|
Quoting: v1.ChannelFeatureBoth,
|
||||||
|
Deleting: v1.ChannelFeatureReceive,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := getConnectionById(b.ConnectionID)
|
||||||
|
|
||||||
|
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||||
|
data, status, err := client.ActivateTransportChannel(ch)
|
||||||
|
if status != http.StatusCreated {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("error_activating_channel"))
|
||||||
|
logger.Error(conn.APIURL, status, err.Error(), data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Channel = data.ChannelID
|
||||||
|
|
||||||
|
err = conn.createBot(b)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteBotHandler(c *gin.Context) {
|
||||||
|
b := c.MustGet("bot").(Bot)
|
||||||
|
conn := getConnectionById(b.ConnectionID)
|
||||||
|
if conn.MGURL == "" || conn.MGToken == "" {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("not_found_account"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||||
|
|
||||||
|
data, status, err := client.DeactivateTransportChannel(getBotChannelByToken(b.Token))
|
||||||
|
if status > http.StatusOK {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("error_deactivating_channel"))
|
||||||
|
logger.Error(b.ID, status, err.Error(), data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.deleteBot()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsHandler(c *gin.Context) {
|
||||||
|
uid := c.Param("uid")
|
||||||
|
|
||||||
|
p := getConnection(uid)
|
||||||
|
if p.ID == 0 {
|
||||||
|
c.Redirect(http.StatusFound, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bots := p.getBotsByClientID()
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
Conn *Connection
|
||||||
|
Bots Bots
|
||||||
|
Locale map[string]string
|
||||||
|
}{
|
||||||
|
p,
|
||||||
|
bots,
|
||||||
|
getLocale(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "form", &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveHandler(c *gin.Context) {
|
||||||
|
conn := c.MustGet("connection").(Connection)
|
||||||
|
_, err, code := getAPIClient(conn.APIURL, conn.APIKEY)
|
||||||
|
if err != nil {
|
||||||
|
if code == http.StatusInternalServerError {
|
||||||
|
c.Error(err)
|
||||||
|
} else {
|
||||||
|
c.AbortWithStatusJSON(BadRequest(err.Error()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.saveConnection()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": getLocalizedMessage("successful")})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHandler(c *gin.Context) {
|
||||||
|
conn := c.MustGet("connection").(Connection)
|
||||||
|
|
||||||
|
cl := getConnectionByURL(conn.APIURL)
|
||||||
|
if cl.ID != 0 {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("connection_already_created"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err, code := getAPIClient(conn.APIURL, conn.APIKEY)
|
||||||
|
if err != nil {
|
||||||
|
if code == http.StatusInternalServerError {
|
||||||
|
c.Error(err)
|
||||||
|
} else {
|
||||||
|
c.AbortWithStatusJSON(code, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.ClientID = GenerateToken()
|
||||||
|
data, status, errr := client.IntegrationModuleEdit(getIntegrationModule(conn.ClientID))
|
||||||
|
if errr.RuntimeErr != nil {
|
||||||
|
c.Error(errr.RuntimeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if status >= http.StatusBadRequest {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("error_activity_mg"))
|
||||||
|
logger.Error(conn.APIURL, status, errr.ApiErr, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.MGURL = data.Info["baseUrl"]
|
||||||
|
conn.MGToken = data.Info["token"]
|
||||||
|
conn.Active = true
|
||||||
|
|
||||||
|
err = conn.createConnection()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(
|
||||||
|
http.StatusCreated,
|
||||||
|
gin.H{
|
||||||
|
"url": "/settings/" + conn.ClientID,
|
||||||
|
"message": getLocalizedMessage("successful"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func activityHandler(c *gin.Context) {
|
||||||
|
var rec v5.ActivityCallback
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&rec); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := getConnection(rec.ClientId)
|
||||||
|
if conn.ID == 0 {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest,
|
||||||
|
gin.H{
|
||||||
|
"success": false,
|
||||||
|
"error": "Wrong data",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Active = rec.Activity.Active && !rec.Activity.Freeze
|
||||||
|
|
||||||
|
if err := conn.setConnectionActivity(); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIntegrationModule(clientId string) v5.IntegrationModule {
|
||||||
|
return v5.IntegrationModule{
|
||||||
|
Code: config.TransportInfo.Code,
|
||||||
|
IntegrationCode: config.TransportInfo.Code,
|
||||||
|
Active: true,
|
||||||
|
Name: config.TransportInfo.Name,
|
||||||
|
ClientID: clientId,
|
||||||
|
Logo: fmt.Sprintf(
|
||||||
|
"https://%s%s",
|
||||||
|
config.HTTPServer.Host,
|
||||||
|
config.TransportInfo.LogoPath,
|
||||||
|
),
|
||||||
|
BaseURL: fmt.Sprintf(
|
||||||
|
"https://%s",
|
||||||
|
config.HTTPServer.Host,
|
||||||
|
),
|
||||||
|
AccountURL: fmt.Sprintf(
|
||||||
|
"https://%s/settings/%s",
|
||||||
|
config.HTTPServer.Host,
|
||||||
|
clientId,
|
||||||
|
),
|
||||||
|
Actions: map[string]string{"activity": "/actions/activity"},
|
||||||
|
Integrations: &v5.Integrations{
|
||||||
|
MgTransport: &v5.MgTransport{
|
||||||
|
WebhookUrl: fmt.Sprintf(
|
||||||
|
"https://%s/webhook/",
|
||||||
|
config.HTTPServer.Host,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func telegramWebhookHandler(c *gin.Context) {
|
||||||
|
token := c.Param("token")
|
||||||
|
b, err := getBotByToken(token)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.ID == 0 {
|
||||||
|
c.AbortWithStatus(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := getConnectionById(b.ConnectionID)
|
||||||
|
if !conn.Active {
|
||||||
|
c.AbortWithStatus(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var update tgbotapi.Update
|
||||||
|
if err := c.ShouldBindJSON(&update); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("mgWebhookHandler request: %v", update)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = v1.New(conn.MGURL, conn.MGToken)
|
||||||
|
|
||||||
|
if update.Message != nil {
|
||||||
|
if update.Message.Text == "" {
|
||||||
|
setLocale(update.Message.From.LanguageCode)
|
||||||
|
update.Message.Text = getLocalizedMessage(getMessageID(update.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
nickname := update.Message.From.UserName
|
||||||
|
user := getUserByExternalID(update.Message.From.ID)
|
||||||
|
|
||||||
|
if update.Message.From.UserName == "" {
|
||||||
|
nickname = update.Message.From.FirstName
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Expired(config.UpdateInterval) || user.ID == 0 {
|
||||||
|
fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileID != user.UserPhotoID && fileURL != "" {
|
||||||
|
picURL, err := UploadUserAvatar(fileURL)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.UserPhotoID = fileID
|
||||||
|
user.UserPhotoURL = picURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.ExternalID == 0 {
|
||||||
|
user.ExternalID = update.Message.From.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
err = user.save()
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lang := update.Message.From.LanguageCode
|
||||||
|
|
||||||
|
if len(update.Message.From.LanguageCode) > 2 {
|
||||||
|
lang = update.Message.From.LanguageCode[:2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("telegramWebhookHandler user %v", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
snd := v1.SendData{
|
||||||
|
Message: v1.SendMessage{
|
||||||
|
Message: v1.Message{
|
||||||
|
ExternalID: strconv.Itoa(update.Message.MessageID),
|
||||||
|
Type: "text",
|
||||||
|
Text: update.Message.Text,
|
||||||
|
},
|
||||||
|
SentAt: time.Now(),
|
||||||
|
},
|
||||||
|
User: v1.User{
|
||||||
|
ExternalID: strconv.Itoa(update.Message.From.ID),
|
||||||
|
Nickname: nickname,
|
||||||
|
Firstname: update.Message.From.FirstName,
|
||||||
|
Avatar: user.UserPhotoURL,
|
||||||
|
Lastname: update.Message.From.LastName,
|
||||||
|
Language: lang,
|
||||||
|
},
|
||||||
|
Channel: b.Channel,
|
||||||
|
ExternalChatID: strconv.FormatInt(update.Message.Chat.ID, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
if update.Message.ReplyToMessage != nil {
|
||||||
|
snd.Quote = &v1.SendMessageRequestQuote{ExternalID: strconv.Itoa(update.Message.ReplyToMessage.MessageID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, st, err := client.Messages(snd)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(b.Token, err.Error(), st, data)
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("telegramWebhookHandler Type: SendMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if update.EditedMessage != nil {
|
||||||
|
if update.EditedMessage.Text == "" {
|
||||||
|
setLocale(update.EditedMessage.From.LanguageCode)
|
||||||
|
update.EditedMessage.Text = getLocalizedMessage(getMessageID(update.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
snd := v1.UpdateData{
|
||||||
|
Message: v1.UpdateMessage{
|
||||||
|
Message: v1.Message{
|
||||||
|
ExternalID: strconv.Itoa(update.EditedMessage.MessageID),
|
||||||
|
Type: "text",
|
||||||
|
Text: update.EditedMessage.Text,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Channel: b.Channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, st, err := client.UpdateMessages(snd)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(b.Token, err.Error(), st, data)
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("telegramWebhookHandler Type: UpdateMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func mgWebhookHandler(c *gin.Context) {
|
||||||
|
clientID := c.GetHeader("Clientid")
|
||||||
|
if clientID == "" {
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := getConnection(clientID)
|
||||||
|
if !conn.Active {
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg v1.WebhookRequest
|
||||||
|
if err := c.ShouldBindJSON(&msg); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("mgWebhookHandler request: %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, _ := strconv.Atoi(msg.Data.ExternalMessageID)
|
||||||
|
cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64)
|
||||||
|
|
||||||
|
b := getBot(conn.ID, msg.Data.ChannelID)
|
||||||
|
if b.ID == 0 {
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot, err := tgbotapi.NewBotAPI(b.Token)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(b, err)
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type == "message_sent" {
|
||||||
|
m := tgbotapi.NewMessage(cid, msg.Data.Content)
|
||||||
|
|
||||||
|
if msg.Data.QuoteExternalID != "" {
|
||||||
|
qid, err := strconv.Atoi(msg.Data.QuoteExternalID)
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.ReplyToMessageID = qid
|
||||||
|
}
|
||||||
|
|
||||||
|
msgSend, err := bot.Send(m)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("mgWebhookHandler sent %v", msgSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"external_message_id": strconv.Itoa(msgSend.MessageID)})
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type == "message_updated" {
|
||||||
|
msgSend, err := bot.Send(tgbotapi.NewEditMessageText(cid, uid, msg.Data.Content))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("mgWebhookHandler update %v", msgSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatus(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type == "message_deleted" {
|
||||||
|
msgSend, err := bot.Send(tgbotapi.NewDeleteMessage(cid, uid))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.AbortWithStatus(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("mgWebhookHandler delete %v", msgSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
|
}
|
@ -7,19 +7,25 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/h2non/gock"
|
"github.com/h2non/gock"
|
||||||
|
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var router *gin.Engine
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
os.Chdir("../")
|
||||||
config = LoadConfig("config_test.yml")
|
config = LoadConfig("config_test.yml")
|
||||||
orm = NewDb(config)
|
orm = NewDb(config)
|
||||||
logger = newLogger()
|
logger = newLogger()
|
||||||
|
router = setup()
|
||||||
c := Connection{
|
c := Connection{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
ClientID: "123123",
|
ClientID: "123123",
|
||||||
@ -41,9 +47,8 @@ func TestRouting_connectHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(connectHandler)
|
router.ServeHTTP(rr, req)
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
assert.Equal(t, http.StatusOK, rr.Code,
|
assert.Equal(t, http.StatusOK, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
||||||
}
|
}
|
||||||
@ -51,6 +56,24 @@ func TestRouting_connectHandler(t *testing.T) {
|
|||||||
func TestRouting_addBotHandler(t *testing.T) {
|
func TestRouting_addBotHandler(t *testing.T) {
|
||||||
defer gock.Off()
|
defer gock.Off()
|
||||||
|
|
||||||
|
ch := v1.Channel{
|
||||||
|
Type: "telegram",
|
||||||
|
Settings: v1.ChannelSettings{
|
||||||
|
SpamAllowed: false,
|
||||||
|
Status: v1.Status{
|
||||||
|
Delivered: v1.ChannelFeatureSend,
|
||||||
|
Read: v1.ChannelFeatureNone,
|
||||||
|
},
|
||||||
|
Text: v1.ChannelSettingsText{
|
||||||
|
Creating: v1.ChannelFeatureBoth,
|
||||||
|
Editing: v1.ChannelFeatureBoth,
|
||||||
|
Quoting: v1.ChannelFeatureBoth,
|
||||||
|
Deleting: v1.ChannelFeatureReceive,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoing, _ := json.Marshal(ch)
|
||||||
p := url.Values{"url": {"https://" + config.HTTPServer.Host + "/telegram/123123:Qwerty"}}
|
p := url.Values{"url": {"https://" + config.HTTPServer.Host + "/telegram/123123:Qwerty"}}
|
||||||
|
|
||||||
gock.New("https://api.telegram.org").
|
gock.New("https://api.telegram.org").
|
||||||
@ -72,7 +95,7 @@ func TestRouting_addBotHandler(t *testing.T) {
|
|||||||
|
|
||||||
gock.New("https://test.retailcrm.pro").
|
gock.New("https://test.retailcrm.pro").
|
||||||
Post("/api/transport/v1/channels").
|
Post("/api/transport/v1/channels").
|
||||||
BodyString(`{"ID":0,"Type":"telegram","Events":["message_sent","message_updated","message_deleted","message_read"]}`).
|
JSON([]byte(outgoing)).
|
||||||
MatchHeader("Content-Type", "application/json").
|
MatchHeader("Content-Type", "application/json").
|
||||||
MatchHeader("X-Transport-Token", "test-token").
|
MatchHeader("X-Transport-Token", "test-token").
|
||||||
Reply(201).
|
Reply(201).
|
||||||
@ -83,8 +106,7 @@ func TestRouting_addBotHandler(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(addBotHandler)
|
router.ServeHTTP(rr, req)
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
require.Equal(t, http.StatusCreated, rr.Code,
|
require.Equal(t, http.StatusCreated, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusCreated))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusCreated))
|
||||||
|
|
||||||
@ -120,8 +142,7 @@ func TestRouting_deleteBotHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(deleteBotHandler)
|
router.ServeHTTP(rr, req)
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rr.Code,
|
assert.Equal(t, http.StatusOK, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
||||||
@ -134,8 +155,7 @@ func TestRouting_settingsHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(makeHandler(settingsHandler))
|
router.ServeHTTP(rr, req)
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rr.Code,
|
assert.Equal(t, http.StatusOK, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
||||||
@ -160,8 +180,7 @@ func TestRouting_saveHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(saveHandler)
|
router.ServeHTTP(rr, req)
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rr.Code,
|
assert.Equal(t, http.StatusOK, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
||||||
@ -177,8 +196,7 @@ func TestRouting_activityHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
handler := http.HandlerFunc(activityHandler)
|
router.ServeHTTP(rr, req)
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, rr.Code,
|
assert.Equal(t, http.StatusOK, rr.Code,
|
||||||
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
fmt.Sprintf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK))
|
148
src/run.go
Normal file
148
src/run.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/gin-contrib/multitemplate"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
_ "github.com/golang-migrate/migrate/database/postgres"
|
||||||
|
_ "github.com/golang-migrate/migrate/source/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
parser.AddCommand("run",
|
||||||
|
"Run mg-telegram",
|
||||||
|
"Run mg-telegram.",
|
||||||
|
&RunCommand{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCommand struct
|
||||||
|
type RunCommand struct{}
|
||||||
|
|
||||||
|
// Execute command
|
||||||
|
func (x *RunCommand) Execute(args []string) error {
|
||||||
|
config = LoadConfig(options.Config)
|
||||||
|
orm = NewDb(config)
|
||||||
|
logger = newLogger()
|
||||||
|
|
||||||
|
go start()
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c)
|
||||||
|
for sig := range c {
|
||||||
|
switch sig {
|
||||||
|
case os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM:
|
||||||
|
orm.DB.Close()
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
routing := setup()
|
||||||
|
routing.Run(config.HTTPServer.Listen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() *gin.Engine {
|
||||||
|
loadTranslateFile()
|
||||||
|
setValidation()
|
||||||
|
|
||||||
|
if config.Debug == false {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
r.Use(gin.Logger())
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Static("/static", "./static")
|
||||||
|
r.HTMLRender = createHTMLRender()
|
||||||
|
|
||||||
|
r.Use(func(c *gin.Context) {
|
||||||
|
setLocale(c.GetHeader("Accept-Language"))
|
||||||
|
})
|
||||||
|
|
||||||
|
errorHandlers := []ErrorHandlerFunc{
|
||||||
|
PanicLogger(),
|
||||||
|
ErrorResponseHandler(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sentry, _ := raven.New(config.SentryDSN)
|
||||||
|
if sentry != nil {
|
||||||
|
errorHandlers = append(errorHandlers, ErrorCaptureHandler(sentry, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Use(ErrorHandler(errorHandlers...))
|
||||||
|
|
||||||
|
r.GET("/", checkAccountForRequest(), connectHandler)
|
||||||
|
r.GET("/settings/:uid", settingsHandler)
|
||||||
|
r.POST("/save/", checkConnectionForRequest(), saveHandler)
|
||||||
|
r.POST("/create/", checkConnectionForRequest(), createHandler)
|
||||||
|
r.POST("/add-bot/", checkBotForRequest(), addBotHandler)
|
||||||
|
r.POST("/delete-bot/", checkBotForRequest(), deleteBotHandler)
|
||||||
|
r.POST("/actions/activity", activityHandler)
|
||||||
|
r.POST("/telegram/:token", telegramWebhookHandler)
|
||||||
|
r.POST("/webhook/", mgWebhookHandler)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHTMLRender() multitemplate.Renderer {
|
||||||
|
r := multitemplate.NewRenderer()
|
||||||
|
r.AddFromFiles("home", "templates/layout.html", "templates/home.html")
|
||||||
|
r.AddFromFiles("form", "templates/layout.html", "templates/form.html")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAccountForRequest() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
rx := regexp.MustCompile(`/+$`)
|
||||||
|
ra := rx.ReplaceAllString(c.Query("account"), ``)
|
||||||
|
p := Connection{
|
||||||
|
APIURL: ra,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("account", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBotForRequest() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var b Bot
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&b); err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Token == "" {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("no_bot_token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("bot", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConnectionForRequest() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var conn Connection
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&conn); err != nil {
|
||||||
|
c.AbortWithStatusJSON(BadRequest("incorrect_url_key"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("connection", conn)
|
||||||
|
}
|
||||||
|
}
|
89
src/stacktrace.go
Normal file
89
src/stacktrace.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/getsentry/raven-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRavenStackTrace(client *raven.Client, myerr error, skip int) *raven.Stacktrace {
|
||||||
|
st := getErrorStackTraceConverted(myerr, 3, client.IncludePaths())
|
||||||
|
if st == nil {
|
||||||
|
st = raven.NewStacktrace(skip, 3, client.IncludePaths())
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorStackTraceConverted(err error, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||||
|
st := getErrorCauseStackTrace(err)
|
||||||
|
if st == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return convertStackTrace(st, context, appPackagePrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorCauseStackTrace(err error) errors.StackTrace {
|
||||||
|
// This code is inspired by github.com/pkg/errors.Cause().
|
||||||
|
var st errors.StackTrace
|
||||||
|
for err != nil {
|
||||||
|
s := getErrorStackTrace(err)
|
||||||
|
if s != nil {
|
||||||
|
st = s
|
||||||
|
}
|
||||||
|
err = getErrorCause(err)
|
||||||
|
}
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStackTrace(st errors.StackTrace, context int, appPackagePrefixes []string) *raven.Stacktrace {
|
||||||
|
// This code is borrowed from github.com/getsentry/raven-go.NewStacktrace().
|
||||||
|
var frames []*raven.StacktraceFrame
|
||||||
|
for _, f := range st {
|
||||||
|
frame := convertFrame(f, context, appPackagePrefixes)
|
||||||
|
if frame != nil {
|
||||||
|
frames = append(frames, frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(frames) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
frames[i], frames[j] = frames[j], frames[i]
|
||||||
|
}
|
||||||
|
return &raven.Stacktrace{Frames: frames}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFrame(f errors.Frame, context int, appPackagePrefixes []string) *raven.StacktraceFrame {
|
||||||
|
// This code is borrowed from github.com/pkg/errors.Frame.
|
||||||
|
pc := uintptr(f) - 1
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
var file string
|
||||||
|
var line int
|
||||||
|
if fn != nil {
|
||||||
|
file, line = fn.FileLine(pc)
|
||||||
|
} else {
|
||||||
|
file = "unknown"
|
||||||
|
}
|
||||||
|
return raven.NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorStackTrace(err error) errors.StackTrace {
|
||||||
|
ster, ok := err.(interface {
|
||||||
|
StackTrace() errors.StackTrace
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ster.StackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorCause(err error) error {
|
||||||
|
cer, ok := err.(interface {
|
||||||
|
Cause() error
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cer.Cause()
|
||||||
|
}
|
57
src/telegram.go
Normal file
57
src/telegram.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
|
|
||||||
|
//GetFileIDAndURL function
|
||||||
|
func GetFileIDAndURL(token string, userID int) (fileID, fileURL string, err error) {
|
||||||
|
bot, err := tgbotapi.NewBotAPI(token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Debug = config.Debug
|
||||||
|
|
||||||
|
res, err := bot.GetUserProfilePhotos(
|
||||||
|
tgbotapi.UserProfilePhotosConfig{
|
||||||
|
UserID: userID,
|
||||||
|
Limit: 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Debug {
|
||||||
|
logger.Debugf("GetFileIDAndURL Photos: %v", res.Photos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.Photos) > 0 {
|
||||||
|
fileID = res.Photos[0][len(res.Photos[0])-1].FileID
|
||||||
|
fileURL, err = bot.GetFileDirectURL(fileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMessageID(data *tgbotapi.Message) string {
|
||||||
|
switch {
|
||||||
|
case data.Sticker != nil:
|
||||||
|
return "sticker"
|
||||||
|
case data.Audio != nil:
|
||||||
|
return "audio"
|
||||||
|
case data.Contact != nil:
|
||||||
|
return "contact"
|
||||||
|
case data.Document != nil:
|
||||||
|
return "document"
|
||||||
|
case data.Location != nil:
|
||||||
|
return "location"
|
||||||
|
case data.Video != nil:
|
||||||
|
return "video"
|
||||||
|
case data.Voice != nil:
|
||||||
|
return "voice"
|
||||||
|
case data.Photo != nil:
|
||||||
|
return "photo"
|
||||||
|
default:
|
||||||
|
return "undefined"
|
||||||
|
}
|
||||||
|
}
|
113
src/utils.go
Normal file
113
src/utils.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"github.com/retailcrm/api-client-go/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateToken function
|
||||||
|
func GenerateToken() string {
|
||||||
|
c := atomic.AddUint32(&tokenCounter, 1)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIClient(url, key string) (*v5.Client, error, int) {
|
||||||
|
client := v5.New(url, key)
|
||||||
|
|
||||||
|
cr, status, e := client.APICredentials()
|
||||||
|
if e.RuntimeErr != nil {
|
||||||
|
logger.Error(url, status, e.RuntimeErr, cr)
|
||||||
|
return nil, e.RuntimeErr, http.StatusInternalServerError
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cr.Success {
|
||||||
|
logger.Error(url, status, e.ApiErr, cr)
|
||||||
|
return nil, errors.New(getLocalizedMessage("incorrect_url_key")), http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := checkCredentials(cr.Credentials); len(res) != 0 {
|
||||||
|
logger.Error(url, status, res)
|
||||||
|
return nil,
|
||||||
|
errors.New(localizer.MustLocalize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: "missing_credentials",
|
||||||
|
TemplateData: map[string]interface{}{
|
||||||
|
"Credentials": strings.Join(res, ", "),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCredentials(credential []string) []string {
|
||||||
|
rc := make([]string, len(config.Credentials))
|
||||||
|
copy(rc, config.Credentials)
|
||||||
|
|
||||||
|
for _, vc := range credential {
|
||||||
|
for kn, vn := range rc {
|
||||||
|
if vn == vc {
|
||||||
|
if len(rc) == 1 {
|
||||||
|
rc = rc[:0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rc = append(rc[:kn], rc[kn+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
//UploadUserAvatar function
|
||||||
|
func UploadUserAvatar(url string) (picURLs3 string, err error) {
|
||||||
|
s3Config := &aws.Config{
|
||||||
|
Credentials: credentials.NewStaticCredentials(
|
||||||
|
config.ConfigAWS.AccessKeyID,
|
||||||
|
config.ConfigAWS.SecretAccessKey,
|
||||||
|
""),
|
||||||
|
Region: aws.String(config.ConfigAWS.Region),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := session.Must(session.NewSession(s3Config))
|
||||||
|
uploader := s3manager.NewUploader(s)
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
|
return "", errors.New(fmt.Sprintf("get: %v code: %v", url, resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := uploader.Upload(&s3manager.UploadInput{
|
||||||
|
Bucket: aws.String(config.ConfigAWS.Bucket),
|
||||||
|
Key: aws.String(fmt.Sprintf("%v/%v.jpg", config.ConfigAWS.FolderName, GenerateToken())),
|
||||||
|
Body: resp.Body,
|
||||||
|
ContentType: aws.String(config.ConfigAWS.ContentType),
|
||||||
|
ACL: aws.String("public-read"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
picURLs3 = result.Location
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
24
src/validator.go
Normal file
24
src/validator.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"gopkg.in/go-playground/validator.v8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setValidation() {
|
||||||
|
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||||
|
v.RegisterValidation("validatecrmurl", validateCrmURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCrmURL(
|
||||||
|
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||||
|
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||||
|
) bool {
|
||||||
|
regCommandName := regexp.MustCompile(`https://?[\da-z.-]+\.(retailcrm\.(ru|pro)|ecomlogic\.com)`)
|
||||||
|
|
||||||
|
return regCommandName.Match([]byte(field.Interface().(string)))
|
||||||
|
}
|
@ -3,8 +3,12 @@ $('#save-crm').on("submit", function(e) {
|
|||||||
send(
|
send(
|
||||||
$(this).attr('action'),
|
$(this).attr('action'),
|
||||||
formDataToObj($(this).serializeArray()),
|
formDataToObj($(this).serializeArray()),
|
||||||
function () {
|
function (data) {
|
||||||
return 0;
|
sessionStorage.setItem("createdMsg", data.message);
|
||||||
|
|
||||||
|
document.location.replace(
|
||||||
|
location.protocol.concat("//").concat(window.location.host) + data.url
|
||||||
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -15,7 +19,7 @@ $("#save").on("submit", function(e) {
|
|||||||
$(this).attr('action'),
|
$(this).attr('action'),
|
||||||
formDataToObj($(this).serializeArray()),
|
formDataToObj($(this).serializeArray()),
|
||||||
function (data) {
|
function (data) {
|
||||||
M.toast({html: data});
|
M.toast({html: data.message});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -62,31 +66,22 @@ function send(url, data, callback) {
|
|||||||
type: "POST",
|
type: "POST",
|
||||||
success: callback,
|
success: callback,
|
||||||
error: function (res){
|
error: function (res){
|
||||||
if (res.status < 400) {
|
if (res.status >= 400) {
|
||||||
if (res.responseText) {
|
M.toast({html: res.responseJSON.error})
|
||||||
let resObj = JSON.parse(res.responseText);
|
|
||||||
sessionStorage.setItem("createdMsg", resObj.Message);
|
|
||||||
|
|
||||||
document.location.replace(
|
|
||||||
location.protocol.concat("//").concat(window.location.host) + resObj.Url
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
M.toast({html: res.responseText})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBotTemplate(data) {
|
function getBotTemplate(data) {
|
||||||
let bot = JSON.parse(data);
|
// let bot = JSON.parse(data);
|
||||||
tmpl =
|
tmpl =
|
||||||
`<tr>
|
`<tr>
|
||||||
<td>${bot.name}</td>
|
<td>${data.name}</td>
|
||||||
<td>${bot.token}</td>
|
<td>${data.token}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="delete-bot btn btn-small waves-effect waves-light light-blue darken-1" type="submit" name="action"
|
<button class="delete-bot btn btn-small waves-effect waves-light light-blue darken-1" type="submit" name="action"
|
||||||
data-token="${bot.token}">
|
data-token="${data.token}">
|
||||||
<i class="material-icons">delete</i>
|
<i class="material-icons">delete</i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
432
telegram.go
432
telegram.go
@ -1,432 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
"github.com/getsentry/raven-go"
|
|
||||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
"github.com/retailcrm/mg-transport-api-client-go/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setTransportRoutes() {
|
|
||||||
http.HandleFunc("/telegram/", makeHandler(telegramWebhookHandler))
|
|
||||||
http.HandleFunc("/webhook/", mgWebhookHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBotName function
|
|
||||||
func GetBotName(bot *tgbotapi.BotAPI) string {
|
|
||||||
return bot.Self.FirstName
|
|
||||||
}
|
|
||||||
|
|
||||||
func telegramWebhookHandler(w http.ResponseWriter, r *http.Request, token string) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
b, err := getBotByToken(token)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(token, err.Error())
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.ID == 0 {
|
|
||||||
logger.Error(token, "telegramWebhookHandler: missing or deactivated")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := getConnectionById(b.ConnectionID)
|
|
||||||
if !c.Active {
|
|
||||||
logger.Error(c.ClientID, "telegramWebhookHandler: connection deactivated")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var update tgbotapi.Update
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(token, err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("telegramWebhookHandler: %v", string(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bytes, &update)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(token, err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var client = v1.New(c.MGURL, c.MGToken)
|
|
||||||
|
|
||||||
if update.Message != nil {
|
|
||||||
if update.Message.Text == "" {
|
|
||||||
setLocale(update.Message.From.LanguageCode)
|
|
||||||
update.Message.Text = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: getMessageID(update.Message)})
|
|
||||||
}
|
|
||||||
|
|
||||||
nickname := update.Message.From.UserName
|
|
||||||
user := getUserByExternalID(update.Message.From.ID)
|
|
||||||
|
|
||||||
if update.Message.From.UserName == "" {
|
|
||||||
nickname = update.Message.From.FirstName
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Expired(config.UpdateInterval) || user.ID == 0 {
|
|
||||||
fileID, fileURL, err := GetFileIDAndURL(b.Token, update.Message.From.ID)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileID != user.UserPhotoID && fileURL != "" {
|
|
||||||
picURL, err := UploadUserAvatar(fileURL)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user.UserPhotoID = fileID
|
|
||||||
user.UserPhotoURL = picURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.ExternalID == 0 {
|
|
||||||
user.ExternalID = update.Message.From.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
err = user.save()
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lang := update.Message.From.LanguageCode
|
|
||||||
|
|
||||||
if len(update.Message.From.LanguageCode) > 2 {
|
|
||||||
lang = update.Message.From.LanguageCode[:2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("telegramWebhookHandler user %v", user)
|
|
||||||
}
|
|
||||||
|
|
||||||
snd := v1.SendData{
|
|
||||||
Message: v1.SendMessage{
|
|
||||||
Message: v1.Message{
|
|
||||||
ExternalID: strconv.Itoa(update.Message.MessageID),
|
|
||||||
Type: "text",
|
|
||||||
Text: update.Message.Text,
|
|
||||||
},
|
|
||||||
SentAt: time.Now(),
|
|
||||||
},
|
|
||||||
User: v1.User{
|
|
||||||
ExternalID: strconv.Itoa(update.Message.From.ID),
|
|
||||||
Nickname: nickname,
|
|
||||||
Firstname: update.Message.From.FirstName,
|
|
||||||
Avatar: user.UserPhotoURL,
|
|
||||||
Lastname: update.Message.From.LastName,
|
|
||||||
Language: lang,
|
|
||||||
},
|
|
||||||
Channel: b.Channel,
|
|
||||||
ExternalChatID: strconv.FormatInt(update.Message.Chat.ID, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
if update.Message.ReplyToMessage != nil {
|
|
||||||
snd.Quote = &v1.SendMessageRequestQuote{ExternalID: strconv.Itoa(update.Message.ReplyToMessage.MessageID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
data, st, err := client.Messages(snd)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(token, err.Error(), st, data)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("telegramWebhookHandler Type: SendMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if update.EditedMessage != nil {
|
|
||||||
if update.EditedMessage.Text == "" {
|
|
||||||
setLocale(update.EditedMessage.From.LanguageCode)
|
|
||||||
update.EditedMessage.Text = localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: getMessageID(update.Message)})
|
|
||||||
}
|
|
||||||
|
|
||||||
snd := v1.UpdateData{
|
|
||||||
Message: v1.UpdateMessage{
|
|
||||||
Message: v1.Message{
|
|
||||||
ExternalID: strconv.Itoa(update.EditedMessage.MessageID),
|
|
||||||
Type: "text",
|
|
||||||
Text: update.EditedMessage.Text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Channel: b.Channel,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, st, err := client.UpdateMessages(snd)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(token, err.Error(), st, data)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("telegramWebhookHandler Type: UpdateMessage, Bot: %v, Message: %v, Response: %v", b.ID, snd, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mgWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
clientID := r.Header.Get("Clientid")
|
|
||||||
if clientID == "" {
|
|
||||||
logger.Error("mgWebhookHandler clientID is empty")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := getConnection(clientID)
|
|
||||||
if !c.Active {
|
|
||||||
logger.Error(c.ClientID, "mgWebhookHandler: connection deactivated")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte("Connection deactivated"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("mgWebhookHandler request: %v", string(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg v1.WebhookRequest
|
|
||||||
err = json.Unmarshal(bytes, &msg)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uid, _ := strconv.Atoi(msg.Data.ExternalMessageID)
|
|
||||||
cid, _ := strconv.ParseInt(msg.Data.ExternalChatID, 10, 64)
|
|
||||||
|
|
||||||
b := getBot(c.ID, msg.Data.ChannelID)
|
|
||||||
if b.ID == 0 {
|
|
||||||
logger.Error(msg.Data.ChannelID, "mgWebhookHandler: missing or deactivated")
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
w.Write([]byte("missing or deactivated"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot, err := tgbotapi.NewBotAPI(b.Token)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Type == "message_sent" {
|
|
||||||
m := tgbotapi.NewMessage(cid, msg.Data.Content)
|
|
||||||
|
|
||||||
if msg.Data.QuoteExternalID != "" {
|
|
||||||
qid, err := strconv.Atoi(msg.Data.QuoteExternalID)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.ReplyToMessageID = qid
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := bot.Send(m)
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("mgWebhookHandler sent %v", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
rsp, err := json.Marshal(map[string]string{"external_message_id": strconv.Itoa(msg.MessageID)})
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("mgWebhookHandler sent response %v", string(rsp))
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write(rsp)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Type == "message_updated" {
|
|
||||||
msg, err := bot.Send(tgbotapi.NewEditMessageText(cid, uid, msg.Data.Content))
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("mgWebhookHandler update %v", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte("Message updated"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Type == "message_deleted" {
|
|
||||||
msg, err := bot.Send(tgbotapi.NewDeleteMessage(cid, uid))
|
|
||||||
if err != nil {
|
|
||||||
raven.CaptureErrorAndWait(err, nil)
|
|
||||||
logger.Error(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("mgWebhookHandler delete %v", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte("Message deleted"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//GetFileIDAndURL function
|
|
||||||
func GetFileIDAndURL(token string, userID int) (fileID, fileURL string, err error) {
|
|
||||||
bot, err := tgbotapi.NewBotAPI(token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.Debug = config.Debug
|
|
||||||
|
|
||||||
res, err := bot.GetUserProfilePhotos(
|
|
||||||
tgbotapi.UserProfilePhotosConfig{
|
|
||||||
UserID: userID,
|
|
||||||
Limit: 1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Debug {
|
|
||||||
logger.Debugf("GetFileIDAndURL Photos: %v", res.Photos)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.Photos) > 0 {
|
|
||||||
fileID = res.Photos[0][len(res.Photos[0])-1].FileID
|
|
||||||
fileURL, err = bot.GetFileDirectURL(fileID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//UploadUserAvatar function
|
|
||||||
func UploadUserAvatar(url string) (picURLs3 string, err error) {
|
|
||||||
s3Config := &aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(
|
|
||||||
config.ConfigAWS.AccessKeyID,
|
|
||||||
config.ConfigAWS.SecretAccessKey,
|
|
||||||
""),
|
|
||||||
Region: aws.String(config.ConfigAWS.Region),
|
|
||||||
}
|
|
||||||
|
|
||||||
s := session.Must(session.NewSession(s3Config))
|
|
||||||
uploader := s3manager.NewUploader(s)
|
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
|
||||||
return "", errors.New(fmt.Sprintf("get: %v code: %v", url, resp.StatusCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := uploader.Upload(&s3manager.UploadInput{
|
|
||||||
Bucket: aws.String(config.ConfigAWS.Bucket),
|
|
||||||
Key: aws.String(fmt.Sprintf("%v/%v.jpg", config.ConfigAWS.FolderName, GenerateToken())),
|
|
||||||
Body: resp.Body,
|
|
||||||
ContentType: aws.String(config.ConfigAWS.ContentType),
|
|
||||||
ACL: aws.String("public-read"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
picURLs3 = result.Location
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMessageID(data *tgbotapi.Message) string {
|
|
||||||
switch {
|
|
||||||
case data.Sticker != nil:
|
|
||||||
return "sticker"
|
|
||||||
case data.Audio != nil:
|
|
||||||
return "audio"
|
|
||||||
case data.Contact != nil:
|
|
||||||
return "contact"
|
|
||||||
case data.Document != nil:
|
|
||||||
return "document"
|
|
||||||
case data.Location != nil:
|
|
||||||
return "location"
|
|
||||||
case data.Video != nil:
|
|
||||||
return "video"
|
|
||||||
case data.Voice != nil:
|
|
||||||
return "voice"
|
|
||||||
case data.Photo != nil:
|
|
||||||
return "photo"
|
|
||||||
default:
|
|
||||||
return "undefined"
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,7 +9,7 @@
|
|||||||
<div id="tab1" class="col s12">
|
<div id="tab1" class="col s12">
|
||||||
<div class="row indent-top">
|
<div class="row indent-top">
|
||||||
<form id="save" class="tab-el-center" action="/save/" method="POST">
|
<form id="save" class="tab-el-center" action="/save/" method="POST">
|
||||||
<input name="connectionId" type="hidden" value="{{.Conn.ID}}">
|
<input name="clientId" type="hidden" value="{{.Conn.ClientID}}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="input-field col s12">
|
<div class="input-field col s12">
|
||||||
<input placeholder="CRM Url" id="api_url" name="api_url" type="text" class="validate" value="{{.Conn.APIURL}}">
|
<input placeholder="CRM Url" id="api_url" name="api_url" type="text" class="validate" value="{{.Conn.APIURL}}">
|
||||||
|
17
token.go
17
token.go
@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tokenCounter uint32
|
|
||||||
|
|
||||||
// GenerateToken function
|
|
||||||
func GenerateToken() string {
|
|
||||||
c := atomic.AddUint32(&tokenCounter, 1)
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d%d", time.Now().UnixNano(), c))))
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user