Skip to content

Instantly share code, notes, and snippets.

@barryz
Last active May 14, 2021 09:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barryz/c2a82ff6a50d69d101cb443c10dfb3ec to your computer and use it in GitHub Desktop.
Save barryz/c2a82ff6a50d69d101cb443c10dfb3ec to your computer and use it in GitHub Desktop.
A telegram bot that reports traffic usage of Tencent lighthouse.
package main
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
lighthouse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324"
"gopkg.in/tucnak/telebot.v2"
)
const tmpl = `Hi, 您的腾讯云轻量服务器用量如下:
实例ID: %s
已用流量: %s
可用流量: %s
套餐流量: %s
超出流量: %s
套餐起始时间: %s
套餐到期时间: %s
实例创建时间: %s
实例到期时间: %s
`
const (
ReportHour = 22
ReportMinute = 30
ReportSecond = 00
)
func main() {
ticker := newTicker()
defer ticker.stop()
log.Printf("tgbot process start")
for {
select {
case <-ticker.C():
if err := do(); err != nil {
log.Printf("ERR: report process failed %s", err.Error())
}
log.Printf("INFO: succeeded in processing report")
ticker.update()
}
}
}
func do() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
tx, err := txTrafficUsage(ctx)
if err != nil {
return errors.Wrap(err, "get tx usage failed")
}
ins, err := txInstanceDesc(ctx)
if err != nil {
return errors.Wrap(err, "get tx instance failed")
}
msg := fmt.Sprintf(tmpl,
instanceId,
byteSize(*tx.TrafficUsed),
byteSize(*tx.TrafficPackageRemaining),
byteSize(*tx.TrafficPackageTotal),
byteSize(*tx.TrafficOverflow),
formatTimeString(*tx.StartTime),
formatTimeString(*tx.EndTime),
formatTimeString(*ins.CreatedTime),
formatTimeString(*ins.ExpiredTime),
)
if err = sendTgMessage(msg); err != nil {
return errors.Wrap(err, "send message to tg failed")
}
return nil
}
var (
tgBotTotken = ""
UserId = 0
)
func sendTgMessage(msg string) error {
tb, err := telebot.NewBot(telebot.Settings{
Token: tgBotTotken,
Updates: 0,
Poller: &telebot.LongPoller{
Timeout: 10 * time.Second,
},
})
if err != nil {
return err
}
user := &telebot.User{ID: UserId}
_, err = tb.Send(user, msg)
if err != nil {
return err
}
return nil
}
var (
credential = common.NewCredential(
"foo",
"bar",
)
cpfEndpoint = "lighthouse.tencentcloudapi.com"
instanceId = "foobar"
region = "ap-hongkong"
)
func txInstanceDesc(ctx context.Context) (*lighthouse.Instance, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = cpfEndpoint
client, _ := lighthouse.NewClient(credential, region, cpf)
request := lighthouse.NewDescribeInstancesRequest()
request.InstanceIds = []*string{&instanceId}
response, err := client.DescribeInstances(request)
if err != nil {
return nil, err
}
if len(response.Response.InstanceSet) != 1 {
return nil, fmt.Errorf("too many instances desc returned")
}
return response.Response.InstanceSet[0], nil
}
func txTrafficUsage(ctx context.Context) (*lighthouse.TrafficPackage, error) {
cpf := profile.NewClientProfile()
cpf.HttpProfile.Endpoint = cpfEndpoint
client, err := lighthouse.NewClient(credential, region, cpf)
if err != nil {
return nil, err
}
request := lighthouse.NewDescribeInstancesTrafficPackagesRequest()
request.InstanceIds = []*string{&instanceId}
response, err := client.DescribeInstancesTrafficPackages(request)
if err != nil {
return nil, err
}
if len(response.Response.InstanceTrafficPackageSet) != 1 {
return nil, fmt.Errorf("bad instance traffic pkg returned")
}
if len(response.Response.InstanceTrafficPackageSet[0].TrafficPackageSet) != 1 {
return nil, fmt.Errorf("bad instance traffic pkg returned")
}
traffic := response.Response.InstanceTrafficPackageSet[0].TrafficPackageSet[0]
return traffic, nil
}
const (
BYTE = 1 << (10 * iota)
KILOBYTE
MEGABYTE
GIGABYTE
TERABYTE
PETABYTE
EXABYTE
)
// byteSize returns a human-readable byte string of the form 10M, 12.5K, and so forth. The following units are available:
// E: Exabyte
// P: Petabyte
// T: Terabyte
// G: Gigabyte
// M: Megabyte
// K: Kilobyte
// B: Byte
// The unit that results in the smallest number greater than or equal to 1 is always chosen.
func byteSize(bytes int64) string {
unit := ""
value := float64(bytes)
switch {
case bytes >= EXABYTE:
unit = "E"
value = value / EXABYTE
case bytes >= PETABYTE:
unit = "P"
value = value / PETABYTE
case bytes >= TERABYTE:
unit = "T"
value = value / TERABYTE
case bytes >= GIGABYTE:
unit = "G"
value = value / GIGABYTE
case bytes >= MEGABYTE:
unit = "M"
value = value / MEGABYTE
case bytes >= KILOBYTE:
unit = "K"
value = value / KILOBYTE
case bytes >= BYTE:
unit = "B"
case bytes == 0:
return "0B"
}
result := strconv.FormatFloat(value, 'f', 1, 64)
result = strings.TrimSuffix(result, ".0")
return result + unit
}
var (
shanghaiLoc, _ = time.LoadLocation("Asia/Shanghai")
)
func formatTimeString(s string) string {
t, err := time.ParseInLocation(time.RFC3339, s, shanghaiLoc)
if err != nil {
panic(err)
}
layout := "2006-01-02 15:04:05"
return t.Format(layout)
}
type Ticker struct {
timer *time.Timer
}
func newTicker() *Ticker {
return &Ticker{timer: time.NewTimer(nextTickDuration())}
}
func (t *Ticker) update() {
t.timer.Reset(nextTickDuration())
}
func (t *Ticker) stop() {
if t.timer != nil {
t.timer.Stop()
}
}
func (t *Ticker) C() <-chan time.Time {
if t.timer != nil {
return t.timer.C
}
return make(<-chan time.Time)
}
func nextTickDuration() time.Duration {
now := time.Now()
nextTick := time.Date(
now.Year(),
now.Month(),
now.Day(),
ReportHour,
ReportMinute,
ReportSecond,
0,
shanghaiLoc,
)
if now.After(nextTick) {
nextTick = nextTick.Add(24 * time.Hour)
}
return nextTick.Sub(now)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment