Telegram pusher.

This commit is contained in:
Stanislav Nikitin 2017-10-31 22:50:14 +05:00
parent 1cd99829e8
commit c9a111c832
7 changed files with 372 additions and 102 deletions

View File

@ -19,41 +19,48 @@ package configstruct
// Config's root. // Config's root.
type ConfigStruct struct { type ConfigStruct struct {
SlackHandler ConfigSlackHandler `yaml:"slackhandler"` SlackHandler ConfigSlackHandler `yaml:"slackhandler"`
Webhooks map[string]ConfigWebhook `yaml:"webhooks"` Webhooks map[string]ConfigWebhook `yaml:"webhooks"`
Matrix map[string]ConfigMatrix `yaml:"matrix"` Matrix map[string]ConfigMatrix `yaml:"matrix"`
Telegram map[string]ConfigTelegram `yaml:"telegram"`
} }
// Slack handler configuration. // Slack handler configuration.
type ConfigSlackHandler struct { type ConfigSlackHandler struct {
Listener ConfigSlackHandlerListener `yaml:"listener"` Listener ConfigSlackHandlerListener `yaml:"listener"`
} }
type ConfigSlackHandlerListener struct { type ConfigSlackHandlerListener struct {
Address string `yaml:"address"` Address string `yaml:"address"`
} }
// Webhook configuration. // Webhook configuration.
type ConfigWebhook struct { type ConfigWebhook struct {
Slack ConfigWebhookSlack `yaml:"slack"` Slack ConfigWebhookSlack `yaml:"slack"`
Remote ConfigWebhookRemote `yaml:"remote"` Remote ConfigWebhookRemote `yaml:"remote"`
} }
type ConfigWebhookSlack struct { type ConfigWebhookSlack struct {
Random1 string `yaml:"random1"` Random1 string `yaml:"random1"`
Random2 string `yaml:"random2"` Random2 string `yaml:"random2"`
LongRandom string `yaml:"longrandom"` LongRandom string `yaml:"longrandom"`
} }
type ConfigWebhookRemote struct { type ConfigWebhookRemote struct {
Pusher string `yaml:"pusher"` Pusher string `yaml:"pusher"`
PushTo string `yaml:"push_to"` PushTo string `yaml:"push_to"`
} }
// Matrix pusher configuration. // Matrix pusher configuration.
type ConfigMatrix struct { type ConfigMatrix struct {
ApiRoot string `yaml:"api_root"` ApiRoot string `yaml:"api_root"`
User string `yaml:"user"` User string `yaml:"user"`
Password string `yaml:"password"` Password string `yaml:"password"`
Room string `yaml:"room"` Room string `yaml:"room"`
}
// Telegram pusher configuration
type ConfigTelegram struct {
BotID string `yaml:"bot_id"`
ChatID string `yaml:"chat_id"`
} }

View File

@ -18,93 +18,93 @@
package context package context
import ( import (
// stdlib // stdlib
"os" "os"
"strings" "strings"
// local // local
"lab.pztrn.name/pztrn/opensaps/config/interface" "lab.pztrn.name/pztrn/opensaps/config/interface"
"lab.pztrn.name/pztrn/opensaps/parsers/interface" "lab.pztrn.name/pztrn/opensaps/parsers/interface"
"lab.pztrn.name/pztrn/opensaps/pushers/interface" "lab.pztrn.name/pztrn/opensaps/pushers/interface"
"lab.pztrn.name/pztrn/opensaps/slack/apiserverinterface" "lab.pztrn.name/pztrn/opensaps/slack/apiserverinterface"
"lab.pztrn.name/pztrn/opensaps/slack/message" "lab.pztrn.name/pztrn/opensaps/slack/message"
// other // other
"lab.pztrn.name/golibs/flagger" "lab.pztrn.name/golibs/flagger"
"lab.pztrn.name/golibs/mogrus" "lab.pztrn.name/golibs/mogrus"
) )
type Context struct { type Context struct {
Config configurationinterface.ConfigurationInterface Config configurationinterface.ConfigurationInterface
Flagger *flagger.Flagger Flagger *flagger.Flagger
Log *mogrus.LoggerHandler Log *mogrus.LoggerHandler
Parsers map[string]parserinterface.ParserInterface Parsers map[string]parserinterface.ParserInterface
Pushers map[string]pusherinterface.PusherInterface Pushers map[string]pusherinterface.PusherInterface
SlackAPIServer slackapiserverinterface.SlackAPIServerInterface SlackAPIServer slackapiserverinterface.SlackAPIServerInterface
} }
func (c *Context) Initialize() { func (c *Context) Initialize() {
c.Parsers = make(map[string]parserinterface.ParserInterface) c.Parsers = make(map[string]parserinterface.ParserInterface)
c.Pushers = make(map[string]pusherinterface.PusherInterface) c.Pushers = make(map[string]pusherinterface.PusherInterface)
l := mogrus.New() l := mogrus.New()
l.Initialize() l.Initialize()
c.Log = l.CreateLogger("opensaps") c.Log = l.CreateLogger("opensaps")
c.Log.CreateOutput("stdout", os.Stdout, true) c.Log.CreateOutput("stdout", os.Stdout, true)
c.Flagger = flagger.New(c.Log) c.Flagger = flagger.New(c.Log)
c.Flagger.Initialize() c.Flagger.Initialize()
} }
// Registers configuration interface. // Registers configuration interface.
func (c *Context) RegisterConfigurationInterface(ci configurationinterface.ConfigurationInterface) { func (c *Context) RegisterConfigurationInterface(ci configurationinterface.ConfigurationInterface) {
c.Config = ci c.Config = ci
c.Config.Initialize() c.Config.Initialize()
} }
// Registers parser interface. // Registers parser interface.
func (c *Context) RegisterParserInterface(name string, iface parserinterface.ParserInterface) { func (c *Context) RegisterParserInterface(name string, iface parserinterface.ParserInterface) {
c.Parsers[name] = iface c.Parsers[name] = iface
c.Parsers[name].Initialize() c.Parsers[name].Initialize()
} }
// Registers Pusher interface. // Registers Pusher interface.
func (c *Context) RegisterPusherInterface(name string, iface pusherinterface.PusherInterface) { func (c *Context) RegisterPusherInterface(name string, iface pusherinterface.PusherInterface) {
c.Pushers[name] = iface c.Pushers[name] = iface
c.Pushers[name].Initialize() c.Pushers[name].Initialize()
} }
// Registers Slack API HTTP server control structure. // Registers Slack API HTTP server control structure.
// Russians will have pretty good luff on variable name. // Russians will have pretty good luff on variable name.
func (c *Context) RegisterSlackAPIServerInterface(sasi slackapiserverinterface.SlackAPIServerInterface) { func (c *Context) RegisterSlackAPIServerInterface(sasi slackapiserverinterface.SlackAPIServerInterface) {
c.SlackAPIServer = sasi c.SlackAPIServer = sasi
c.SlackAPIServer.Initialize() c.SlackAPIServer.Initialize()
} }
func (c *Context) SendToParser(name string, message slackmessage.SlackMessage) map[string]string { func (c *Context) SendToParser(name string, message slackmessage.SlackMessage) map[string]string {
parser, found := c.Parsers[strings.ToLower(name)] parser, found := c.Parsers[strings.ToLower(name)]
if !found { if !found {
c.Log.Errorf("Parser '%s' not found, will use default one!", name) c.Log.Errorf("Parser '%s' not found, will use default one!", name)
return c.Parsers["default"].ParseMessage(message) return c.Parsers["default"].ParseMessage(message)
} }
return parser.ParseMessage(message) return parser.ParseMessage(message)
} }
func (c *Context) SendToPusher(protocol string, connection string, data slackmessage.SlackMessage) { func (c *Context) SendToPusher(protocol string, connection string, data slackmessage.SlackMessage) {
pusher, ok := c.Pushers[protocol] pusher, ok := c.Pushers[protocol]
if !ok { if !ok {
c.Log.Errorf("Pusher not found (or initialized) for protocol '%s'!", protocol) c.Log.Errorf("Pusher not found (or initialized) for protocol '%s'!", protocol)
} }
pusher.Push(connection, data) pusher.Push(connection, data)
} }
// Shutdown everything. // Shutdown everything.
func (c *Context) Shutdown() { func (c *Context) Shutdown() {
c.SlackAPIServer.Shutdown() c.SlackAPIServer.Shutdown()
for _, pusher := range c.Pushers { for _, pusher := range c.Pushers {
pusher.Shutdown() pusher.Shutdown()
} }
} }

View File

@ -10,9 +10,21 @@ webhooks:
remote: remote:
pusher: "matrix" pusher: "matrix"
push_to: "matrix_test" push_to: "matrix_test"
gitea_to_telegram:
slack:
random1: "87654321"
random2: "12345678"
longrandom: "432109876543210987654321"
remote:
pusher: "telegram"
push_to: "telegram_test"
matrix: matrix:
matrix_test: matrix_test:
api_root: "https://localhost:8448/_matrix/client/r0" api_root: "https://localhost:8448/_matrix/client/r0"
user: "" user: ""
password: "" password: ""
room: "!roomid:server.tld" room: "!roomid:server.tld"
telegram:
telegram_test:
bot_id: "bot:id"
chat_id: "chat_id or -chat_id"

