Skip to content

Commit

Permalink
Handle error status codes from Telegram.
Browse files Browse the repository at this point in the history
Rate limitation.
  • Loading branch information
muety committed Feb 5, 2018
1 parent 447180a commit 4b60e2c
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 18 deletions.
11 changes: 7 additions & 4 deletions README.md
Expand Up @@ -9,17 +9,17 @@ __I'm the [@MiddleMan](https://telegram.me/MiddleManBot) bot! I sit in the middl

I translate simple JSON HTTP requests into Telegram push messages that you will get on your Smartphone, PC or whatever Telegram client you have.

## What's new (2017-08-08) ?
* Support for webhook mode
* Read token and further config parameters from command-line arguments
## What's new (2018-02-05) ?
* Reacting to non-`200` status codes from Telegram API
* Message rate limitation per recipient (`--rateLimit` parameter). On my hosted instance, this is **set to 10 req / hour / recipient** from now on.

## Why might this be useful?
This is especially useful for __developers or sysadmins__. Imagine you want some kind of reporting from your application or server, like a daily report including some statistics. You don't want to actively look it up on a website but you want to receive it in a __passive fashion__. Just like getting an e-mail. But come on, let's be honest. __E-Mails are so 2010__. And they require your little server-side script to include some SMTP library and connect to a mail server. That's __too heavyweight__ just to __get some short information__. Personally, I have a Python script running on my server which gathers some statistics from log files and databases and regularly sends me a Telegram message.

If you develop those thoughts further, this could potentially __replace any kind of e-mail notifications__ - be it the message that someone has answered to your __forum post__, your favorite game is __now on sale at Steam__, and so on. It's __lightweight and easy__, unlike e-mails that have way too much overhead.

