diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..88457b6 --- /dev/null +++ b/.env-example @@ -0,0 +1,4 @@ +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHANNEL=@viz_news +VIZ_NODE=https://node.viz.cx +DB_PATH=database diff --git a/.gitignore b/.gitignore index 66fd13c..d7cd66d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +.env +.vscode/ +database/ +__debug_bin +viz-news-bot + # Binaries for programs and plugins *.exe *.exe~ diff --git a/README.md b/README.md index 08161fb..61e08a5 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# viz-news \ No newline at end of file +# VIZ News bot + +Script for tracking telegram channels which added VIZ social bot, then publishing this list to telegram channel. \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..bfc54a6 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/bin/bash +GOOS=linux GOARCH=amd64 go build diff --git a/main.go b/main.go new file mode 100644 index 0000000..d39a4fd --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "html" + "log" + "math/rand" + "os" + "regexp" + "strconv" + "time" + + "github.com/VIZ-Blockchain/viz-go-lib" + "github.com/VIZ-Blockchain/viz-go-lib/operations" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" + _ "github.com/joho/godotenv/autoload" + "github.com/syndtr/goleveldb/leveldb" +) + +func main() { + db, err := leveldb.OpenFile(os.Getenv("DB_PATH"), nil) + defer db.Close() + + bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_BOT_TOKEN")) + if err != nil { + log.Panic(err) + } + + err = start(db, bot) + if err != nil { + log.Panic(err) + } +} + +func start(db *leveldb.DB, bot *tgbotapi.BotAPI) error { + cls, _ := viz.NewClient(os.Getenv("VIZ_NODE")) + defer cls.Close() + + config, err := cls.API.GetConfig() + if err != nil { + return err + } + + props, err := cls.API.GetDynamicGlobalProperties() + if err != nil { + return err + } + lastBlock := props.LastIrreversibleBlockNum + + log.Printf("---> Entering the block processing loop (last block = %v)\n", lastBlock) + for { + props, err := cls.API.GetDynamicGlobalProperties() + if err != nil { + return err + } + + for props.LastIrreversibleBlockNum-lastBlock > 0 { + block, err := cls.API.GetBlock(lastBlock) + if err != nil { + return err + } + log.Printf("Received block %v", block.Number) + + for _, tx := range block.Transactions { + for _, operation := range tx.Operations { + switch op := operation.Data().(type) { + case *operations.AwardOperation: + channel := getChannel(op.Memo) + if channel != "" { + err = saveChannel(db, bot, channel) + if err != nil { + log.Println(err) + } + } + // case *operations.UnknownOperation: + // log.Printf("Unkowned operation receivded: %v+\n", op) + } + } + } + + lastBlock++ + } + + time.Sleep(time.Duration(config.BlockInterval) * time.Second) + } +} + +func getChannel(str string) string { + var re = regexp.MustCompile(`(?m)channel:(@[a-z0-9_]+):`) + var arr = re.FindStringSubmatch(str) + if len(arr) > 1 { + return arr[1] + } + return "" +} + +func saveChannel(db *leveldb.DB, bot *tgbotapi.BotAPI, channel string) error { + data, _ := db.Get([]byte(channel), nil) + if data != nil { + return nil // already saved + } + err := db.Put([]byte(channel), []byte("true"), nil) + if err != nil { + return err + } + c, err := bot.GetChat(tgbotapi.ChatConfig{SuperGroupUsername: channel}) + if err != nil { + return err + } + + text := randomEmoji() + " Новый #канал с ботом: \n\n" + c.Title + " — @" + c.UserName + "\n\n *** \n\n" + c.Description + "\n\n ***" + msg := tgbotapi.NewMessageToChannel(os.Getenv("TELEGRAM_CHANNEL"), text) + _, err = bot.Send(msg) + return err +} + +func randomEmoji() string { + rand.Seed(time.Now().UnixNano()) + // http://apps.timwhitlock.info/emoji/tables/unicode + emoji := [][]int{ + // Emoticons icons + {128513, 128591}, + // Transport and map symbols + {128640, 128704}, + } + r := emoji[rand.Int()%len(emoji)] + min := r[0] + max := r[1] + n := rand.Intn(max-min+1) + min + return html.UnescapeString("&#" + strconv.Itoa(n) + ";") +}