drone-telegram/plugin.go
Daniel M. Lambea f8f311ba1f Add support for auxiliary message file (#81)
* Add support for external message loaded from the file specified in Config.MessageFile

* Enable the message file option in command line/env vars

* Update docs

* Add a testcase

* Fix YAML tag in DOCS.md from 'messageFile' to 'message_file', as is commonly used in Drone

* Remove unintended blank line

* Move .test.message file to tests/ folder
2019-10-22 23:44:50 +08:00

423 lines
8.3 KiB
Go

package main
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/drone/drone-template-lib/template"
tgbotapi "gopkg.in/telegram-bot-api.v4"
)
type (
// GitHub information.
GitHub struct {
Workflow string
Workspace string
Action string
EventName string
EventPath string
}
// Repo information.
Repo struct {
FullName string
Namespace string
Name string
}
// Commit information.
Commit struct {
Sha string
Ref string
Branch string
Link string
Author string
Avatar string
Email string
Message string
}
// Build information.
Build struct {
Tag string
Event string
Number int
Status string
Link string
Started int64
Finished int64
PR string
DeployTo string
}
// Config for the plugin.
Config struct {
Token string
Debug bool
MatchEmail bool
WebPreview bool
To []string
Message []string
MessageFile string
Photo []string
Document []string
Sticker []string
Audio []string
Voice []string
Location []string
Video []string
Venue []string
Format string
GitHub bool
}
// Plugin values.
Plugin struct {
GitHub GitHub
Repo Repo
Commit Commit
Build Build
Config Config
}
// Location format
Location struct {
Title string
Address string
Latitude float64
Longitude float64
}
)
func trimElement(keys []string) []string {
var newKeys []string
for _, value := range keys {
value = strings.Trim(value, " ")
if len(value) == 0 {
continue
}
newKeys = append(newKeys, value)
}
return newKeys
}
func escapeMarkdown(keys []string) []string {
var newKeys []string
for _, value := range keys {
value = escapeMarkdownOne(value)
if len(value) == 0 {
continue
}
newKeys = append(newKeys, value)
}
return newKeys
}
func escapeMarkdownOne(str string) string {
str = strings.Replace(str, `\_`, `_`, -1)
str = strings.Replace(str, `_`, `\_`, -1)
return str
}
func globList(keys []string) []string {
var newKeys []string
for _, pattern := range keys {
pattern = strings.Trim(pattern, " ")
matches, err := filepath.Glob(pattern)
if err != nil {
fmt.Printf("Glob error for %q: %s\n", pattern, err)
continue
}
newKeys = append(newKeys, matches...)
}
return newKeys
}
func convertLocation(value string) (Location, bool) {
var latitude, longitude float64
var title, address string
var err error
values := trimElement(strings.Split(value, " "))
if len(values) < 2 {
return Location{}, true
}
if len(values) > 2 {
title = values[2]
}
if len(values) > 3 {
title = values[2]
address = values[3]
}
latitude, err = strconv.ParseFloat(values[0], 64)
if err != nil {
log.Println(err.Error())
return Location{}, true
}
longitude, err = strconv.ParseFloat(values[1], 64)
if err != nil {
log.Println(err.Error())
return Location{}, true
}
return Location{
Title: title,
Address: address,
Latitude: latitude,
Longitude: longitude,
}, false
}
func loadTextFromFile(filename string) ([]string, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
r := bufio.NewReader(f)
content, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return []string{string(content)}, nil
}
func parseTo(to []string, authorEmail string, matchEmail bool) []int64 {
var emails []int64
var ids []int64
attachEmail := true
for _, value := range trimElement(to) {
idArray := trimElement(strings.Split(value, ":"))
// check id
id, err := strconv.ParseInt(idArray[0], 10, 64)
if err != nil {
continue
}
// check match author email
if len(idArray) > 1 {
if email := idArray[1]; email != authorEmail {
continue
}
emails = append(emails, id)
attachEmail = false
continue
}
ids = append(ids, id)
}
if matchEmail && !attachEmail {
return emails
}
ids = append(ids, emails...)
return ids
}
// Exec executes the plugin.
func (p Plugin) Exec() (err error) {
if len(p.Config.Token) == 0 || len(p.Config.To) == 0 {
return errors.New("missing telegram token or user list")
}
var message []string
switch {
case len(p.Config.MessageFile) > 0:
message, err = loadTextFromFile(p.Config.MessageFile)
if err != nil {
return fmt.Errorf("error loading message file '%s': %v", p.Config.MessageFile, err)
}
case len(p.Config.Message) > 0:
message = p.Config.Message
default:
message = p.Message()
}
var bot *tgbotapi.BotAPI
if bot, err = tgbotapi.NewBotAPI(p.Config.Token); err != nil {
return err
}
bot.Debug = p.Config.Debug
ids := parseTo(p.Config.To, p.Commit.Email, p.Config.MatchEmail)
photos := globList(trimElement(p.Config.Photo))
documents := globList(trimElement(p.Config.Document))
stickers := globList(trimElement(p.Config.Sticker))
audios := globList(trimElement(p.Config.Audio))
voices := globList(trimElement(p.Config.Voice))
videos := globList(trimElement(p.Config.Video))
locations := trimElement(p.Config.Location)
venues := trimElement(p.Config.Venue)
message = trimElement(message)
if p.Config.Format == "markdown" {
message = escapeMarkdown(message)
p.Commit.Message = escapeMarkdownOne(p.Commit.Message)
p.Commit.Branch = escapeMarkdownOne(p.Commit.Branch)
p.Commit.Link = escapeMarkdownOne(p.Commit.Link)
p.Commit.Author = escapeMarkdownOne(p.Commit.Author)
p.Commit.Email = escapeMarkdownOne(p.Commit.Email)
p.Build.Tag = escapeMarkdownOne(p.Build.Tag)
p.Build.Link = escapeMarkdownOne(p.Build.Link)
p.Build.PR = escapeMarkdownOne(p.Build.PR)
p.Repo.Namespace = escapeMarkdownOne(p.Repo.Namespace)
p.Repo.Name = escapeMarkdownOne(p.Repo.Name)
}
// send message.
for _, user := range ids {
for _, value := range message {
txt, err := template.RenderTrim(value, p)
if err != nil {
return err
}
msg := tgbotapi.NewMessage(user, txt)
msg.ParseMode = p.Config.Format
msg.DisableWebPagePreview = !p.Config.WebPreview
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range photos {
msg := tgbotapi.NewPhotoUpload(user, value)
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range documents {
msg := tgbotapi.NewDocumentUpload(user, value)
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range stickers {
msg := tgbotapi.NewStickerUpload(user, value)
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range audios {
msg := tgbotapi.NewAudioUpload(user, value)
msg.Title = "Audio Message."
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range voices {
msg := tgbotapi.NewVoiceUpload(user, value)
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range videos {
msg := tgbotapi.NewVideoUpload(user, value)
msg.Caption = "Video Message"
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range locations {
location, empty := convertLocation(value)
if empty {
continue
}
msg := tgbotapi.NewLocation(user, location.Latitude, location.Longitude)
if err := p.Send(bot, msg); err != nil {
return err
}
}
for _, value := range venues {
location, empty := convertLocation(value)
if empty {
continue
}
msg := tgbotapi.NewVenue(user, location.Title, location.Address, location.Latitude, location.Longitude)
if err := p.Send(bot, msg); err != nil {
return err
}
}
}
return nil
}
// Send bot message.
func (p Plugin) Send(bot *tgbotapi.BotAPI, msg tgbotapi.Chattable) error {
message, err := bot.Send(msg)
if p.Config.Debug {
log.Println("=====================")
log.Printf("Response Message: %#v\n", message)
log.Println("=====================")
}
if err == nil {
return nil
}
return errors.New(strings.Replace(err.Error(), p.Config.Token, "<token>", -1))
}
// Message is plugin default message.
func (p Plugin) Message() []string {
if p.Config.GitHub {
return []string{fmt.Sprintf("%s/%s triggered by %s (%s)",
p.Repo.FullName,
p.GitHub.Workflow,
p.Repo.Namespace,
p.GitHub.EventName,
)}
}
return []string{fmt.Sprintf("[%s] <%s> (%s)『%s』by %s",
p.Build.Status,
p.Build.Link,
p.Commit.Branch,
p.Commit.Message,
p.Commit.Author,
)}
}