jlelse
/
kis3
Archived
1
Fork 0

Feature: Request stats via Telegram

This commit is contained in:
Jan-Lukas Else 2019-05-27 15:03:30 +02:00
parent 94251f1968
commit e9e25ae41a
5 changed files with 150 additions and 49 deletions

View File

@ -6,7 +6,9 @@
KISSS is really easy to install via Docker.
docker run -d --name kis3 -p 8080:8080 -v kis3:/app/data -v ${pwd}/config.json:/app/config.json kis3/kis3
```bash
docker run -d --name kis3 -p 8080:8080 -v kis3:/app/data -v ${pwd}/config.json:/app/config.json kis3/kis3
```
Depending on your setup, replace `-p 8080:8080` with your custom port configuration. KISSS listens to port 8080 by default, but you can also change this via the configuration.
@ -18,9 +20,11 @@ You should also mount a configuration file to `/app/config.json`.
It's also possible to use KISSS without Docker, but for that you need to compile it yourself. All you need to do so is installing go (follow the [instruction](https://golang.org/doc/install) or use [distro.tools](https://distro.tools) to install the latest version on Linux - you need at least version 1.12) and execute the following command:
go get -u github.com/kis3/kis3
After that there should be an executable with the name `kis3` in `$HOME/go/bin`.
```bash
go get -u github.com/kis3/kis3
```
After that there should be an executable with the name `kis3` in `$HOME/go/bin`.
## Configuration
@ -48,6 +52,24 @@ The configuration file can look like this:
If you specify an environment variable (`PORT`, `DNT`, `DB_PATH`, `STATS_USERNAME`, `STATS_PASSWORD`), that will override the settings from the configuration file.
### Email
To enable email integration for sending reports, you need to add some configuration values for that:
`smtpFrom`: Sender address for the emails
`smtpHost`: Address of the mail server (including port)
`smtpUser`: Username for SMTP login
`smtpPassword`: Password for SMTP login
### Telegram
The Telegram integration allows sending reports via Telegram and also requesting stats via Telegram. For that the following configuration value must be set:
`tgBotToken`: Token for the Telegram bot, which you can request via the [Bot Father](https://t.me/BotFather).
## Add to website
You can add the KISSS tracker to any website by putting `<script src="https://yourkis3domain.tld/kis3.js"></script>` just before `</body>` in the HTML. Just replace `yourkis3domain.tld` with the correct address.
@ -80,6 +102,12 @@ The following filters are available:
`format`: the format to represent the data, default is `plain` for a simple plain text list, `json` for a JSON response or `chart` for a chart generated with ChartJS in the browser
### Via Telegram
You can also request stats via Telegram (in case you enable the Telegram integration). To do so, simply send a message with the command `stats` like `/stats view=pages...`.
If you have authentication enabled, you need to add `username=yourusername&password=yourpassword` to the query.
## Daily reports
KISSS has a feature that can send you daily reports. It basically requests the statistics and sends the response via your preferred communication channel (mail or Telegram). You can configure it by adding report configurations to the configuration file:
@ -89,20 +117,19 @@ KISSS has a feature that can send you daily reports. It basically requests the s
// Other configurations...
"reports": [
{
// Email configuration
"name": "Daily stats from KISSS",
"time": "15:00",
"query": "view=pages&ordercol=second&order=desc",
"from": "myemailaddress@mydomain.tld",
"to": "myemailaddress@mydomain.tld",
"smtpHost": "mail.mydomain.tld:587",
"smtpUser": "myemailaddress@mydomain.tld",
"smtpPassword": "mysecretpassword"
"to": "myemailaddress@mydomain.tld"
},
{
// Telegram configuration
"name": "Daily stats from KISSS",
"type": "telegram", // Add this for Telegram
"time": "15:00",
"query": "view=pages&ordercol=second&order=desc",
"tgBotToken": "TelegramBotToken",
"tgUserId": 123456
},
{
@ -112,7 +139,7 @@ KISSS has a feature that can send you daily reports. It basically requests the s
}
```
To use Telegram for reports, create a bot with the [Bot Father](https://t.me/BotFather) and request your user id from [@userinfobot](https://t.me/userinfobot).
You can find out your Telegram user id using [@userinfobot](https://t.me/userinfobot).
## License

View File

@ -14,6 +14,11 @@ type config struct {
DbPath string `json:"dbPath"`
StatsUsername string `json:"statsUsername"`
StatsPassword string `json:"statsPassword"`
SmtpFrom string `json:"smtpfrom"`
SmtpUser string `json:"smtpUser"`
SmtpPassword string `json:"smtpPassword"`
SmtpHost string `json:"smtpHost"`
TGBotToken string `json:"tgBotToken"`
Reports []report `json:"reports"`
}
@ -24,6 +29,7 @@ var (
DbPath: "data/kis3.db",
StatsUsername: "",
StatsPassword: "",
TGBotToken: "",
}
)

18
main.go
View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/gobuffalo/packr/v2"
"github.com/gorilla/mux"
"log"
@ -14,6 +15,7 @@ import (
type kis3 struct {
router *mux.Router
staticBox *packr.Box
tgBot *tgbotapi.BotAPI
}
var (
@ -29,11 +31,13 @@ func init() {
log.Fatal("Database setup failed:", e)
}
initRouter()
initTelegramBot()
}
func main() {
go startListeningToWeb()
go startReports()
go startStatsTelegram()
// Graceful stop
var gracefulStop = make(chan os.Signal, 1)
signal.Notify(gracefulStop, os.Interrupt, syscall.SIGTERM)
@ -48,6 +52,20 @@ func initRouter() {
initTrackingRouter()
}
func initTelegramBot() {
if appConfig.TGBotToken == "" {
fmt.Println("Telegram not configured.")
return
}
bot, e := tgbotapi.NewBotAPI(appConfig.TGBotToken)
if e != nil {
fmt.Println("Failed to setup Telegram:", e)
return
}
fmt.Println("Authorized Telegram bot on account", bot.Self.UserName)
app.tgBot = bot
}
func startListeningToWeb() {
port := appConfig.Port
addr := ":" + port

View File

@ -12,17 +12,12 @@ import (
)
type report struct {
Name string `json:"name"`
Time string `json:"time"`
Query string `json:"query"`
Type string `json:"type"`
To string `json:"to"`
From string `json:"from"`
SmtpUser string `json:"smtpUser"`
SmtpPassword string `json:"smtpPassword"`
SmtpHost string `json:"smtpHost"`
TGBotToken string `json:"tgBotToken"`
TGUserId int64 `json:"tgUserId"`
Name string `json:"name"`
Time string `json:"time"`
Query string `json:"query"`
Type string `json:"type"`
To string `json:"to"`
TGUserId int64 `json:"tgUserId"`
}
func startReports() {
@ -67,17 +62,17 @@ func sendReport(r *report, content []byte) {
}
func sendMail(r *report, content []byte) {
if r.To == "" || r.From == "" || r.SmtpUser == "" || r.SmtpHost == "" {
if r.To == "" || appConfig.SmtpFrom == "" || appConfig.SmtpUser == "" || appConfig.SmtpHost == "" {
fmt.Println("No valid report configuration")
return
}
smtpHostNoPort, _, _ := net.SplitHostPort(r.SmtpHost)
smtpHostNoPort, _, _ := net.SplitHostPort(appConfig.SmtpHost)
mail := email.NewEmail()
mail.From = r.From
mail.From = appConfig.SmtpFrom
mail.To = []string{r.To}
mail.Subject = "KISSS report: " + r.Name
mail.Text = content
e := mail.Send(r.SmtpHost, smtp.PlainAuth("", r.SmtpUser, r.SmtpPassword, smtpHostNoPort))
e := mail.Send(appConfig.SmtpHost, smtp.PlainAuth("", appConfig.SmtpUser, appConfig.SmtpPassword, smtpHostNoPort))
if e != nil {
fmt.Println("Sending report failed:", e)
return
@ -87,17 +82,12 @@ func sendMail(r *report, content []byte) {
}
func sendTelegram(r *report, content []byte) {
if r.TGUserId == 0 || r.TGBotToken == "" {
if r.TGUserId == 0 || app.tgBot == nil {
fmt.Println("No valid report configuration")
return
}
bot, e := tgbotapi.NewBotAPI(r.TGBotToken)
if e != nil {
fmt.Println("Sending report failed:", e)
return
}
msg := tgbotapi.NewMessage(r.TGUserId, r.Name+"\n\n"+string(content))
_, e = bot.Send(msg)
_, e := app.tgBot.Send(msg)
if e != nil {
fmt.Println("Sending report failed:", e)
return

View File

@ -3,14 +3,17 @@ package main
import (
"encoding/json"
"fmt"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/kis3/kis3/helpers"
"github.com/whiteshtef/clockwork"
"html/template"
"net/http"
"net/url"
"strconv"
"strings"
)
func initStatsRouter() {
func initStatsRouter() {
app.router.HandleFunc("/stats", StatsHandler)
}
@ -22,7 +25,25 @@ func StatsHandler(w http.ResponseWriter, r *http.Request) {
}
}
// Do request
queries := r.URL.Query()
queryValues := r.URL.Query()
result, e := doRequest(queryValues)
if e != nil {
fmt.Println("Database request failed:", e)
w.WriteHeader(500)
} else if result != nil {
w.Header().Set("Cache-Control", "max-age=0")
switch queryValues.Get("format") {
case "json":
sendJsonResponse(result, w)
case "chart":
sendChartResponse(result, w)
default: // "plain"
sendPlainResponse(result, w)
}
}
}
func doRequest(queries url.Values) (result []*RequestResultRow, e error) {
view := PAGES
switch strings.ToLower(queries.Get("view")) {
case "pages":
@ -48,7 +69,7 @@ func StatsHandler(w http.ResponseWriter, r *http.Request) {
case "count":
view = COUNT
}
result, e := request(&ViewsRequest{
result, e = request(&ViewsRequest{
view: view,
from: queries.Get("from"),
fromRel: queries.Get("fromrel"),
@ -61,20 +82,7 @@ func StatsHandler(w http.ResponseWriter, r *http.Request) {
order: strings.ToUpper(queries.Get("order")),
limit: queries.Get("limit"),
})
if e != nil {
fmt.Println("Database request failed:", e)
w.WriteHeader(500)
} else if result != nil {
w.Header().Set("Cache-Control", "max-age=0")
switch queries.Get("format") {
case "json":
sendJsonResponse(result, w)
case "chart":
sendChartResponse(result, w)
default: // "plain"
sendPlainResponse(result, w)
}
}
return
}
func sendPlainResponse(result []*RequestResultRow, w http.ResponseWriter) {
@ -120,3 +128,55 @@ func sendChartResponse(result []*RequestResultRow, w http.ResponseWriter) {
}
_ = t.Execute(w, data)
}
func startStatsTelegram() {
if app.tgBot == nil {
return
}
u := tgbotapi.NewUpdate(0)
scheduler := clockwork.NewScheduler()
scheduler.Schedule().Every(5).Seconds().Do(func() {
checkForTelegramUpdates(&u)
})
scheduler.Run()
}
func checkForTelegramUpdates(u *tgbotapi.UpdateConfig) {
updates, e := app.tgBot.GetUpdates(*u)
if e != nil {
return
}
for _, update := range updates {
if update.Message != nil && update.Message.Command() == "stats" {
response := ""
fakeUrl, e := url.Parse("/stats?" + update.Message.CommandArguments())
if e != nil {
response = "Request failed"
} else {
if appConfig.statsAuth() && (fakeUrl.Query().Get("username") != appConfig.StatsUsername || fakeUrl.Query().Get("password") != appConfig.StatsPassword) {
response = "Not authorized. Add username=yourusername&password=yourpassword to the query."
} else {
result, e := doRequest(fakeUrl.Query())
if e != nil {
response = "Request failed"
} else {
rowStrings := make([]string, len(result))
for i, row := range result {
rowStrings[i] = (*row).First + ": " + strconv.Itoa((*row).Second)
}
response = strings.Join(rowStrings, "\n")
}
}
}
msg := tgbotapi.NewMessage(update.Message.Chat.ID, response)
msg.ReplyToMessageID = update.Message.MessageID
_, e = app.tgBot.Send(msg)
if e != nil {
fmt.Println("Failed to send message:", e)
}
}
if update.UpdateID >= u.Offset {
u.Offset = update.UpdateID + 1
}
}
}