View File

@ -18,53 +18,55 @@
package main package main
import ( import (
// stdlib // stdlib
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
// local // local
"lab.pztrn.name/pztrn/opensaps/config" "lab.pztrn.name/pztrn/opensaps/config"
"lab.pztrn.name/pztrn/opensaps/context" "lab.pztrn.name/pztrn/opensaps/context"
"lab.pztrn.name/pztrn/opensaps/parsers/default" "lab.pztrn.name/pztrn/opensaps/parsers/default"
"lab.pztrn.name/pztrn/opensaps/parsers/gitea" "lab.pztrn.name/pztrn/opensaps/parsers/gitea"
"lab.pztrn.name/pztrn/opensaps/parsers/gitlab" "lab.pztrn.name/pztrn/opensaps/parsers/gitlab"
"lab.pztrn.name/pztrn/opensaps/pushers/matrix" "lab.pztrn.name/pztrn/opensaps/pushers/matrix"
"lab.pztrn.name/pztrn/opensaps/slack" "lab.pztrn.name/pztrn/opensaps/pushers/telegram"
"lab.pztrn.name/pztrn/opensaps/slack"
) )
func main() { func main() {
c := context.New() c := context.New()
c.Initialize() c.Initialize()
config.New(c) config.New(c)
c.Log.Infoln("Launching OpenSAPS...") c.Log.Infoln("Launching OpenSAPS...")
c.Flagger.Parse() c.Flagger.Parse()
c.Config.InitializeLater() c.Config.InitializeLater()
c.Config.LoadConfigurationFromFile() c.Config.LoadConfigurationFromFile()
slack.New(c) slack.New(c)
// Initialize parsers. // Initialize parsers.
defaultparser.New(c) defaultparser.New(c)
giteaparser.New(c) giteaparser.New(c)
gitlabparser.New(c) gitlabparser.New(c)
// Initialize pushers. // Initialize pushers.
matrixpusher.New(c) matrixpusher.New(c)
telegrampusher.New(c)
// CTRL+C handler. // CTRL+C handler.
signal_handler := make(chan os.Signal, 1) signal_handler := make(chan os.Signal, 1)
shutdown_done := make(chan bool, 1) shutdown_done := make(chan bool, 1)
signal.Notify(signal_handler, os.Interrupt, syscall.SIGTERM) signal.Notify(signal_handler, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-signal_handler <-signal_handler
c.Shutdown() c.Shutdown()
shutdown_done <- true shutdown_done <- true
}() }()
<-shutdown_done <-shutdown_done
os.Exit(0) os.Exit(0)
} }

View File

@ -0,0 +1,37 @@
// OpenSAPS - Open Slack API server for everyone.
//
// Copyright (c) 2017, Stanislav N. aka pztrn.
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package telegrampusher
import (
// local
"lab.pztrn.name/pztrn/opensaps/context"
"lab.pztrn.name/pztrn/opensaps/pushers/interface"
)
var (
c *context.Context
connections map[string]*TelegramConnection
)
func New(cc *context.Context) {
c = cc
connections = make(map[string]*TelegramConnection)
tp := TelegramPusher{}
c.RegisterPusherInterface("telegram", pusherinterface.PusherInterface(tp))
}

View File

