From 24befab16ffa7743b1ef4874fd8966e08b056b1c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 10 Jun 2017 11:14:12 +0800 Subject: [PATCH] initial project Signed-off-by: Bo-Yi Wu --- .drone.yml | 68 ++++++++++ .editorconfig | 42 ++++++ .gitignore | 2 + Dockerfile | 5 + Dockerfile.armhf | 8 ++ Makefile | 152 +++++++++++++++++++++ README.md | 215 +++++++++++++++++++++++++++++- doc.go | 38 ++++++ main.go | 188 ++++++++++++++++++++++++++ plugin.go | 109 +++++++++++++++ plugin_test.go | 341 +++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1166 insertions(+), 2 deletions(-) create mode 100644 .drone.yml create mode 100644 .editorconfig create mode 100644 Dockerfile create mode 100644 Dockerfile.armhf create mode 100644 Makefile create mode 100644 doc.go create mode 100644 main.go create mode 100644 plugin.go create mode 100644 plugin_test.go diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e966a55 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,68 @@ +workspace: + base: /go + path: src/github.com/appleboy/drone-discord + +clone: + git: + image: plugins/git + depth: 50 + tags: true + +pipeline: + test: + image: appleboy/golang-testing + pull: true + environment: + TAGS: netgo + secrets: [ codecov_token, line_to, line_channel_secret, line_channel_token ] + commands: + - make vet + - make lint + - make test-vendor + - make misspell-check + - make test + - make coverage + - make build + # build binary for docker image + - make static_build + when: + event: [ push, tag, pull_request ] + + release: + image: appleboy/golang-testing + pull: true + environment: + TAGS: netgo + GOPATH: /srv/app + commands: + - make release + when: + event: [ tag ] + branch: [ refs/tags/* ] + + docker: + image: plugins/docker + repo: ${DRONE_REPO} + tags: [ '${DRONE_TAG}' ] + secrets: [ docker_username, docker_password ] + when: + event: [ tag ] + branch: [ refs/tags/* ] + + docker: + image: plugins/docker + repo: ${DRONE_REPO} + tags: [ 'latest' ] + secrets: [ docker_username, docker_password ] + when: + event: [ push ] + branch: [ master ] + + github: + image: plugins/github-release + secrets: [ github_token ] + files: + - dist/release/* + when: + event: [ tag ] + branch: [ refs/tags/* ] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e4f9bec --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +# unifying the coding style for different editors and IDEs => editorconfig.org + +; indicate this is the root of the project +root = true + +########################################################### +; common +########################################################### + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +########################################################### +; make +########################################################### + +[Makefile] +indent_style = tab + +[makefile] +indent_style = tab + +########################################################### +; markdown +########################################################### + +[*.md] +trim_trailing_whitespace = false + +########################################################### +; golang +########################################################### + +[*.go] +indent_style = tab \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1338d6..d7391d1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ + +drone-discord diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ea1a96a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM centurylink/ca-certs + +ADD drone-line / + +ENTRYPOINT ["/drone-line"] diff --git a/Dockerfile.armhf b/Dockerfile.armhf new file mode 100644 index 0000000..3183854 --- /dev/null +++ b/Dockerfile.armhf @@ -0,0 +1,8 @@ +FROM armhfbuild/alpine:3.4 + +RUN apk update && \ + apk add ca-certificates && \ + rm -rf /var/cache/apk/* + +ADD drone-discord /bin/ +ENTRYPOINT ["/bin/drone-discord"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5552b6a --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +DIST := dist +EXECUTABLE := drone-discord +GOFMT ?= gofmt "-s" + +# for dockerhub +DEPLOY_ACCOUNT := appleboy +DEPLOY_IMAGE := $(EXECUTABLE) + +TARGETS ?= linux darwin windows +PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") +SOURCES ?= $(shell find . -name "*.go" -type f) +TAGS ?= +LDFLAGS ?= -X 'main.Version=$(VERSION)' +TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'tempdir') + +ifneq ($(shell uname), Darwin) + EXTLDFLAGS = -extldflags "-static" $(null) +else + EXTLDFLAGS = +endif + +ifneq ($(DRONE_TAG),) + VERSION ?= $(DRONE_TAG) +else + VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD) +endif + +all: build + +fmt: + $(GOFMT) -w $(GOFILES) + +vet: + go vet $(PACKAGES) + +errcheck: + @which errcheck > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/kisielk/errcheck; \ + fi + errcheck $(PACKAGES) + +lint: + @which golint > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/golang/lint/golint; \ + fi + for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; + +unconvert: + @which unconvert > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/mdempsky/unconvert; \ + fi + for PKG in $(PACKAGES); do unconvert -v $$PKG || exit 1; done; + +.PHONY: misspell-check +misspell-check: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -error $(GOFILES) + +.PHONY: misspell +misspell: + @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/client9/misspell/cmd/misspell; \ + fi + misspell -w $(GOFILES) + +.PHONY: fmt-check +fmt-check: + # get all go files and run go fmt on them + @diff=$$($(GOFMT) -d $(GOFILES)); \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make fmt' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi; + +.PHONY: test-vendor +test-vendor: + @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go get -u github.com/kardianos/govendor; \ + fi + govendor list +unused | tee "$(TMPDIR)/wc-gitea-unused" + [ $$(cat "$(TMPDIR)/wc-gitea-unused" | wc -l) -eq 0 ] || echo "Warning: /!\\ Some vendor are not used /!\\" + + govendor list +outside | tee "$(TMPDIR)/wc-gitea-outside" + [ $$(cat "$(TMPDIR)/wc-gitea-outside" | wc -l) -eq 0 ] || exit 1 + + govendor status || exit 1 + +test: fmt-check + for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.txt $$PKG || exit 1; done; + +html: + go tool cover -html=coverage.txt + +install: $(SOURCES) + go install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' + +build: $(EXECUTABLE) + +$(EXECUTABLE): $(SOURCES) + go build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o $@ + +release: release-dirs release-build release-copy release-check + +release-dirs: + mkdir -p $(DIST)/binaries $(DIST)/release + +release-build: + @which gox > /dev/null; if [ $$? -ne 0 ]; then \ + go get -u github.com/mitchellh/gox; \ + fi + gox -os="$(TARGETS)" -arch="amd64 386" -tags="$(TAGS)" -ldflags="-s -w $(LDFLAGS)" -output="$(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-{{.OS}}-{{.Arch}}" + +release-copy: + $(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));) + +release-check: + cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) + +# for docker. +static_build: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o $(DEPLOY_IMAGE) + +docker_image: + docker build -t $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE) . + +docker: static_build docker_image + +docker_deploy: +ifeq ($(tag),) + @echo "Usage: make $@ tag=" + @exit 1 +endif + # deploy image + docker tag $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):latest $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag) + docker push $(DEPLOY_ACCOUNT)/$(DEPLOY_IMAGE):$(tag) + +coverage: + sed -i '/main.go/d' coverage.txt + curl -s https://codecov.io/bash > .codecov && \ + chmod +x .codecov && \ + ./.codecov -f coverage.txt + +clean: + go clean -x -i ./... + rm -rf coverage.txt $(EXECUTABLE) $(DIST) vendor + +version: + @echo $(VERSION) diff --git a/README.md b/README.md index f167da8..cdd8e9a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,213 @@ -# drone-discord -Drone plugin for sending message to Discord channel using Webhook + + +# drone-[discord](https://discordapp.com) + +Drone plugin for sending message to Discord channel using Webhook. + +[![GoDoc](https://godoc.org/github.com/appleboy/drone-discord?status.svg)](https://godoc.org/github.com/appleboy/drone-discord) +[![Build Status](http://drone.wu-boy.com/api/badges/appleboy/drone-discord/status.svg)](http://drone.wu-boy.com/appleboy/drone-discord) +[![codecov](https://codecov.io/gh/appleboy/drone-discord/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/drone-discord) +[![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/drone-discord)](https://goreportcard.com/report/github.com/appleboy/drone-discord) +[![Docker Pulls](https://img.shields.io/docker/pulls/appleboy/drone-discord.svg)](https://hub.docker.com/r/appleboy/drone-discord/) +[![](https://images.microbadger.com/badges/image/appleboy/drone-discord.svg)](https://microbadger.com/images/appleboy/drone-discord "Get your own image badge on microbadger.com") +[![Release](https://github-release-version.herokuapp.com/github/appleboy/drone-discord/release.svg?style=flat)](https://github.com/appleboy/drone-discord/releases/latest) + +Webhooks are a low-effort way to post messages to channels in Discord. They do not require a bot user or authentication to use. See more [api document information](https://discordapp.com/developers/docs/resources/webhook). + +Sending discord message using a binary, docker or [Drone CI](http://docs.drone.io/cli-installation/). + +## Build or Download a binary + +The pre-compiled binaries can be downloaded from [release page](https://github.com/appleboy/drone-discord/releases). Support the following OS type. + +* Windows amd64/386 +* Linux amd64/386 +* Darwin amd64/386 + +With `Go` installed + +``` +$ go get -u -v github.com/appleboy/drone-discord +``` + +or build the binary with the following command: + +``` +$ make build +``` + +## Docker + +Build the docker image with the following commands: + +``` +$ make docker +``` + +Please note incorrectly building the image for the correct x64 linux and with +CGO disabled will result in an error when running the Docker image: + +``` +docker: Error response from daemon: Container command +'/bin/drone-discord' not found or does not exist.. +``` + +## Usage + +There are three ways to send notification. + +* [usage from binary](#usage-from-binary) +* [usage from docker](#usage-from-docker) +* [usage from drone ci](#usage-from-drone-ci) + + +### Usage from binary + +#### Setup Webhook service + +Setup Webhook service as default port `8088`. + +```bash +drone-discord \ + --secret xxxx \ + --token xxxx \ + webhook +``` + +Change default webhook port to `8089`. + +```bash +drone-discord \ + --port 8089 \ + --secret xxxx \ + --token xxxx \ + webhook +``` + +Use [localtunnel](https://localtunnel.github.io/www/) to tunnel your locally running bot so that Line can reach the webhook. + +```bash +drone-discord \ + -s secret \ + -t token \ + --tunnel \ + --port 2002 \ + webhook +``` + +Use [Let's Encrypt](https://letsencrypt.org/). Please make sure you have permission to listen on `443` port. + +```bash +drone-discord \ + -s secret \ + -t token \ + -autotls \ + -host example.com \ + -cache /var/www/.cache \ + --port 443 \ + webhook +``` + +**Tips:** Another way to use [ngrok](https://ngrok.com/) to tunnel your locally running bot so that Line can reach the webhook. + +#### Send Notification + +Setup the `--to` flag after fetch user id from webhook service. + +```bash +drone-discord \ + --secret xxxx \ + --token xxxx \ + --to xxxx \ + --message "Test Message" +``` + + +### Usage from docker + +#### Setup Webhook service + +Setup Webhook service as default port `8088`. + +```bash +docker run --rm \ + -e LINE_CHANNEL_SECRET=xxxxxxx \ + -e LINE_CHANNEL_TOKEN=xxxxxxx \ + appleboy/drone-discord webhook +``` + +Change default webhook port to `8089`. + +```bash +docker run --rm \ + -e LINE_CHANNEL_SECRET=xxxxxxx \ + -e LINE_CHANNEL_TOKEN=xxxxxxx \ + -e LINE_PORT=8089 \ + appleboy/drone-discord webhook +``` + +**Tips:** Use [ngrok](https://ngrok.com/) to tunnel your locally running bot so that Line can reach the webhook. + +#### Send Notification + +```bash +docker run --rm \ + -e LINE_CHANNEL_SECRET=xxxxxxx \ + -e LINE_CHANNEL_TOKEN=xxxxxxx \ + -e LINE_TO=xxxxxxx \ + -e LINE_MESSAGE=test \ + -e LINE_IMAGES=https://example.com/1.png \ + -e LINE_VIDEOS=https://example.com/1.mp4 \ + -e LINE_AUDIOS=https://example.com/1.mp3::1000 \ + -e LINE_STICKERS=1::1 \ + -e LINE_LOCATIONS=title::address::latitude::longitude \ + -e LINE_DELIMITER=:: \ + appleboy/drone-discord +``` + + +### Usage from drone ci + +#### Send Notification + +Execute from the working directory: + +```bash +docker run --rm \ + -e PLUGIN_CHANNEL_SECRET=xxxxxxx \ + -e PLUGIN_CHANNEL_TOKEN=xxxxxxx \ + -e PLUGIN_TO=xxxxxxx \ + -e PLUGIN_MESSAGE=test \ + -e PLUGIN_IMAGES=https://example.com/1.png \ + -e PLUGIN_VIDEOS=https://example.com/1.mp4 \ + -e PLUGIN_AUDIOS=https://example.com/1.mp3::1000 \ + -e PLUGIN_STICKERS=1::1 \ + -e PLUGIN_LOCATIONS=title::address::latitude::longitude \ + -e PLUGIN_DELIMITER=:: \ + -e PLUGIN_ONLY_MATCH_EMAIL=false \ + -e DRONE_REPO_OWNER=appleboy \ + -e DRONE_REPO_NAME=go-hello \ + -e DRONE_COMMIT_SHA=e5e82b5eb3737205c25955dcc3dcacc839b7be52 \ + -e DRONE_COMMIT_BRANCH=master \ + -e DRONE_COMMIT_AUTHOR=appleboy \ + -e DRONE_COMMIT_AUTHOR_EMAIL=appleboy@gmail.com \ + -e DRONE_COMMIT_MESSAGE=Test_Your_Commit \ + -e DRONE_BUILD_NUMBER=1 \ + -e DRONE_BUILD_STATUS=success \ + -e DRONE_BUILD_LINK=http://github.com/appleboy/go-hello \ + -e DRONE_JOB_STARTED=1477550550 \ + -e DRONE_JOB_FINISHED=1477550750 \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + appleboy/drone-discord +``` + +You can get more [information](DOCS.md) about how to use scp plugin in drone. + +## Testing + +Test the package with the following command: + +``` +$ make test +``` diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..916ecb5 --- /dev/null +++ b/doc.go @@ -0,0 +1,38 @@ +// Sending line notifications using a binary, docker or Drone CI written in Go (Golang). +// +// Details about the drone-line project are found in github page: +// +// https://github.com/appleboy/drone-line +// +// The pre-compiled binaries can be downloaded from release page. +// +// Setup Webhook service +// +// Setup Webhook service as default port 8088. +// +// drone-line-v1.4.0-windows-amd64.exe \ +// --secret xxxx \ +// --token xxxx \ +// webhook +// +// Change default webhook port to 8089. +// +// drone-line-v1.4.0-windows-amd64.exe \ +// --port 8089 \ +// --secret xxxx \ +// --token xxxx \ +// webhook +// +// Send Notification +// +// Setup the --to flag after fetch user id from webhook service. +// +// drone-line-v1.4.0-windows-amd64.exe \ +// --secret xxxx \ +// --token xxxx \ +// --to xxxx \ +// --message "Test Message" +// +// For more details, see the documentation and example. +// +package main diff --git a/main.go b/main.go new file mode 100644 index 0000000..fbbb159 --- /dev/null +++ b/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "os" + + "github.com/joho/godotenv" + _ "github.com/joho/godotenv/autoload" + "github.com/urfave/cli" +) + +// Version set at compile-time +var Version string + +func main() { + app := cli.NewApp() + app.Name = "Drone Discord" + app.Usage = "Sending message to Discord channel using Webhook" + app.Copyright = "Copyright (c) 2017 Bo-Yi Wu" + app.Authors = []cli.Author{ + { + Name: "Bo-Yi Wu", + Email: "appleboy.tw@gmail.com", + }, + } + app.Action = run + app.Version = Version + app.Flags = []cli.Flag{ + // WebhookID: c.String("webhook.id"), + // WebhookToken: c.String("webhook.token"), + // Wait: c.Bool("wait"), + // Content: c.StringSlice("content"), + // Username: c.String("username"), + // AvatarURL: c.String("avatarURL"), + // TTS: c.Bool("tts"), + cli.StringFlag{ + Name: "webhook-id", + Usage: "discord webhook id", + EnvVar: "PLUGIN_WEBHOOK_ID,WEBHOOK_ID", + }, + cli.StringFlag{ + Name: "webhook-token", + Usage: "discord webhook token", + EnvVar: "PLUGIN_WEBHOOK_TOKEN,WEBHOOK_TOKEN", + }, + cli.StringSliceFlag{ + Name: "content", + Usage: "the message contents (up to 2000 characters)", + EnvVar: "PLUGIN_CONTENT,CONTENT", + }, + cli.BoolFlag{ + Name: "wait", + Usage: "waits for server confirmation of message send before response, and returns the created message body", + EnvVar: "PLUGIN_WAIT,WAIT", + }, + cli.BoolFlag{ + Name: "tts", + Usage: "true if this is a TTS message", + EnvVar: "PLUGIN_TTS,TTS", + }, + cli.StringFlag{ + Name: "username", + Usage: "override the default username of the webhook", + EnvVar: "PLUGIN_USERNAME,USERNAME", + }, + cli.StringFlag{ + Name: "avatar-url", + Usage: "override the default avatar of the webhook", + EnvVar: "PLUGIN_AVATAR_URL,AVATAR_URL", + }, + cli.StringFlag{ + Name: "repo.owner", + Usage: "repository owner", + EnvVar: "DRONE_REPO_OWNER", + }, + cli.StringFlag{ + Name: "repo.name", + Usage: "repository name", + EnvVar: "DRONE_REPO_NAME", + }, + cli.StringFlag{ + Name: "commit.sha", + Usage: "git commit sha", + EnvVar: "DRONE_COMMIT_SHA", + }, + cli.StringFlag{ + Name: "commit.branch", + Value: "master", + Usage: "git commit branch", + EnvVar: "DRONE_COMMIT_BRANCH", + }, + cli.StringFlag{ + Name: "commit.author", + Usage: "git author name", + EnvVar: "DRONE_COMMIT_AUTHOR", + }, + cli.StringFlag{ + Name: "commit.author.email", + Usage: "git author email", + EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL", + }, + cli.StringFlag{ + Name: "commit.message", + Usage: "commit message", + EnvVar: "DRONE_COMMIT_MESSAGE", + }, + cli.StringFlag{ + Name: "build.event", + Value: "push", + Usage: "build event", + EnvVar: "DRONE_BUILD_EVENT", + }, + cli.IntFlag{ + Name: "build.number", + Usage: "build number", + EnvVar: "DRONE_BUILD_NUMBER", + }, + cli.StringFlag{ + Name: "build.status", + Usage: "build status", + Value: "success", + EnvVar: "DRONE_BUILD_STATUS", + }, + cli.StringFlag{ + Name: "build.link", + Usage: "build link", + EnvVar: "DRONE_BUILD_LINK", + }, + cli.StringFlag{ + Name: "build.tag", + Usage: "build tag", + EnvVar: "DRONE_TAG", + }, + cli.Float64Flag{ + Name: "job.started", + Usage: "job started", + EnvVar: "DRONE_JOB_STARTED", + }, + cli.Float64Flag{ + Name: "job.finished", + Usage: "job finished", + EnvVar: "DRONE_JOB_FINISHED", + }, + cli.StringFlag{ + Name: "env-file", + Usage: "source env file", + }, + } + + app.Run(os.Args) +} + +func run(c *cli.Context) error { + if c.String("env-file") != "" { + _ = godotenv.Load(c.String("env-file")) + } + + plugin := Plugin{ + Repo: Repo{ + Owner: c.String("repo.owner"), + Name: c.String("repo.name"), + }, + Build: Build{ + Tag: c.String("build.tag"), + Number: c.Int("build.number"), + Event: c.String("build.event"), + Status: c.String("build.status"), + Commit: c.String("commit.sha"), + Branch: c.String("commit.branch"), + Author: c.String("commit.author"), + Email: c.String("commit.author.email"), + Message: c.String("commit.message"), + Link: c.String("build.link"), + Started: c.Float64("job.started"), + Finished: c.Float64("job.finished"), + }, + Config: Config{ + WebhookID: c.String("webhook-id"), + WebhookToken: c.String("webhook-token"), + Wait: c.Bool("wait"), + Content: c.StringSlice("content"), + Username: c.String("username"), + AvatarURL: c.String("avatar-url"), + TTS: c.Bool("tts"), + }, + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..5fb11d9 --- /dev/null +++ b/plugin.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "net/http" + "strings" +) + +type ( + // Repo information + Repo struct { + Owner string + Name string + } + + // Build information + Build struct { + Tag string + Event string + Number int + Commit string + Branch string + Author string + Status string + Link string + } + + // Config for the plugin. + Config struct { + WebhookID string + WebhookToken string + Wait bool + Content string + Username string + AvatarURL string + TTS bool + } + + // Plugin values. + Plugin struct { + Repo Repo + Build Build + Config Config + } +) + +// Exec executes the plugin. +func (p Plugin) Exec() error { + // attachment := slack.Attachment{ + // Text: message(p.Repo, p.Build), + // Fallback: fallback(p.Repo, p.Build), + // Color: color(p.Build), + // MarkdownIn: []string{"text", "fallback"}, + // ImageURL: p.Config.ImageURL, + // } + + // payload := slack.WebHookPostPayload{} + // payload.Username = p.Config.Username + // payload.Attachments = []*slack.Attachment{&attachment} + // payload.IconUrl = p.Config.IconURL + // payload.IconEmoji = p.Config.IconEmoji + + // if p.Config.Recipient != "" { + // payload.Channel = prepend("@", p.Config.Recipient) + // } else if p.Config.Channel != "" { + // payload.Channel = prepend("#", p.Config.Channel) + // } + + // if p.Config.Template != "" { + // txt, err := RenderTrim(p.Config.Template, p) + // if err != nil { + // return err + // } + // attachment.Text = txt + // } + + // client := slack.NewWebHook(p.Config.Webhook) + // return client.PostMessage(&payload) +} + +func message(repo Repo, build Build) string { + return fmt.Sprintf("*%s* <%s|%s/%s#%s> (%s) by %s", + build.Status, + build.Link, + repo.Owner, + repo.Name, + build.Commit[:8], + build.Branch, + build.Author, + ) +} + +func fallback(repo Repo, build Build) string { + return fmt.Sprintf("%s %s/%s#%s (%s) by %s", + build.Status, + repo.Owner, + repo.Name, + build.Commit[:8], + build.Branch, + build.Author, + ) +} + +func prepend(prefix, s string) string { + if !strings.HasPrefix(s, prefix) { + return prefix + s + } + return s +} diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..2387050 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,341 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + + "net/http" + "net/http/httptest" + "os" + "testing" +) + +func TestMissingLineConfig(t *testing.T) { + var plugin Plugin + + err := plugin.Webhook() + + assert.NotNil(t, err) +} + +func TestMissingChannelID(t *testing.T) { + var plugin Plugin + + plugin.Config.ChannelToken = "" + plugin.Config.ChannelSecret = "" + + err := plugin.Exec() + + assert.NotNil(t, err) +} + +func TestMissingUserConfig(t *testing.T) { + plugin := Plugin{ + Config: Config{ + ChannelToken: "123456789", + ChannelSecret: "test wrong id", + }, + } + + err := plugin.Exec() + + assert.NotNil(t, err) +} + +func TestSendTextError(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update by drone line plugin.", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + Config: Config{ + ChannelToken: "1465486347", + ChannelSecret: "ChannelSecret", + To: []string{"1234567890"}, + Message: []string{"Test"}, + }, + } + + // enable message + err := plugin.Exec() + assert.Nil(t, err) +} + +func TestDefaultMessageFormat(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update by drone line plugin.", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + } + + message := plugin.Message(plugin.Repo, plugin.Build) + + assert.Equal(t, []string{"[success] (master)『update by drone line plugin.』by Bo-Yi Wu"}, message) +} + +func TestErrorSendMessage(t *testing.T) { + plugin := Plugin{ + Repo: Repo{ + Name: "go-hello", + Owner: "appleboy", + }, + Build: Build{ + Number: 101, + Status: "success", + Link: "https://github.com/appleboy/go-hello", + Author: "Bo-Yi Wu", + Branch: "master", + Message: "update by drone line plugin.", + Commit: "e7c4f0a63ceeb42a39ac7806f7b51f3f0d204fd2", + }, + + Config: Config{ + ChannelToken: os.Getenv("LINE_CHANNEL_TOKEN"), + ChannelSecret: os.Getenv("LINE_CHANNEL_SECRET"), + To: []string{os.Getenv("LINE_TO")}, + Delimiter: "::", + Message: []string{"Test Line Bot From Travis or Local", "commit message: 『{{ build.message }}』", " "}, + Image: []string{"https://cdn3.iconfinder.com/data/icons/picons-social/57/16-apple-128.png"}, + Video: []string{"https://www.sample-videos.com/video/mp4/480/big_buck_bunny_480p_5mb.mp4"}, + Audio: []string{"https://feeds-tmp.soundcloud.com/stream/270161326-gotimefm-5-sarah-adams-on-test2doc-and-women-who-go.mp3::2920000", "http://feeds-tmp.soundcloud.com/stream/270161326-gotimefm-5-sarah-adams-on-test2doc-and-women-who-go.mp3"}, + Sticker: []string{"1::1::100", "1"}, + Location: []string{"竹北體育館::新竹縣竹北市::24.834687::120.993368", "1::1"}, + }, + } + + err := plugin.Exec() + assert.Nil(t, err) + + plugin.Config.Message = []string{} + err = plugin.Exec() + assert.Nil(t, err) +} + +func TestTrimElement(t *testing.T) { + var input, result []string + + input = []string{"1", " ", "3"} + result = []string{"1", "3"} + + assert.Equal(t, result, trimElement(input)) + + input = []string{"1", "2"} + result = []string{"1", "2"} + + assert.Equal(t, result, trimElement(input)) +} + +func TestConvertImage(t *testing.T) { + var input string + var result []string + + input = "http://example.com/1.png" + result = []string{"http://example.com/1.png", "http://example.com/1.png"} + + assert.Equal(t, result, convertImage(input, "::")) + + input = "http://example.com/1.png::http://example.com/2.png" + result = []string{"http://example.com/1.png", "http://example.com/2.png"} + + assert.Equal(t, result, convertImage(input, "::")) + + input = "http://example.com/1.png@@http://example.com/2.png" + result = []string{"http://example.com/1.png", "http://example.com/2.png"} + + assert.Equal(t, result, convertImage(input, "@@")) +} + +func TestConvertVideo(t *testing.T) { + var input string + var result []string + + input = "http://example.com/1.mp4" + result = []string{"http://example.com/1.mp4", defaultPreviewImageURL} + + assert.Equal(t, result, convertVideo(input, "::")) + + input = "http://example.com/1.mp4::http://example.com/2.png" + result = []string{"http://example.com/1.mp4", "http://example.com/2.png"} + + assert.Equal(t, result, convertVideo(input, "::")) + + input = "http://example.com/1.mp4@@http://example.com/2.png" + result = []string{"http://example.com/1.mp4", "http://example.com/2.png"} + + assert.Equal(t, result, convertVideo(input, "@@")) +} + +func TestConvertAudio(t *testing.T) { + var input string + var result Audio + var empty bool + + input = "http://example.com/1.mp3" + result, empty = convertAudio(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, Audio{}, result) + + // strconv.ParseInt: parsing "我": invalid syntax + input = "http://example.com/1.mp3::我" + result, empty = convertAudio(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, Audio{}, result) + + input = "http://example.com/1.mp3::1000" + result, empty = convertAudio(input, "::") + + assert.Equal(t, false, empty) + assert.Equal(t, Audio{ + URL: "http://example.com/1.mp3", + Duration: 1000, + }, result) +} + +func TestConvertSticker(t *testing.T) { + var input string + var result []string + var empty bool + + input = "1,1" + result, empty = convertSticker(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, []string{}, result) + + input = "1::1::100" + result, empty = convertSticker(input, "::") + + assert.Equal(t, false, empty) + assert.Equal(t, []string{"1", "1", "100"}, result) +} + +func TestConvertLocation(t *testing.T) { + var input string + var result Location + var empty bool + + input = "1::2::3" + result, empty = convertLocation(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + // strconv.ParseInt: parsing "測試": invalid syntax + input = "竹北體育館::新竹縣竹北市::測試::139.704051" + result, empty = convertLocation(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + // strconv.ParseInt: parsing "測試": invalid syntax + input = "竹北體育館::新竹縣竹北市::35.661777::測試" + result, empty = convertLocation(input, "::") + + assert.Equal(t, true, empty) + assert.Equal(t, Location{}, result) + + input = "竹北體育館::新竹縣竹北市::35.661777::139.704051" + result, empty = convertLocation(input, "::") + + assert.Equal(t, false, empty) + assert.Equal(t, Location{ + Title: "竹北體育館", + Address: "新竹縣竹北市", + Latitude: float64(35.661777), + Longitude: float64(139.704051), + }, result) +} + +func TestParseTo(t *testing.T) { + input := []string{"a", "b::1@gmail.com", "c::2@gmail.com", "d::3@gmail.com", "e", "f"} + + ids := parseTo(input, "1@gmail.com", false, "::") + assert.Equal(t, []string{"a", "e", "f", "b"}, ids) + + ids = parseTo(input, "1@gmail.com", true, "::") + assert.Equal(t, []string{"b"}, ids) + + ids = parseTo(input, "a@gmail.com", false, "::") + assert.Equal(t, []string{"a", "e", "f"}, ids) + + ids = parseTo(input, "a@gmail.com", true, "::") + assert.Equal(t, []string{"a", "e", "f"}, ids) + + // test empty ids + ids = parseTo([]string{"", " ", " "}, "a@gmail.com", true, "::") + assert.Equal(t, 0, len(ids)) +} + +func TestTunnelDomain(t *testing.T) { + plugin := Plugin{ + Config: Config{ + Domain: "abc", + }, + } + + domain, err := plugin.getTunnelDomain() + assert.Empty(t, domain) + assert.Error(t, err) + + plugin.Config.Domain = "newtunnel" + + domain, err = plugin.getTunnelDomain() + assert.Equal(t, "newtunnel", domain) + assert.Nil(t, err) + + plugin.Config.Domain = "" + + domain, err = plugin.getTunnelDomain() + assert.Equal(t, 10, len(domain)) + assert.Nil(t, err) +} + +func performRequest(r http.Handler, method, url string) *httptest.ResponseRecorder { + req, _ := http.NewRequest(method, url, nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + return w +} + +func TestDefaultRouter(t *testing.T) { + p := Plugin{ + Config: Config{ + ChannelToken: os.Getenv("LINE_CHANNEL_TOKEN"), + ChannelSecret: os.Getenv("LINE_CHANNEL_SECRET"), + To: []string{os.Getenv("LINE_TO")}, + }, + } + + bot, err := p.Bot() + + if err != nil { + t.Fatal(err) + } + + router := p.Handler(bot) + w := performRequest(router, "GET", "/") + assert.Equal(t, "Welcome to Line webhook page.\n", w.Body.String()) + + w = performRequest(router, "GET", "/metrics") + assert.Equal(t, 200, w.Code) +}