diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..0f79cb5
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,15 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+build
+coverage.txt
+gofbot.lock
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b5e26ad..0f79cb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
build
-coverage.txt
\ No newline at end of file
+coverage.txt
+gofbot.lock
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6e0676a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,22 @@
+FROM golang:latest AS build-env
+
+ENV APP_HOME /app
+WORKDIR $APP_HOME
+
+ADD go.mod go.sum Makefile .git $APP_HOME/
+RUN make mod
+
+ADD . $APP_HOME
+RUN make
+
+
+# Runing Environment
+FROM debian:9
+
+ENV APP_HOME /app
+WORKDIR $APP_HOME
+
+ADD deployments $APP_HOME/deployments
+COPY --from=build-env /app/build/gofbot $APP_HOME/gofbot
+
+ENTRYPOINT ["./gofbot"]
diff --git a/Makefile b/Makefile
index 7858dc8..a834a41 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
MKFILE_DIR := $(dir $(MKFILE_PATH))
TARGET = ${MKFILE_DIR}build/gofbot
-RELEASE?=$(shell git describe --tags)
+RELEASE := $(shell git describe --tags --always | awk -F '-' '{print $$1}')
GIT_REPO_INFO=$(shell git config --get remote.origin.url)
ifndef COMMIT
@@ -20,12 +20,12 @@ mod:
build:
@echo "-------------- building the program ---------------"
- cd ${MKFILE_DIR} && go build -v -ldflags "-s -w \
- -X main.repo=${GIT_REPO_INFO} \
- -X main.commit=${COMMIT} \
- -X main.version=${RELEASE} \
- -X 'main.buildTime=${BUILD_TIME}' \
- " -o ${TARGET} ${MKFILE_DIR}server
+ cd ${MKFILE_DIR} && go build -v -ldflags "-s -w \
+ -X main.repo=${GIT_REPO_INFO} \
+ -X main.commit=${COMMIT} \
+ -X main.version=${RELEASE} \
+ -X 'main.buildTime=${BUILD_TIME}' \
+ " -o ${TARGET} ${MKFILE_DIR}cmd/server.go
@echo "-------------- version detail ---------------"
@${TARGET} -v
diff --git a/api/server.go b/api/server.go
new file mode 100644
index 0000000..4daa75d
--- /dev/null
+++ b/api/server.go
@@ -0,0 +1,103 @@
+package api
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/saltbo/gofbot/robot"
+)
+
+type Server struct {
+ http.Server
+ router *gin.Engine
+ robots map[string]*robot.Robot
+}
+
+func NewServer() *Server {
+ router := gin.Default()
+ server := &Server{
+ Server: http.Server{
+ Addr: ":8080",
+ Handler: router,
+ },
+ router: router,
+ robots: make(map[string]*robot.Robot),
+ }
+
+ router.POST("/incoming/:alias", server.incomingHandler)
+ return server
+}
+
+func (s *Server) SetupRobots(robots []*robot.Robot) {
+ for _, bot := range robots {
+ s.robots[bot.Alias] = bot
+ }
+}
+
+func (s *Server) Run(addr ...string) error {
+ if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ return err
+ }
+
+ return nil
+}
+
+func (s *Server) Shutdown() error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ return s.Server.Shutdown(ctx)
+}
+
+func (s *Server) incomingHandler(ctx *gin.Context) {
+ alias := ctx.Param("alias")
+ bot, ok := s.robots[alias]
+ if !ok {
+ ctx.AbortWithError(http.StatusNotFound, fmt.Errorf("not found your robot"))
+ return
+ }
+
+ body, err := ioutil.ReadAll(ctx.Request.Body)
+ if err != nil {
+ ctx.AbortWithError(http.StatusBadRequest, err)
+ return
+ }
+
+ params := make(robot.Map)
+ if err := json.Unmarshal(body, ¶ms); err != nil {
+ ctx.AbortWithError(http.StatusBadRequest, err)
+ return
+ }
+
+ msg, err := bot.MatchMessage(body)
+ if err != nil {
+ ctx.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
+
+ msgStr := robot.BuildMessage(msg.Template, params) // 正则替换参数
+ postBody := robot.BuildPostBody(bot.BodyTpl, msgStr)
+ if err := forwardToRobot(ctx, bot.WebHook, postBody); err != nil {
+ ctx.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
+}
+
+func forwardToRobot(ctx *gin.Context, url string, body io.Reader) error {
+ http.DefaultClient.Timeout = 3 * time.Second
+ resp, err := http.DefaultClient.Post(url, "application/json", body)
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+ rb, _ := ioutil.ReadAll(resp.Body)
+ ctx.Data(resp.StatusCode, resp.Header.Get("Content-Type"), rb)
+ return nil
+}
diff --git a/cmd/server.go b/cmd/server.go
new file mode 100644
index 0000000..7037a9b
--- /dev/null
+++ b/cmd/server.go
@@ -0,0 +1,121 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+ "time"
+
+ "github.com/urfave/cli"
+
+ "github.com/saltbo/gofbot/api"
+ "github.com/saltbo/gofbot/pkg/process"
+ "github.com/saltbo/gofbot/robot"
+)
+
+var (
+ repo string
+ commit string
+ version string
+ buildTime string
+)
+
+var pidCtrl = process.New("gofbot.lock")
+
+// define some flags
+var flags = []cli.Flag{
+ cli.StringFlag{
+ Name: "robots",
+ Value: "deployments/robots",
+ },
+}
+
+// define some commands
+var commands = []cli.Command{
+ {
+ Name: "reload",
+ Usage: "reload for the config",
+ Action: reloadAction,
+ },
+}
+
+func main() {
+ cli.VersionPrinter = func(c *cli.Context) {
+ fmt.Printf("repo: %s\ncommit: %s\nversion: %s\nbuildTime: %s\n", repo, commit, version, buildTime)
+ }
+ app := cli.NewApp()
+ app.Compiled = time.Now()
+ app.Copyright = "(c) 2019 yanbo.me"
+ app.Flags = flags
+ app.Commands = commands
+ app.Action = serverRun
+ err := app.Run(os.Args)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func serverRun(c *cli.Context) {
+ robotsPath := c.String("robots")
+ robots, err := robot.LoadAndParse(robotsPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := pidCtrl.Save(); err != nil {
+ log.Fatal(err)
+ }
+ defer pidCtrl.Clean()
+
+ server := api.NewServer()
+ server.SetupRobots(robots)
+ setupSignalHandler(server, robotsPath)
+
+ // startup
+ if err := server.Run(":9613"); err != nil {
+ log.Fatal(err)
+ }
+
+ log.Println("normal exited.")
+}
+
+func reloadAction(c *cli.Context) {
+ p, err := pidCtrl.Find()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+
+ if err := p.Signal(syscall.SIGUSR1); err != nil {
+ log.Println(err)
+ return
+ }
+}
+
+func setupSignalHandler(server *api.Server, robotsPath string) {
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
+ go func() {
+ for {
+ switch <-ch {
+ case syscall.SIGINT, syscall.SIGTERM:
+ _ = pidCtrl.Clean()
+ signal.Stop(ch)
+ _ = server.Shutdown()
+ log.Print("system exit.")
+ return
+ case syscall.SIGUSR1:
+ // hot reload
+ robots, err := robot.LoadAndParse(robotsPath)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ server.SetupRobots(robots)
+ log.Printf("config reload.")
+ }
+ }
+ }()
+}
diff --git a/deployments/robots/wxwork4gitlab.yaml b/deployments/robots/wxwork4gitlab.yaml
new file mode 100644
index 0000000..d36e016
--- /dev/null
+++ b/deployments/robots/wxwork4gitlab.yaml
@@ -0,0 +1,12 @@
+name: wxwork4gitlab
+webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0d3ffb7e-3a7a-4f4c-8385-bea0db5e581a
+bodytpl: '{"msgtype":"markdown","markdown":{"content":"$template"}}'
+messages:
+ - regexp: push # regexp for match this message.
+ template: {{ $project.name }} 有新的PUSH,请相关同事注意。\n
+ >Commit: {{ $project.id }} \n
+ >Author: {{ $user_name }}({{ $user_email }}) \n
+ - regexp: merge
+ template: test merge message.
+ - regexp: issue
+ template: test push message.
diff --git a/go.mod b/go.mod
index 05f04b5..d77d2d5 100644
--- a/go.mod
+++ b/go.mod
@@ -4,5 +4,8 @@ require (
github.com/gin-gonic/gin v1.4.0
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.3.0
+ github.com/urfave/cli v1.20.0
gopkg.in/yaml.v2 v2.2.2
)
+
+go 1.13
diff --git a/go.sum b/go.sum
index de3ffab..f9f9a94 100644
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/pkg/process/process.go b/pkg/process/process.go
new file mode 100644
index 0000000..7de2cb7
--- /dev/null
+++ b/pkg/process/process.go
@@ -0,0 +1,45 @@
+package process
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strconv"
+)
+
+type PidCtrl struct {
+ pidFile string
+}
+
+func New(pidFile string) *PidCtrl {
+ return &PidCtrl{pidFile: pidFile}
+}
+
+func (p *PidCtrl) Save() error {
+ _, err := os.Stat(p.pidFile) // os.Stat获取文件信息
+ if (err != nil && os.IsExist(err)) || err == nil {
+ return fmt.Errorf("gofbot already running.")
+ }
+
+ pid := strconv.Itoa(os.Getpid())
+ return ioutil.WriteFile(p.pidFile, []byte(pid), 0600)
+}
+
+func (p *PidCtrl) Clean() error {
+ return os.Remove(p.pidFile)
+}
+
+func (p *PidCtrl) Find() (*os.Process, error) {
+ data, err := ioutil.ReadFile(p.pidFile)
+ if err != nil {
+ return nil, err
+ }
+
+ pidStr := string(data)
+ pid, err := strconv.Atoi(pidStr)
+ if err != nil {
+ return nil, err
+ }
+
+ return os.FindProcess(pid)
+}
diff --git a/robot/message.go b/robot/message.go
new file mode 100644
index 0000000..444f309
--- /dev/null
+++ b/robot/message.go
@@ -0,0 +1,63 @@
+package robot
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var tplArgExp = regexp.MustCompile(`{{(\s*\$\S+\s*)}}`)
+
+type Map map[string]interface{}
+
+type Message struct {
+ Regexp string `yaml:"regexp"`
+ Template string `yaml:"template"`
+
+ Exp *regexp.Regexp
+}
+
+type variable struct {
+ full string
+ name string
+}
+
+func BuildMessage(tpl string, params Map) string {
+ variables := make([]variable, 0)
+ for _, v := range tplArgExp.FindAllStringSubmatch(tpl, -1) {
+ variables = append(variables, variable{full: v[0], name: v[1]})
+ }
+
+ newMsg := tpl
+ for _, v := range variables {
+ newMsg = strings.Replace(newMsg, v.full, extractArgs(params, strings.TrimSpace(v.name)), -1)
+ }
+
+ if strconv.CanBackquote(newMsg) {
+ return newMsg
+ }
+
+ return strconv.Quote(newMsg)
+}
+
+func BuildPostBody(bodyTpl string, message string) *bytes.Buffer {
+ return bytes.NewBufferString(strings.Replace(bodyTpl, "$template", message, -1))
+}
+
+func extractArgs(params Map, key string) string {
+ key = strings.Replace(key, "$", "", -1)
+ keys := strings.Split(key, ".")
+ for index, k := range keys {
+ if index == len(keys)-1 {
+ return fmt.Sprintf("%v", params[k])
+ }
+
+ if nextParams, ok := params[k].(map[string]interface{}); ok {
+ params = nextParams
+ }
+ }
+
+ return ""
+}
diff --git a/robot/message_test.go b/robot/message_test.go
new file mode 100644
index 0000000..d5ea3e3
--- /dev/null
+++ b/robot/message_test.go
@@ -0,0 +1,26 @@
+package robot
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBuildMessage(t *testing.T) {
+ params := Map{
+ "name": "saltbo",
+ "age": "53",
+ "info": map[string]interface{}{
+ "city": "Beijing",
+ },
+ }
+ tpl := `name: {{$name}}, age: {{ $age }}, city: {{ $info.city }}`
+ msg := BuildMessage(tpl, params)
+ assert.Contains(t, msg, params["name"])
+ assert.Contains(t, msg, params["age"])
+ assert.Contains(t, msg, params["info"].(map[string]interface{})["city"])
+
+ bodyTpl := `{"msgtype": "markdown", "content": "$template"}`
+ body := BuildPostBody(bodyTpl, msg)
+ assert.Contains(t, body.String(), msg)
+}
diff --git a/server/robot.go b/robot/robot.go
similarity index 82%
rename from server/robot.go
rename to robot/robot.go
index 84ed9b4..6885dff 100644
--- a/server/robot.go
+++ b/robot/robot.go
@@ -1,4 +1,4 @@
-package main
+package robot
import (
"crypto/md5"
@@ -13,6 +13,43 @@ import (
"strings"
)
+func LoadAndParse(robotsPath string) ([]*Robot, error) {
+ robots := make([]*Robot, 0)
+ robotCreator := func(filepath string) error {
+ robot, err := newRobot(filepath)
+ if err != nil {
+ return err
+ }
+
+ robots = append(robots, robot)
+ return nil
+ }
+
+ if err := findRobots(robotsPath, robotCreator); err != nil {
+ return nil, err
+ }
+
+ if len(robots) == 0 {
+ return nil, fmt.Errorf("not found any robot.")
+ }
+
+ return robots, nil
+}
+
+func findRobots(root string, creator func(filepath string) error) error {
+ return filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ } else if info.IsDir() {
+ return nil
+ } else if path.Ext(filepath) != ".yaml" && path.Ext(filepath) != ".yml" {
+ return nil
+ }
+
+ return creator(filepath)
+ })
+}
+
type Robot struct {
Name string `yaml:"name"`
Alias string `yaml:"uuid"`
@@ -21,11 +58,14 @@ type Robot struct {
Messages []*Message `yaml:"messages"`
}
-type Message struct {
- Regexp string `yaml:"regexp"`
- Template string `yaml:"template"`
+func (r *Robot) MatchMessage(body []byte) (*Message, error) {
+ for _, msg := range r.Messages {
+ if msg.Exp.Match(body) {
+ return msg, nil
+ }
+ }
- Exp *regexp.Regexp
+ return nil, fmt.Errorf("not found any message")
}
func newRobot(yamlPath string) (*Robot, error) {
@@ -43,9 +83,9 @@ func newRobot(yamlPath string) (*Robot, error) {
robot.Alias = hex.EncodeToString(nameHash[:])
errors := make([]string, 0)
for _, msg := range robot.Messages {
- exp, err2 := regexp.Compile(msg.Regexp)
+ exp, err := regexp.Compile(msg.Regexp)
if err != nil {
- errors = append(errors, err2.Error())
+ errors = append(errors, err.Error())
continue
}
@@ -58,40 +98,3 @@ func newRobot(yamlPath string) (*Robot, error) {
return robot, nil
}
-
-func findRobots(root string, creator func(filepath string) error) error {
- return filepath.Walk(root, func(filepath string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- } else if info.IsDir() {
- return nil
- } else if path.Ext(filepath) != ".yaml" && path.Ext(filepath) != ".yml" {
- return nil
- }
-
- return creator(filepath)
- })
-}
-
-func loadRobots(robotsPath string) ([]*Robot, error) {
- robots := make([]*Robot, 0)
- robotCreator := func(filepath string) error {
- robot, err := newRobot(filepath)
- if err != nil {
- return err
- }
-
- robots = append(robots, robot)
- return nil
- }
-
- if err := findRobots(robotsPath, robotCreator); err != nil {
- return nil, err
- }
-
- if len(robots) == 0 {
- return nil, fmt.Errorf("not found any robot.")
- }
-
- return robots, nil
-}
diff --git a/server/robot_test.go b/robot/robot_test.go
similarity index 60%
rename from server/robot_test.go
rename to robot/robot_test.go
index b417ed1..b0a3c74 100644
--- a/server/robot_test.go
+++ b/robot/robot_test.go
@@ -1,11 +1,12 @@
-package main
+package robot
import (
- "github.com/stretchr/testify/assert"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func TestNewRobot(t *testing.T) {
- _, e := newRobot("../robots/wxwork4gitlab.yaml")
+ _, e := newRobot("../deployments/robots/wxwork4gitlab.yaml")
assert.NoError(t, e)
}
diff --git a/robots/wxwork4gitlab.yaml b/robots/wxwork4gitlab.yaml
deleted file mode 100644
index 38355a9..0000000
--- a/robots/wxwork4gitlab.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: wxwork4gitlab
-webhook: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=0d3ffb7e-3a7a-4f4c-8385-bea0db5e581a
-bodytpl: '{"msgtype":"markdown","markdown":{"content":"$template"}}'
-messages:
- - regexp: name # regexp for match this message.
- template: 实时新增用户反馈132例,请相关同事注意。\n
- >类型:用户反馈 \n
- >普通用户反馈:{{ $test }} \n
- >VIP用户反馈:{{ $test2 }}例
- - regexp: merge
- template: test merge message.
- - regexp: issue
- template: test push message.
\ No newline at end of file
diff --git a/server/main.go b/server/main.go
deleted file mode 100644
index 3b80eb1..0000000
--- a/server/main.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "log"
- "os"
-)
-
-var (
- showVer bool
- repo string
- commit string
- version string
- buildTime string
-)
-
-func init() {
- flag.BoolVar(&showVer, "v", false, "show build version")
- flag.Parse()
-
- if showVer {
- fmt.Printf("repo: %s\ncommit: %s\nversion: %s\nbuildTime: %s\n", repo, commit, version, buildTime)
- os.Exit(0)
- }
-}
-
-func main() {
- robots, err := loadRobots("robots")
- if err != nil {
- log.Fatal(err)
- }
-
- if s, err := New(robots); err != nil {
- log.Fatal(err)
- } else {
- log.Fatal(s.Run(":9613"))
- }
-}
diff --git a/server/server.go b/server/server.go
deleted file mode 100644
index 2cbef43..0000000
--- a/server/server.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "regexp"
- "strconv"
- "strings"
- "time"
-
- "github.com/gin-gonic/gin"
-)
-
-var tplArgExp = regexp.MustCompile(`{{(\s*\$\S+\s*)}}`)
-
-type Map map[string]interface{}
-
-type Server struct {
- *gin.Engine
-}
-
-func New(robots []*Robot) (*Server, error) {
- router := gin.Default()
- for _, robot := range robots {
- router.POST(fmt.Sprintf("/incoming/%s", robot.Alias), func(context *gin.Context) {
- incomingHandler(context, robot)
- })
- }
-
- return &Server{
- Engine: router,
- }, nil
-}
-
-func incomingHandler(ctx *gin.Context, robot *Robot) {
- body, err := ioutil.ReadAll(ctx.Request.Body)
- if err != nil {
- ctx.AbortWithError(http.StatusBadRequest, err)
- return
- }
-
- params := make(Map)
- if err := json.Unmarshal(body, ¶ms); err != nil {
- ctx.AbortWithError(http.StatusInternalServerError, err)
- return
- }
-
- for _, msg := range robot.Messages {
- if !msg.Exp.Match(body) {
- continue
- }
-
- // 正则替换参数
- message := buildMessage(msg.Template, params)
- body := buildPostBody(robot.BodyTpl, message)
- http.DefaultClient.Timeout = 3 * time.Second
- if resp, err := http.DefaultClient.Post(robot.WebHook, "application/json", body); err != nil {
- ctx.AbortWithError(http.StatusInternalServerError, err)
- return
- } else {
- defer resp.Body.Close()
- rb, _ := ioutil.ReadAll(resp.Body)
- ctx.Data(resp.StatusCode, resp.Header.Get("Content-Type"), rb)
- }
- }
-}
-
-type variable struct {
- full string
- name string
-}
-
-func buildMessage(tpl string, params Map) string {
- variables := make([]variable, 0)
- for _, v := range tplArgExp.FindAllStringSubmatch(tpl, -1) {
- variables = append(variables, variable{full: v[0], name: v[1]})
- }
-
- newMsg := tpl
- for _, v := range variables {
- newMsg = strings.Replace(newMsg, v.full, extractArgs(params, strings.TrimSpace(v.name)), -1)
- }
-
- if strconv.CanBackquote(newMsg) {
- return newMsg
- }
-
- return strconv.Quote(newMsg)
-}
-
-func buildPostBody(bodyTpl string, message string) *bytes.Buffer {
- return bytes.NewBufferString(strings.Replace(bodyTpl, "$template", message, -1))
-}
-
-func extractArgs(params Map, key string) string {
- key = strings.Replace(key, "$", "", -1)
- keys := strings.Split(key, ".")
- for index, k := range keys {
- if index == len(keys)-1 {
- if v, ok := params[k].(string); ok {
- return v
- }
- return ""
- }
-
- if nextParams, ok := params[k].(Map); ok {
- params = nextParams
- }
- }
-
- return ""
-}
diff --git a/server/server_test.go b/server/server_test.go
deleted file mode 100644
index d763f8a..0000000
--- a/server/server_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package main
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestBuildMessage(t *testing.T) {
- params := Map{
- "name": "saltbo",
- "age": "53",
- "info": Map{
- "city": "Beijing",
- },
- }
- tpl := `name: {{$name}}, age: {{ $age }}, city: {{ $info.city }}`
- msg := buildMessage(tpl, params)
- assert.Contains(t, msg, params["name"])
- assert.Contains(t, msg, params["age"])
- assert.Contains(t, msg, params["info"].(Map)["city"])
-
- bodyTpl := `{"msgtype": "markdown", "content": "$template"}`
- body := buildPostBody(bodyTpl, msg)
- assert.Contains(t, body.String(), msg)
-}
-
-func performRequest(r http.Handler, method, path string, body io.Reader) *httptest.ResponseRecorder {
- req := httptest.NewRequest(method, path, body)
- w := httptest.NewRecorder()
- r.ServeHTTP(w, req)
- return w
-}
-
-type testServer struct {
-}
-
-func (ts *testServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- b, e := ioutil.ReadAll(r.Body)
- if e != nil {
- w.WriteHeader(http.StatusBadRequest)
- return
- }
-
- if _, err := w.Write(b); err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- }
-}
-
-func TestServer_Run(t *testing.T) {
- ts := httptest.NewServer(&testServer{})
- defer ts.Close()
-
- robots, err := loadRobots("../robots")
- assert.NoError(t, err)
-
- r, e := New(robots)
- assert.NoError(t, e)
- for _, robot := range robots {
- // reset the hook to the test server URL
- robot.WebHook = ts.URL
-
- // RUN
- body := bytes.NewBufferString(`{"name": "saltbo", "sex": "man", "info":{"city": "beijing"}}`)
- w := performRequest(r, "POST", fmt.Sprintf("/incoming/%s", robot.Alias), body)
-
- // TEST
- assert.Equal(t, http.StatusOK, w.Code)
- }
-}