## How to run it?
You can either set up your own instance or use mine, which is running at [http://middleman.ferdinand-muetsch.de](https://middleman.ferdinand-muetsch.de). If you want to set this up on your own, do the following. You can either run the bot in long-polling- or webhook mode. For production use the latter option is recommended for [various reasons](https://core.telegram.org/bots/webhooks). However, you'll need a server with a static IP and s (self-signed) SSL certificate.
You can either set up your own instance or use mine, which is running at [http://middleman.ferdinand-muetsch.de](https://middleman.ferdinand-muetsch.de). The hosted instance only allows for a maxmimum of 240 requests per recipient per day. If you want to set this up on your own, do the following. You can either run the bot in long-polling- or webhook mode. For production use the latter option is recommended for [various reasons](https://core.telegram.org/bots/webhooks). However, you'll need a server with a static IP and s (self-signed) SSL certificate.
1. Make sure u have the latest version of Go installed.
2. `go get github.com/n1try/telegram-middleman-bot`
3. `cd <YOUR_GO_WORKSPACE_PATH>/src/github.com/n1try/telegram-middleman-bot`
Expand All @@ -36,6 +36,9 @@ You can either set up your own instance or use mine, which is running at [http:/

Alternatively, you can also use a __reverse proxy__ like _nginx_ or [_Caddy_](https://caddyserver.com) to handle encryption. In that case you would set the `mode` to _webhook_, but `useHttps` to _false_ and your bot wouldn't need any certificate.

### Additional parameters
* `--rateLimit` (`int`) - Maximum number of messages to be delivered to each recipient per hour. Defaults to `10`.

## How to use it?
1. You need to get a token from the bot. Send a message with `/start` to the [@MiddleManBot](https://telegram.me/MiddleManBot) therefore.
2. Now you can use that token to make HTTP POST requests to `http://localhost:8080/api/messages` (replace localhost by the hostname of your server running the bot or mine as shown above) with a body that looks like this.
Expand Down
42 changes: 34 additions & 8 deletions main.go
Expand Up @@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"time"
"errors"

"github.com/satori/go.uuid"
)
Expand All @@ -24,6 +25,8 @@ const STORE_KEY_REQUESTS = "totalRequests"
const STORE_KEY_MESSAGES = "messages"

var token string
var limiterMap map[string]int
var maxReqsPerHous int

func getApiUrl() string {
return BASE_URL + token
Expand Down Expand Up @@ -65,7 +68,6 @@ func messageHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(415)
return
}
StorePut(STORE_KEY_REQUESTS, StoreGet(STORE_KEY_REQUESTS).(int)+1)
dec := json.NewDecoder(r.Body)
var m InMessage
err := dec.Decode(&m)
Expand All @@ -88,6 +90,17 @@ func messageHandler(w http.ResponseWriter, r *http.Request) {
return
}

_, hasKey := limiterMap[recipientId]
if !hasKey {
limiterMap[recipientId] = 0
}
if limiterMap[recipientId] >= maxReqsPerHous {
w.WriteHeader(429)
w.Write([]byte(fmt.Sprintf("Request rate of %d per hour exceeded.", maxReqsPerHous)))
return
}
limiterMap[recipientId] += 1

err = sendMessage(recipientId, "*"+m.Origin+"* wrote:\n\n"+m.Text)
if err != nil {
w.WriteHeader(500)
Expand All @@ -97,6 +110,7 @@ func messageHandler(w http.ResponseWriter, r *http.Request) {
storedMessages := StoreGet(STORE_KEY_MESSAGES).(StoreMessageObject)
storedMessages = append(storedMessages, m.Text)
StorePut(STORE_KEY_MESSAGES, storedMessages)
StorePut(STORE_KEY_REQUESTS, StoreGet(STORE_KEY_REQUESTS).(int)+1)

w.WriteHeader(200)
}
Expand Down Expand Up @@ -140,6 +154,9 @@ func getUpdate() (*[]TelegramUpdate, error) {
if err != nil {
return nil, err
}
if response.StatusCode != 200 {
return nil, errors.New(string(data))
}

var update TelegramUpdateResponse
err = json.Unmarshal(data, &update)
Expand Down Expand Up @@ -180,6 +197,9 @@ func startPolling() {
for _, update := range *updates {
processUpdate(update)
}
} else {
log.Printf("ERROR getting updates: %s\n", err)
time.Sleep(POLL_TIMEOUT_SEC * time.Second)
}
}
}
Expand All @@ -191,16 +211,18 @@ func getConfig() BotConfig {
certPathPtr := flag.String("certPath", "", "Path of your SSL certificate when using webhook mode")
keyPathPtr := flag.String("keyPath", "", "Path of your private SSL key when using webhook mode")
portPtr := flag.Int("port", 8080, "Port for the webserver to listen on")
rateLimitPtr := flag.Int("rateLimit", 10, "Max number of requests per recipient per hour")

flag.Parse()

return BotConfig{
Token: *tokenPtr,
Mode: *modePtr,
UseHTTPS: *useHttpsPtr,
CertPath: *certPathPtr,
KeyPath: *keyPathPtr,
Port: *portPtr}
Token: *tokenPtr,
Mode: *modePtr,
UseHTTPS: *useHttpsPtr,
CertPath: *certPathPtr,
KeyPath: *keyPathPtr,
Port: *portPtr,
RateLimit: *rateLimitPtr}
}

func toJson(filePath string, data interface{}) {
Expand All @@ -224,15 +246,19 @@ func main() {

go func() {
for {
time.Sleep(30 * time.Minute)
time.Sleep(60 * time.Minute)
FlushStoreToBinary(STORE_FILE)
stats := Stats{TotalRequests: StoreGet(STORE_KEY_REQUESTS).(int), Timestamp: int(time.Now().Unix())}
toJson("stats.json", stats)

limiterMap = make(map[string]int)
}
}()

config := getConfig()
token = config.Token
limiterMap = make(map[string]int)
maxReqsPerHous = config.RateLimit

http.HandleFunc("/api/messages", messageHandler)

Expand Down
13 changes: 7 additions & 6 deletions model.go
Expand Up @@ -55,12 +55,13 @@ type TelegramUpdateResponse struct {
}

type BotConfig struct {
Token string
Mode string
UseHTTPS bool
CertPath string
KeyPath string
Port int
Token string
Mode string
UseHTTPS bool
CertPath string
KeyPath string
Port int
RateLimit int
}

type Stats struct {
Expand Down

0 comments on commit 4b60e2c

Please sign in to comment.