Merge branch 'release/1.0'

This commit is contained in:
Maxim Lebedev 2017-12-02 03:49:10 +05:00
commit fbadd19a79
29 changed files with 1385 additions and 1 deletions

7
.gitignore vendored
View File

@ -15,4 +15,9 @@
.glide/
# Golang project vendor packages which should be ignored
vendor/
vendor/
# Production files must not be in public repository
config.yaml
cert.key
cert.pem

8
.travis.yml Normal file
View File

@ -0,0 +1,8 @@
language: go
go:
- 1.9
- tip
install:
- go get

26
Makefile Normal file
View File

@ -0,0 +1,26 @@
# Build package by default with all general tools and things
all:
make localization
make build
# Build minimal package only
build:
go build
# Build debug version with more logs
debug:
go build -tags=debug
# Format the source code
fmt:
go fmt
# Build localization files with separated untranslated strings
translation:
goi18n merge -format yaml -sourceLanguage en-us -outdir ./i18n/ ./i18n/*
# Build localization files and merge untranslated strings
localization:
make translation
goi18n -format yaml -sourceLanguage en-us -outdir ./i18n/ ./i18n/*.all.yaml \
./i18n/*.untranslated.yaml

7
PATRONS.md Normal file
View File

@ -0,0 +1,7 @@
# [Patrons!](https://www.patreon.com/bePatron?c=243288)
**These people have sponsored the current version of the project:**
- **Ivan Kolesnikov**
- Daniil Tlenov
- Aurielb
- Yami Odymel
- MoD21k

107
add.go Normal file
View File

@ -0,0 +1,107 @@
package main
import (
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/go-telegram" // My Telegram bindings
)
func commandAdd(msg *telegram.Message, pack bool) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("reply_add_sticker"))
reply.ParseMode = telegram.ModeMarkdown
err = dbChangeUserState(msg.From.ID, stateAddSticker)
errCheck(err)
if pack {
reply.Text = T("reply_add_pack")
err = dbChangeUserState(msg.From.ID, stateAddPack)
errCheck(err)
}
log.Ln("Sending add reply...")
_, err = bot.SendMessage(reply)
errCheck(err)
}
func actionAdd(msg *telegram.Message, pack bool) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("success_add_sticker"))
reply.ParseMode = telegram.ModeMarkdown
switch {
case pack && msg.Sticker.SetName == "":
reply.Text = T("error_empty_add_pack", map[string]interface{}{
"AddStickerCommand": cmdAddSticker,
})
case pack && msg.Sticker.SetName != "":
set, err := bot.GetStickerSet(msg.Sticker.SetName)
errCheck(err)
log.Ln("SetTitle:", set.Title)
reply.Text = T("success_add_pack", map[string]interface{}{
"SetTitle": set.Title,
})
allExists := true
for _, sticker := range set.Stickers {
exists, err := dbAddSticker(msg.From.ID, sticker.FileID, sticker.Emoji)
errCheck(err)
if !exists {
allExists = false
}
}
log.Ln("All exists?", allExists)
if allExists {
reply.Text = T("error_already_add_pack", map[string]interface{}{
"SetTitle": set.Title,
})
} else {
markup := telegram.NewInlineKeyboardMarkup(
telegram.NewInlineKeyboardRow(
telegram.NewInlineKeyboardButtonSwitch(
T("button_share"),
" ",
),
),
)
reply.ReplyMarkup = &markup
}
default:
exists, err := dbAddSticker(msg.From.ID, msg.Sticker.FileID, msg.Sticker.Emoji)
errCheck(err)
if exists {
reply.Text = T("error_already_add_sticker")
}
if msg.Sticker.Emoji == "" {
msg.Sticker.Emoji = " "
}
markup := telegram.NewInlineKeyboardMarkup(
telegram.NewInlineKeyboardRow(
telegram.NewInlineKeyboardButtonSwitch(
T("button_share"),
msg.Sticker.Emoji,
),
),
)
reply.ReplyMarkup = &markup
}
_, err = bot.SendMessage(reply)
errCheck(err)
}

34
cancel.go Normal file
View File

@ -0,0 +1,34 @@
package main
import "github.com/toby3d/go-telegram" // My Telegram bindings
func commandCancel(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
state, err := dbGetUserState(msg.From.ID)
errCheck(err)
var text string
switch state {
case stateAddSticker:
text = T("cancel_add_sticker")
case stateAddPack:
text = T("cancel_add_pack")
case stateDelete:
text = T("cancel_del")
case stateReset:
text = T("cancel_reset")
default:
text = T("cancel_error")
}
err = dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, text)
_, err = bot.SendMessage(reply)
errCheck(err)
}

111
commands.go Normal file
View File

@ -0,0 +1,111 @@
package main
import (
"strings"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/botan"
"github.com/toby3d/go-telegram" // My Telegram bindings
)
const (
cmdAddPack = "addPack"
cmdAddSticker = "addSticker"
cmdCancel = "cancel"
cmdHelp = "help"
cmdDelete = "del"
cmdReset = "reset"
cmdStart = "start"
)
func commands(msg *telegram.Message) {
log.Ln("Received a", msg.Command(), "command")
switch strings.ToLower(msg.Command()) {
case strings.ToLower(cmdStart):
appMetrika.TrackAsync(
"Start", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandStart(msg)
<-metrika
case strings.ToLower(cmdHelp):
appMetrika.TrackAsync(
"Help", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandHelp(msg)
<-metrika
case strings.ToLower(cmdAddSticker):
appMetrika.TrackAsync(
"Add single sticker", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandAdd(msg, false)
<-metrika
case strings.ToLower(cmdAddPack):
appMetrika.TrackAsync(
"Add pack", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandAdd(msg, true)
<-metrika
case strings.ToLower(cmdDelete):
appMetrika.TrackAsync(
"Delete single sticker", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandDelete(msg)
<-metrika
case strings.ToLower(cmdReset):
appMetrika.TrackAsync(
"Reset", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandReset(msg)
<-metrika
case strings.ToLower(cmdCancel):
appMetrika.TrackAsync(
"Cancel", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
commandCancel(msg)
<-metrika
default:
appMetrika.Track("Command", msg.From.ID, *msg)
}
}

8
config.example.yaml Normal file
View File

@ -0,0 +1,8 @@
telegram:
token: 123456789:ABCd1efGhjKLM23O4pqR5stuvwx678yz90
webhook:
set: https://www.google.com
listen: /bot
serve: 0.0.0.0:2368
botan: 12345a67-8b9c-01d2-e345-67fg8901hj23

167
database.go Normal file
View File

@ -0,0 +1,167 @@
package main
import (
"fmt"
"strconv"
"strings"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/tidwall/buntdb" // Redis-like database
)
const (
stateNone = "none"
stateAddSticker = "addSticker"
stateAddPack = "addPack"
stateDelete = "del"
stateReset = "reset"
)
var db *buntdb.DB
func dbInit() {
log.Ln("Open database file...")
var err error
db, err = buntdb.Open("stickers.db")
errCheck(err)
log.Ln("Creating user_stickers index...")
err = db.CreateIndex(
"user_stickers", // name
"user:*:sticker:*", // pattern
buntdb.IndexString, // options
)
errCheck(err)
select {}
}
func dbChangeUserState(userID int, state string) error {
log.Ln("Trying to change", userID, "state to", state)
return db.Update(func(tx *buntdb.Tx) error {
_, _, err := tx.Set(
fmt.Sprint("user:", userID, ":state"), // key
state, // val
nil, // options
)
return err
})
}
func dbGetUserState(userID int) (string, error) {
log.Ln("Trying to get", userID, "state")
var state string
err := db.View(func(tx *buntdb.Tx) error {
var err error
state, err = tx.Get(fmt.Sprint("user:", userID, ":state"))
return err
})
switch err {
case buntdb.ErrNotFound:
log.Ln(userID, "not found, create new one")
if err := dbChangeUserState(userID, stateNone); err != nil {
return state, err
}
}
return state, err
}
func dbAddSticker(userID int, fileID, emoji string) (bool, error) {
log.Ln("Trying to add", fileID, "sticker from", userID, "user")
var exists bool
err := db.Update(func(tx *buntdb.Tx) error {
var err error
_, exists, err = tx.Set(
fmt.Sprint("user:", userID, ":sticker:", fileID), // key
emoji, // value
nil, // options
)
if err == buntdb.ErrIndexExists {
exists = true
return nil
}
return err
})
return exists, err
}
func dbDeleteSticker(userID int, fileID string) (bool, error) {
log.Ln("Trying to remove", fileID, "sticker from", userID, "user")
err := db.Update(func(tx *buntdb.Tx) error {
_, err := tx.Delete(fmt.Sprint("user:", userID, ":sticker:", fileID))
return err
})
switch err {
case buntdb.ErrNotFound:
log.Ln(userID, "not found, create new one")
return true, nil
}
return false, err
}
func dbResetUserStickers(userID int) error {
log.Ln("Trying reset all stickers of", userID, "user")
return db.Update(func(tx *buntdb.Tx) error {
var keys []string
err := tx.Ascend(
"user_stickers", // index
func(key, val string) bool { // iterator
subKeys := strings.Split(key, ":")
if subKeys[1] == strconv.Itoa(userID) {
keys = append(keys, key)
}
return true
},
)
if err != nil {
return err
}
for i := range keys {
_, err = tx.Delete(keys[i])
if err != nil {
break
}
}
return err
})
}
func dbGetUserStickers(userID int) ([]string, []string, error) {
log.Ln("Trying to get", userID, "stickers")
var stickers, emojis []string
err := db.View(func(tx *buntdb.Tx) error {
return tx.Ascend(
"user_stickers", // index
func(key, val string) bool { // iterator
subKeys := strings.Split(key, ":")
if subKeys[1] != strconv.Itoa(userID) {
return true
}
stickers = append(stickers, subKeys[3])
emojis = append(emojis, val)
return true
},
)
})
switch {
case err == buntdb.ErrNotFound:
log.Ln("Not found stickers")
return nil, nil, nil
case err != nil:
return nil, nil, err
}
return stickers, emojis, nil
}

62
delete.go Normal file
View File

@ -0,0 +1,62 @@
package main
import "github.com/toby3d/go-telegram" // My Telegram bindings
func commandDelete(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
stickers, _, err := dbGetUserStickers(msg.From.ID)
errCheck(err)
if len(stickers) <= 0 {
err = dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("error_empty_remove"))
_, err = bot.SendMessage(reply)
errCheck(err)
return
}
err = dbChangeUserState(msg.From.ID, stateDelete)
errCheck(err)
markup := telegram.NewInlineKeyboardMarkup(
telegram.NewInlineKeyboardRow(
telegram.NewInlineKeyboardButtonSwitchSelf(
T("button_remove"),
" ",
),
),
)
reply := telegram.NewMessage(msg.Chat.ID, T("reply_remove"))
reply.ParseMode = telegram.ModeMarkdown
reply.ReplyMarkup = &markup
_, err = bot.SendMessage(reply)
errCheck(err)
}
func actionDelete(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
notExist, err := dbDeleteSticker(msg.From.ID, msg.Sticker.FileID)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("success_remove"))
reply.ParseMode = telegram.ModeMarkdown
if notExist {
reply.Text = T("error_already_remove")
}
_, err = bot.SendMessage(reply)
errCheck(err)
}

13
docker-compose.yaml Normal file
View File

@ -0,0 +1,13 @@
version: "3"
services:
bot:
container_name: mypackbot
image: golang:latest
expose:
- 2368
volumes:
- ./MyPackBot:/go/MyPackBot
- ./i18n/:/go/i18n/
- ./config.yaml:/go/config.yaml
- ./stickers.db:/go/stickers.db
entrypoint: ["/go/MyPackBot", "-webhook"]

8
err_check.go Normal file
View File

@ -0,0 +1,8 @@
package main
// errCheck helps debug critical errors without warnings from 'gocyclo' linter
func errCheck(err error) {
if err != nil {
panic(err.Error())
}
}

59
get_updates_channel.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/go-telegram" // My Telegram bindings
)
// allowedUpdates is a value for parameter of updates configuration
var allowedUpdates = []string{
telegram.UpdateChosenInlineResult, // For collecting statistics
telegram.UpdateInlineQuery, // For searching and sending stickers
telegram.UpdateMessage, // For get commands and messages
}
// getUpdatesChannel return webhook or long polling channel with bot updates
func getUpdatesChannel() telegram.UpdatesChannel {
log.Ln("Preparing channel for updates...")
if !*flagWebhook {
log.Ln("Use LongPolling updates")
log.Ln("Deleting webhook if exists")
_, err := bot.DeleteWebhook()
errCheck(err)
return bot.NewLongPollingChannel(&telegram.GetUpdatesParameters{
Offset: 0,
Limit: 100,
Timeout: 60,
AllowedUpdates: allowedUpdates,
})
}
set := cfg.UString("telegram.webhook.set")
listen := cfg.UString("telegram.webhook.listen")
serve := cfg.UString("telegram.webhook.serve")
log.Ln(
"Trying set webhook on address:",
fmt.Sprint(set, listen, bot.AccessToken),
)
log.Ln("Creating new webhook...")
webhook := telegram.NewWebhook(
fmt.Sprint(set, listen, bot.AccessToken), nil,
)
webhook.MaxConnections = 100
webhook.AllowedUpdates = allowedUpdates
return bot.NewWebhookChannel(
webhook, // params
"", // certFile
"", // keyFile
set, // set
fmt.Sprint(listen, bot.AccessToken), // listen
serve, // serve
)
}

38
help.go Normal file
View File

@ -0,0 +1,38 @@
package main
import "github.com/toby3d/go-telegram" // My Telegram bindings
func commandHelp(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
err := dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
markup := telegram.NewInlineKeyboardMarkup(
telegram.NewInlineKeyboardRow(
telegram.NewInlineKeyboardButtonSwitch(
T("button_share"),
" ",
),
),
)
reply := telegram.NewMessage(
msg.Chat.ID, T("reply_help", map[string]interface{}{
"AddStickerCommand": cmdAddSticker,
"AddPackCommand": cmdAddPack,
"DeleteCommand": cmdDelete,
"ResetCommand": cmdReset,
"CancelCommand": cmdCancel,
"Username": bot.Self.Username,
}),
)
reply.ParseMode = telegram.ModeMarkdown
reply.ReplyMarkup = &markup
_, err = bot.SendMessage(reply)
errCheck(err)
}

99
i18n/en-us.all.yaml Normal file
View File

@ -0,0 +1,99 @@
button_inline_add:
one: You have {{.Count}} sticker. Add one more?
other: You have {{.Count}} stickers. Add one more?
button_inline_empty:
other: Your pack is empty
button_inline_nothing:
other: Not found stickers for {{.Query}}, add one?
button_remove:
other: Select sticker for remove
button_share:
other: Use your stickers pack!
cancel_add:
other: You cancelled the process of adding a new sticker to your pack.
cancel_add_pack:
other: You cancelled the process of adding a new pack to yours.
cancel_add_sticker:
other: You cancelled the process of adding a new sticker to your pack.
cancel_del:
other: You cancelled the process of removing a sticker from your pack.
cancel_error:
other: Nothing to cancel.
cancel_remove:
other: You cancelled the process of removing a sticker from your pack.
cancel_reset:
other: You cancelled the process of reseting your stickers pack.
error_already_add:
other: This sticker is already in your pack.
error_already_add_pack:
other: All stickers from *{{.SetTitle}}* pack is already in yours.
error_already_add_sticker:
other: This sticker is already in your pack.
error_already_del:
other: Maybe this sticker is already removed from your pack.
error_already_remove:
other: Maybe this sticker is already removed from your pack.
error_already_reset:
other: There is nothing to reset, pack is already empty.
error_empty_add_pack:
other: You try to add a sticker that exists outside the packs. Use /{{.AddStickerCommand}}
instead.
error_empty_del:
other: There is nothing to remove, pack is empty.
error_empty_remove:
other: There is nothing to remove, pack is empty.
error_reset_phrase:
other: Invalid phrase of resetting. This action has been canceled.
error_unknown:
other: |-
I do not know what to do with this sticker.
Please run /{{.AddStickerCommand}}, /{{.AddPackCommand}} or /{{.DeleteCommand}} command first.
meta_reset_1:
other: Wait. Who are you?
meta_reset_2:
other: What are you doing here?
meta_reset_3:
other: What am I doing here?
reply_add:
other: Send an existing sticker from any other pack to add it to yourself.
reply_add_pack:
other: Send an existing stickers from any other packs to add the entire packs to
yourself.
reply_add_sticker:
other: Send an existing stickers from any other packs to add it to yourself.
reply_del:
other: Send an existing stickers from your pack for removing it.
reply_help:
other: |-
/{{.AddStickerCommand}} - add a single sticker to your pack
/{{.AddPackCommand}} - add a full other pack to your pack
/{{.DeleteCommand}} - remove a single sticker from your pack
/{{.ResetCommand}} - remove all stickers from your pack
/{{.CancelCommand}} - cancel the current operation
To view and send stickers from your pack, just type `@{{.Username}}` (and space) in any chat.
reply_remove:
other: Send an existing sticker from your pack for removing it.
reply_reset:
other: |
This operation will remove *all* stickers from your pack and *this can't be undone*.
Send `{{.KeyPhrase}}` to confirm what you really want to reset my brain (oh god why).
Or use /{{.CancelCommand}} for abort current operation.
reply_start:
other: |
Hello, I'm the [@{{.Username}}](tg://user?id={{.ID}})!
I can create your personal pack with stickers from others packs.
Without limits and installing. In any chat. For free.
success_add:
other: The sticker was successfully added to your pack!
success_add_pack:
other: The sticker pack *{{.SetTitle}}* was successfully added to yours!
success_add_sticker:
other: The sticker was successfully added to your pack!
success_del:
other: The sticker was successfully removed from your pack!
success_remove:
other: The sticker was successfully removed from your pack!
success_reset:
other: The contents of your pack are completely reset!..

11
i18n/en-us.buttons.yaml Normal file
View File

@ -0,0 +1,11 @@
button_inline_empty:
other: Your pack is empty
button_inline_nothing:
other: Not found stickers for {{.Query}}, add one?
button_inline_add:
one: You have {{.Count}} sticker. Add one more?
other: You have {{.Count}} stickers. Add one more?
button_remove:
other: Select sticker for remove
button_share:
other: Use your stickers pack!

10
i18n/en-us.cancel.yaml Normal file
View File

@ -0,0 +1,10 @@
cancel_add_sticker:
other: You cancelled the process of adding a new sticker to your pack.
cancel_add_pack:
other: You cancelled the process of adding a new pack to yours.
cancel_del:
other: You cancelled the process of removing a sticker from your pack.
cancel_reset:
other: You cancelled the process of reseting your stickers pack.
cancel_error:
other: Nothing to cancel.

18
i18n/en-us.errors.yaml Normal file
View File

@ -0,0 +1,18 @@
error_already_add_sticker:
other: This sticker is already in your pack.
error_already_add_pack:
other: All stickers from *{{.SetTitle}}* pack is already in yours.
error_already_del:
other: Maybe this sticker is already removed from your pack.
error_already_reset:
other: There is nothing to reset, pack is already empty.
error_reset_phrase:
other: Invalid phrase of resetting. This action has been canceled.
error_empty_del:
other: There is nothing to remove, pack is empty.
error_empty_add_pack:
other: You try to add a sticker that exists outside the packs. Use /{{.AddStickerCommand}} instead.
error_unknown:
other: |
I do not know what to do with this sticker.
Please run /{{.AddStickerCommand}}, /{{.AddPackCommand}} or /{{.DeleteCommand}} command first.

6
i18n/en-us.meta.yaml Normal file
View File

@ -0,0 +1,6 @@
meta_reset_1:
other: Wait. Who are you?
meta_reset_2:
other: What are you doing here?
meta_reset_3:
other: What am I doing here?

26
i18n/en-us.replies.yaml Normal file
View File

@ -0,0 +1,26 @@
reply_start:
other: |
Hello, I'm the [@{{.Username}}](tg://user?id={{.ID}})!
I can create your personal pack with stickers from others packs.
Without limits and installing. In any chat. For free.
reply_add_sticker:
other: Send an existing stickers from any other packs to add it to yourself.
reply_add_pack:
other: Send an existing stickers from any other packs to add the entire packs to yourself.
reply_del:
other: Send an existing stickers from your pack for removing it.
reply_reset:
other: |
This operation will remove *all* stickers from your pack and *this can't be undone*.
Send `{{.KeyPhrase}}` to confirm what you really want to reset my brain (oh god why).
Or use /{{.CancelCommand}} for abort current operation.
reply_help:
other: |
/{{.AddStickerCommand}} - add a single sticker to your pack
/{{.AddPackCommand}} - add a full other pack to your pack
/{{.DeleteCommand}} - remove a single sticker from your pack
/{{.ResetCommand}} - remove all stickers from your pack
/{{.CancelCommand}} - cancel the current operation
To view and send stickers from your pack, just type `@{{.Username}}` (and space) in any chat.

8
i18n/en-us.success.yaml Normal file
View File

@ -0,0 +1,8 @@
success_add_sticker:
other: The sticker was successfully added to your pack!
success_add_pack:
other: The sticker pack *{{.SetTitle}}* was successfully added to yours!
success_del:
other: The sticker was successfully removed from your pack!
success_reset:
other: The contents of your pack are completely reset!..

View File

@ -0,0 +1 @@
{}

68
init.go Normal file
View File

@ -0,0 +1,68 @@
package main
import (
"flag"
"os"
"path/filepath"
"strings"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/nicksnyder/go-i18n/i18n" // Internationalization and localization
"github.com/olebedev/config" // Easy configuration file parsing
"github.com/toby3d/botan"
)
var (
// Variables with types from imports
cfg *config.Config
appMetrika *botan.Botan
// Setted variables
metrika = make(chan bool)
flagWebhook = flag.Bool(
"webhook",
false,
"enable work via webhooks (required valid certificates)",
)
)
// init prepare configuration and other things for successful start of main
// function.
func init() {
log.Ln("Initializing...")
log.Ln("Parse flags...")
flag.Parse()
err := filepath.Walk("./i18n/", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".all.yaml") {
i18n.MustLoadTranslationFile(path)
}
return nil
})
errCheck(err)
log.Ln("Loading configuration file...")
cfg, err = config.ParseYamlFile("config.yaml")
errCheck(err)
appMetrika = botan.New(cfg.UString("botan"))
log.Ln("Checking bot access token in configuration file...")
_, err = cfg.String("telegram.token")
errCheck(err)
if *flagWebhook {
log.Ln("Enabled webhook mode, check configuration strings...")
log.Ln("Checking webhook set string...")
_, err = cfg.String("telegram.webhook.set")
errCheck(err)
log.Ln("Checking webhook listen string...")
_, err = cfg.String("telegram.webhook.listen")
errCheck(err)
log.Ln("Checking webhook listen string...")
_, err = cfg.String("telegram.webhook.serve")
errCheck(err)
}
}

132
inline_query.go Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"strconv"
"strings"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/go-telegram" // My Telegram bindings
)
const perPage = 50
var r = strings.NewReplacer(
"🏻", "",
"🏼", "",
"🏽", "",
"🏾", "",
"🏿", "",
)
func inlineQuery(inline *telegram.InlineQuery) {
inline.Query = r.Replace(inline.Query)
log.Ln("Let's preparing answer...")
T, err := switchLocale(inline.From.LanguageCode)
errCheck(err)
log.Ln("INLINE OFFSET:", inline.Offset)
if inline.Offset == "" {
inline.Offset = "-1"
}
offset, err := strconv.Atoi(inline.Offset)
errCheck(err)
if offset == -1 {
offset++
}
answer := &telegram.AnswerInlineQueryParameters{
InlineQueryID: inline.ID,
CacheTime: 1,
IsPersonal: true,
}
stickers, emojis, err := dbGetUserStickers(inline.From.ID)
errCheck(err)
packSize := len(stickers)
if inline.Query != "" {
var buffer []string
for i := range stickers {
if emojis[i] != inline.Query {
continue
}
buffer = append(buffer, stickers[i])
}
stickers = buffer
}
totalStickers := len(stickers)
totalPages := totalStickers / perPage
if totalStickers == 0 {
if inline.Query != "" {
// If search stickers by emoji return 0 results
answer.SwitchPrivateMessageText = T(
"button_inline_nothing",
map[string]interface{}{"Query": inline.Query},
)
answer.SwitchPrivateMessageParameter = cmdAddSticker
} else {
// If query is empty and get 0 stickers
answer.SwitchPrivateMessageText = T("button_inline_empty")
answer.SwitchPrivateMessageParameter = cmdAddSticker
}
} else {
log.Ln("LESS THAN:", offset < totalPages)
if offset < totalPages {
from := offset * perPage
if offset > 0 {
from--
}
to := from + perPage
log.Ln("from:", from)
log.Ln("to:", to)
stickers = stickers[from:to]
offset++
answer.NextOffset = strconv.Itoa(offset)
} else {
from := offset * perPage
if offset > 0 {
from--
}
to := from
log.Ln("MINUS:", totalStickers%perPage)
if totalStickers%perPage != 0 {
log.Ln("FUCK")
to += totalStickers % perPage
} else {
to += perPage
}
log.Ln("from:", from)
log.Ln("to:", to)
stickers = stickers[from:to]
}
log.Ln("Stickers after checks:", len(stickers))
var results = make([]interface{}, len(stickers))
for i, sticker := range stickers {
results[i] = telegram.NewInlineQueryResultCachedSticker(
sticker, sticker,
)
}
answer.SwitchPrivateMessageText = T(
"button_inline_add",
packSize,
map[string]interface{}{
"Count": packSize,
})
answer.SwitchPrivateMessageParameter = cmdAddSticker
answer.Results = results
}
_, err = bot.AnswerInlineQuery(answer)
errCheck(err)
}

73
main.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/botan"
"github.com/toby3d/go-telegram" // My Telegram bindings
)
// bot is general structure of the bot
var bot *telegram.Bot
// main function is a general function for work of this bot
func main() {
log.Ln("Let'g Get It Started...")
var err error
go dbInit()
log.Ln("Initializing new bot via checking access_token...")
bot, err = telegram.NewBot(cfg.UString("telegram.token"))
errCheck(err)
log.Ln("Let's check updates channel!")
for update := range getUpdatesChannel() {
switch {
case update.ChosenInlineResult != nil:
log.Ln("Get ChosenInlineResult update")
appMetrika.Track(
"Chosen inline result",
update.ChosenInlineResult.From.ID,
*update.ChosenInlineResult,
)
case update.InlineQuery != nil:
// Just don't check same updates
log.D(update.InlineQuery.Query)
if len(update.InlineQuery.Query) > 25 {
continue
}
appMetrika.TrackAsync(
"Inline query", update.InlineQuery.From.ID, *update.InlineQuery,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
inlineQuery(update.InlineQuery)
<-metrika
case update.Message != nil:
if update.Message.From.ID == bot.Self.ID {
log.Ln("Received a message from myself, ignore this update")
return
}
if update.Message.ForwardFrom != nil {
if update.Message.ForwardFrom.ID == bot.Self.ID {
log.Ln("Received a forward from myself, ignore this update")
return
}
}
messages(update.Message)
default:
log.Ln("Get unsupported update")
}
continue
}
err = db.Close()
errCheck(err)
}

136
messages.go Normal file
View File

@ -0,0 +1,136 @@
package main
import (
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/botan"
"github.com/toby3d/go-telegram" // My Telegram bindings
)
// message function check Message update on commands, sended stickers or other
// user stuff
func messages(msg *telegram.Message) {
if msg.IsCommand() {
commands(msg)
return
}
state, err := dbGetUserState(msg.From.ID)
errCheck(err)
switch state {
case stateNone:
appMetrika.TrackAsync(
"Nothing", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
reply := telegram.NewMessage(
msg.Chat.ID,
T("error_unknown", map[string]interface{}{
"AddStickerCommand": cmdAddSticker,
"AddPackCommand": cmdAddPack,
"DeleteCommand": cmdDelete,
}))
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
<-metrika
case stateAddSticker:
if msg.Sticker == nil {
appMetrika.Track("Message", msg.From.ID, *msg)
return
}
log.D(msg.Sticker)
log.D(msg.Sticker.Emoji)
appMetrika.TrackAsync(
"Add single sticker", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
actionAdd(msg, false)
<-metrika
case stateAddPack:
if msg.Sticker == nil {
appMetrika.Track("Message", msg.From.ID, *msg)
return
}
appMetrika.TrackAsync(
"Add pack", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
log.D(msg.Sticker)
log.D(msg.Sticker.Emoji)
actionAdd(msg, true)
<-metrika
case stateDelete:
if msg.Sticker == nil {
appMetrika.Track("Message", msg.From.ID, *msg)
return
}
appMetrika.TrackAsync(
"Delete sticker", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
log.D(msg.Sticker)
log.D(msg.Sticker.Emoji)
actionDelete(msg)
<-metrika
case stateReset:
appMetrika.TrackAsync(
"Reset pack", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
actionReset(msg)
<-metrika
default:
appMetrika.TrackAsync(
"Message", msg.From.ID, *msg,
func(answer *botan.Answer, err error) {
log.Ln("Asynchonous:", answer.Status)
metrika <- true
},
)
err = dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
messages(msg)
<-metrika
}
}

88
reset.go Normal file
View File

@ -0,0 +1,88 @@
package main
import (
"fmt"
"time"
"github.com/toby3d/go-telegram" // My Telegram bindings
)
const keyPhrase = "Yes, I am totally sure."
func commandReset(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
stickers, _, err := dbGetUserStickers(msg.From.ID)
errCheck(err)
if len(stickers) <= 0 {
err = dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("error_already_reset"))
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
return
}
err = dbChangeUserState(msg.From.ID, stateReset)
errCheck(err)
reply := telegram.NewMessage(
msg.Chat.ID,
T("reply_reset", map[string]interface{}{
"KeyPhrase": keyPhrase,
"CancelCommand": cmdCancel,
}))
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
}
func actionReset(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
err := dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
if msg.Text != keyPhrase {
reply := telegram.NewMessage(msg.Chat.ID, T("error_reset_phrase"))
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
return
}
err = dbResetUserStickers(msg.From.ID)
errCheck(err)
reply := telegram.NewMessage(msg.Chat.ID, T("success_reset"))
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
for i := 1; i <= 3; i++ {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
text := T(fmt.Sprint("meta_reset_", i))
time.Sleep(time.Minute * time.Duration(len(text)) / 1000)
reply = telegram.NewMessage(msg.Chat.ID, text)
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
}
}

37
start.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"strings"
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/toby3d/go-telegram" // My Telegram bindings
)
func commandStart(msg *telegram.Message) {
bot.SendChatAction(msg.Chat.ID, telegram.ActionTyping)
err := dbChangeUserState(msg.From.ID, stateNone)
errCheck(err)
if msg.HasArgument() {
log.Ln("Received a", msg.Command(), "command with", msg.CommandArgument(), "argument")
if strings.ToLower(msg.CommandArgument()) == strings.ToLower(cmdAddSticker) {
commandAdd(msg, false)
return
}
}
T, err := switchLocale(msg.From.LanguageCode)
errCheck(err)
reply := telegram.NewMessage(
msg.Chat.ID, T("reply_start", map[string]interface{}{
"Username": bot.Self.Username,
"ID": bot.Self.ID,
}),
)
reply.ParseMode = telegram.ModeMarkdown
_, err = bot.SendMessage(reply)
errCheck(err)
}

18
switch_locale.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
log "github.com/kirillDanshin/dlog" // Insert logs only in debug builds
"github.com/nicksnyder/go-i18n/i18n" // Internationalization and localization
)
const langDefault = "en-us"
func switchLocale(langCode string) (T i18n.TranslateFunc, err error) {
log.Ln("Check", langCode, "localization")
T, err = i18n.Tfunc(langCode)
if err != nil {
log.Ln("Unsupported language, change to 'en-us' by default")
T, err = i18n.Tfunc(langDefault)
}
return
}