Julien Riou
d2d1503779
- Add ETC to the list of supported coins. A new `coin` setting can be configured to avoid conflict with `eth`. Mind the lowercase. By default, flexassitant will try to deduce the coin from the miner's address (with eth by default, not etc). (#5) - Add `test` (true/false) to `notifications` section to test notifications with random values fetched from the Flexpool API - Fix typo in the configuration example (#6) BREAKING CHANGE: `notification-templates` configuration settings have been renamed to `notifications`, with sections to configure balance, payment, block and offline workers notifications, with `template` and `test` settings. Signed-off-by: Julien Riou <julien@riou.xyz>
279 lines
7.6 KiB
Go
279 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"text/template"
|
|
|
|
telegram "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
//go:embed templates
|
|
var templateFiles embed.FS
|
|
|
|
// Attachment is used to attach objects to templates
|
|
type Attachment struct {
|
|
Miner Miner
|
|
Payment Payment
|
|
Pool Pool
|
|
Block Block
|
|
Worker Worker
|
|
}
|
|
|
|
// Notifier interface to define how to send all kind of notifications
|
|
type Notifier interface {
|
|
NotifyBalance(miner Miner, difference float64) error
|
|
NotifyPayment(miner Miner, payment Payment) error
|
|
NotifyBlock(pool Pool, block Block) error
|
|
NotifyOfflineWorker(worker Worker) error
|
|
NotifyTest(client FlexpoolClient) error
|
|
}
|
|
|
|
// TelegramNotifier to send notifications using Telegram
|
|
// Implements the Notifier interface
|
|
type TelegramNotifier struct {
|
|
bot *telegram.BotAPI
|
|
chatID int64
|
|
channelName string
|
|
configurations *NotificationsConfig
|
|
}
|
|
|
|
// NewTelegramNotifier to create a TelegramNotifier
|
|
func NewTelegramNotifier(config *TelegramConfig, configurations *NotificationsConfig) (*TelegramNotifier, error) {
|
|
bot, err := telegram.NewBotAPI(config.Token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Debugf("Connected to Telegram as %s", bot.Self.UserName)
|
|
|
|
return &TelegramNotifier{
|
|
bot: bot,
|
|
chatID: config.ChatID,
|
|
channelName: config.ChannelName,
|
|
configurations: configurations,
|
|
}, nil
|
|
}
|
|
|
|
// sendMessage to send a generic message on Telegram
|
|
func (t *TelegramNotifier) sendMessage(message string) error {
|
|
var request telegram.MessageConfig
|
|
if t.chatID != 0 {
|
|
request = telegram.NewMessage(t.chatID, message)
|
|
} else {
|
|
request = telegram.NewMessageToChannel(t.channelName, message)
|
|
}
|
|
request.DisableWebPagePreview = true
|
|
request.ParseMode = telegram.ModeMarkdown
|
|
|
|
response, err := t.bot.Send(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("Message %d sent to Telegram", response.MessageID)
|
|
return nil
|
|
}
|
|
|
|
// formatMessage to create a message with a template file name (either embeded or on disk)
|
|
func (t *TelegramNotifier) formatMessage(templateFileName string, attachment interface{}) (message string, err error) {
|
|
// Create template
|
|
templateName := path.Base(templateFileName)
|
|
templateFunctions := template.FuncMap{
|
|
"upper": strings.ToUpper,
|
|
"lower": strings.ToLower,
|
|
"convertCurrency": ConvertCurrency,
|
|
"formatBlockURL": FormatBlockURL,
|
|
"formatTransactionURL": FormatTransactionURL,
|
|
}
|
|
tmpl := template.New(templateName).Funcs(templateFunctions)
|
|
|
|
// Parse template
|
|
if fileExists(templateFileName) {
|
|
log.Debugf("Parsing template file %s", templateFileName)
|
|
tmpl, err = tmpl.ParseFiles(templateFileName)
|
|
} else {
|
|
log.Debugf("Parsing embeded template file %s", templateFileName)
|
|
tmpl, err = tmpl.ParseFS(templateFiles, templateFileName)
|
|
}
|
|
if err != nil {
|
|
return "", fmt.Errorf("parse failed: %v", err)
|
|
}
|
|
|
|
// Execute template
|
|
var buffer bytes.Buffer
|
|
err = tmpl.Execute(&buffer, attachment)
|
|
if err != nil {
|
|
return "", fmt.Errorf("execute failed: %v", err)
|
|
}
|
|
|
|
// Extract and return the formatted message
|
|
message = buffer.String()
|
|
return message, nil
|
|
}
|
|
|
|
// isFileTemplate returns true when the filename is a real file on the filesystem
|
|
func fileExists(filename string) bool {
|
|
_, err := os.Stat(filename)
|
|
return !errors.Is(err, os.ErrNotExist)
|
|
}
|
|
|
|
// NotifyBalance to format and send a notification when the unpaid balance has changed
|
|
// Implements the Notifier interface
|
|
func (t *TelegramNotifier) NotifyBalance(miner Miner) (err error) {
|
|
templateName := "templates/balance.tmpl"
|
|
if t.configurations.Balance.Template != "" {
|
|
templateName = t.configurations.Balance.Template
|
|
}
|
|
message, err := t.formatMessage(templateName, Attachment{Miner: miner})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.sendMessage(message)
|
|
}
|
|
|
|
// testNotifyBalance sends a fake balance notification
|
|
func (t *TelegramNotifier) testNotifyBalance(client FlexpoolClient) error {
|
|
log.Debug("Testing balance notification")
|
|
randomPool, err := client.RandomPool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomMiner, err := client.RandomMiner(randomPool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.NotifyBalance(*randomMiner)
|
|
}
|
|
|
|
// NotifyPayment to format and send a notification when a new payment has been detected
|
|
// Implements the Notifier interface
|
|
func (t *TelegramNotifier) NotifyPayment(miner Miner, payment Payment) error {
|
|
templateName := "templates/payment.tmpl"
|
|
if t.configurations.Payment.Template != "" {
|
|
templateName = t.configurations.Payment.Template
|
|
}
|
|
message, err := t.formatMessage(templateName, Attachment{Miner: miner, Payment: payment})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.sendMessage(message)
|
|
}
|
|
|
|
// testNotifyPayment sends a fake payment notification
|
|
func (t *TelegramNotifier) testNotifyPayment(client FlexpoolClient) error {
|
|
log.Debug("Testing payment notification")
|
|
randomPool, err := client.RandomPool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomMiner, err := client.RandomMiner(randomPool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomPayment, err := client.LastMinerPayment(randomMiner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.NotifyPayment(*randomMiner, *randomPayment)
|
|
}
|
|
|
|
// NotifyBlock to format and send a notification when a new block has been detected
|
|
// Implements the Notifier interface
|
|
func (t *TelegramNotifier) NotifyBlock(pool Pool, block Block) error {
|
|
templateName := "templates/block.tmpl"
|
|
if t.configurations.Block.Template != "" {
|
|
templateName = t.configurations.Block.Template
|
|
}
|
|
message, err := t.formatMessage(templateName, Attachment{Pool: pool, Block: block})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.sendMessage(message)
|
|
}
|
|
|
|
// testNotifyBlock sends a random block notification
|
|
func (t *TelegramNotifier) testNotifyBlock(client FlexpoolClient) error {
|
|
log.Debug("Testing block notification")
|
|
randomPool, err := client.RandomPool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomBlock, err := client.LastPoolBlock(randomPool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.NotifyBlock(*randomPool, *randomBlock)
|
|
}
|
|
|
|
// NotifyOfflineWorker sends a message when a worker is online or offline
|
|
func (t *TelegramNotifier) NotifyOfflineWorker(worker Worker) error {
|
|
templateName := "templates/offline-worker.tmpl"
|
|
if t.configurations.OfflineWorker.Template != "" {
|
|
templateName = t.configurations.OfflineWorker.Template
|
|
}
|
|
message, err := t.formatMessage(templateName, Attachment{Worker: worker})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return t.sendMessage(message)
|
|
}
|
|
|
|
// testNotifyOfflineWorker sends a fake worker offline notification
|
|
func (t *TelegramNotifier) testNotifyOfflineWorker(client FlexpoolClient) error {
|
|
log.Debug("Testing offline worker notification")
|
|
randomBlock, err := client.RandomPool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomMiner, err := client.RandomMiner(randomBlock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
randomWorker, err := client.RandomWorker(randomMiner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("%s", randomWorker)
|
|
return t.NotifyOfflineWorker(*randomWorker)
|
|
}
|
|
|
|
// NotifyTest sends fake notifications
|
|
func (t *TelegramNotifier) NotifyTest(client FlexpoolClient) (executed bool, err error) {
|
|
if t.configurations.Balance.Test {
|
|
if err = t.testNotifyBalance(client); err != nil {
|
|
return false, err
|
|
} else {
|
|
executed = true
|
|
}
|
|
}
|
|
|
|
if t.configurations.Payment.Test {
|
|
if err = t.testNotifyPayment(client); err != nil {
|
|
return false, err
|
|
} else {
|
|
executed = true
|
|
}
|
|
}
|
|
|
|
if t.configurations.Block.Test {
|
|
if err = t.testNotifyBlock(client); err != nil {
|
|
return false, err
|
|
} else {
|
|
executed = true
|
|
}
|
|
}
|
|
|
|
if t.configurations.OfflineWorker.Test {
|
|
if err = t.testNotifyOfflineWorker(client); err != nil {
|
|
return false, err
|
|
} else {
|
|
executed = true
|
|
}
|
|
}
|
|
return executed, nil
|
|
}
|