@ -0,0 +1,156 @@
// OpenSAPS - Open Slack API server for everyone.
//
// Copyright (c) 2017, Stanislav N. aka pztrn.
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package telegrampusher
import (
// stdlib
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
// local
"lab.pztrn.name/pztrn/opensaps/slack/message"
)
type TelegramConnection struct {
botId string
chatId string
connName string
}
func (tc *TelegramConnection) Initialize(connName string, botId string, chatId string) {
tc.connName = connName
tc.chatId = chatId
tc.botId = botId
}
func (tc *TelegramConnection) ProcessMessage(message slackmessage.SlackMessage) {
// Prepare message body.
message_data := c.SendToParser(message.Username, message)
// Get message template.
msg_tpl := message_data["message"]
delete(message_data, "message")
// Repeatables.
var repeatables []string
repeatables_raw, repeatables_found := message_data["repeatables"]
if repeatables_found {
repeatables = strings.Split(repeatables_raw, ",")
c.Log.Debugln("Repeatable keys:", repeatables, ", length:", len(repeatables))
}
// Process keys.
for key, value := range message_data {
// Do nothing for keys with "_url" appendix.
if strings.Contains(key, "_url") {
c.Log.Debugln("_url key found in pre-stage, skipping:", key)
continue
}
// Do nothing (yet) on repeatables.
if strings.Contains(key, "repeatable") {
c.Log.Debugln("Key containing 'repeatable' in pre-stage, skipping:", key)
continue
}
if len(repeatables) > 0 {
if strings.Contains(key, "repeatable_item_") {
c.Log.Debugln("Repeatable key in pre-stage, skipping:", key)
continue
}
}
c.Log.Debugln("Processing message data key:", key)
// Check if we have an item with "_url" appendix. This means
// that we should generate a link.
val_url, found := message_data[key+"_url"]
// Generate a link and put into message if key with "_url"
// was found.
var s string = ""
if found {
c.Log.Debugln("Found _url key, will create HTML link")
s = fmt.Sprintf("<a href='%s'>%s</a>", val_url, value)
} else {
c.Log.Debugln("Found no _url key, will use as-is")
s = value
}
msg_tpl = strings.Replace(msg_tpl, "{"+key+"}", s, -1)
}
// Process repeatables.
repeatable_tpl, repeatable_found := message_data["repeatable_message"]
if repeatable_found {
var repeatables_string string = ""
repeatables_count, _ := strconv.Atoi(message_data["repeatables_count"])
idx := 0
for {
if idx == repeatables_count {
c.Log.Debug("IDX goes above repeatables_count, breaking loop")
break
}
var repstring string = repeatable_tpl
for i := range repeatables {
c.Log.Debugln("Processing repeatable variable:", repeatables[i]+strconv.Itoa(idx))
var data string = ""
rdata := message_data["repeatable_item_"+repeatables[i]+strconv.Itoa(idx)]
rurl, rurl_found := message_data["repeatable_item_"+repeatables[i]+strconv.Itoa(idx)+"_url"]
if rurl_found {
c.Log.Debugln("Found _url key, will create HTML link")
data = fmt.Sprintf("<a href='%s'>%s</a>", rurl, rdata)
} else {
c.Log.Debugln("Found no _url key, will use as-is")
data = rdata
}
repstring = strings.Replace(repstring, "{"+repeatables[i]+"}", data, -1)
}
repeatables_string += repstring
c.Log.Debugln("Repeatable string:", repstring)
idx += 1
}
msg_tpl = strings.Replace(msg_tpl, "{repeatables}", repeatables_string, -1)
}
msg_tpl = strings.Replace(msg_tpl, "{newline}", "\n", -1)
c.Log.Debugln("Crafted message:", msg_tpl)
// Send message.
tc.SendMessage(msg_tpl)
}
func (tc *TelegramConnection) SendMessage(message string) {
msgdata := url.Values{}
msgdata.Set("chat_id", tc.chatId)
msgdata.Set("text", message)
msgdata.Set("parse_mode", "HTML")
client := &http.Client{}
botUrl := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", tc.botId)
c.Log.Debugln("Bot URL:", botUrl)
response, _ := client.PostForm(botUrl, msgdata)
c.Log.Debugln("Status:", response.Status)
}
func (tc *TelegramConnection) Shutdown() {
// There is nothing we can do actually.
}

View File

@ -0,0 +1,56 @@
// OpenSAPS - Open Slack API server for everyone.
//
// Copyright (c) 2017, Stanislav N. aka pztrn.
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the itplied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package telegrampusher
import (
// local
"lab.pztrn.name/pztrn/opensaps/slack/message"
)
type TelegramPusher struct{}
func (tp TelegramPusher) Initialize() {
c.Log.Infoln("Initializing Telegram protocol pusher...")
// Get configuration for pushers and initialize every connection.
cfg := c.Config.GetConfig()
for name, config := range cfg.Telegram {
c.Log.Infof("Initializing connection: '%s'", name)
conn := TelegramConnection{}
connections[name] = &conn
go conn.Initialize(name, config.BotID, config.ChatID)
}
}
func (tp TelegramPusher) Push(connection string, data slackmessage.SlackMessage) {
conn, found := connections[connection]
if !found {
c.Log.Errorf("Connection not found: '%s'!", connection)
return
}
c.Log.Debugf("Pushing data to '%s'", connection)
conn.ProcessMessage(data)
}
func (tp TelegramPusher) Shutdown() {
c.Log.Infoln("Shutting down Telegram pusher...")
for _, conn := range connections {
conn.Shutdown()
}
}