diff --git a/.gitignore b/.gitignore index 3a7085c..84871e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,13 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +# Project-local glide cache +# RE: https://github.com/Masterminds/glide/issues/736 .glide/ -# Golang project vendor packages which should be ignored -vendor/ - # Production files must not be in public repository -config.yaml +MyPackBot +stickers.db +configs/config.yaml cert.key cert.pem \ No newline at end of file diff --git a/Makefile b/Makefile index a507ec5..34ed310 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,15 @@ fmt: # Build localization files with separated untranslated strings translation: - goi18n merge -format yaml -sourceLanguage en -outdir ./i18n/ ./i18n/*/* + goi18n merge -format yaml \ + -sourceLanguage en \ + -outdir ./translations/ \ + ./translations/src/*/* # Build localization files and merge untranslated strings localization: make translation - goi18n -format yaml -sourceLanguage en -outdir ./i18n/ ./i18n/*.all.yaml \ - ./i18n/*.untranslated.yaml \ No newline at end of file + goi18n -format yaml \ + -sourceLanguage en \ + -outdir ./translations/ \ + ./translations/*.all.yaml ./translations/*.untranslated.yaml \ No newline at end of file diff --git a/PATRONS.md b/PATRONS.md deleted file mode 100644 index 5ca2eb0..0000000 --- a/PATRONS.md +++ /dev/null @@ -1,6 +0,0 @@ -# [Patrons!](https://www.patreon.com/bePatron?c=243288) -**These people have sponsored the current version of the project:** -- Daniil Tlenov -- Aurielb -- Yami Odymel -- MoD21k \ No newline at end of file diff --git a/actions.go b/actions.go deleted file mode 100755 index 6dff3b7..0000000 --- a/actions.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -// actions function check Message update on commands, sended stickers or other user stuff -func actions(msg *tg.Message) { - state, err := dbGetUserState(msg.From.ID) - errCheck(err) - - log.Ln("state:", state) - switch state { - case stateAddSticker: - actionAdd(msg, false) - case stateAddPack: - actionAdd(msg, true) - case stateDeleteSticker: - actionDelete(msg, false) - case stateDeletePack: - actionDelete(msg, true) - case stateReset: - actionReset(msg) - default: - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - actionError(msg) - } -} diff --git a/add.go b/add.go deleted file mode 100644 index 2228895..0000000 --- a/add.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -func commandAdd(msg *tg.Message, pack bool) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("reply_add_sticker")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getCancelButton(T) - - 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 *tg.Message, pack bool) { - if !msg.IsSticker() { - return - } - - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("success_add_sticker")) - reply.ParseMode = tg.ModeMarkdown - - if !pack { - var exist bool - sticker := msg.Sticker - exist, err = dbAddSticker( - msg.From.ID, sticker.SetName, sticker.FileID, sticker.Emoji, - ) - errCheck(err) - - if exist { - reply.Text = T("error_already_add_sticker") - } - - reply.ReplyMarkup = getCancelButton(T) - _, err = bot.SendMessage(reply) - errCheck(err) - return - } - - reply.Text = T("error_empty_add_pack", map[string]interface{}{ - "AddStickerCommand": cmdAddSticker, - }) - - if msg.Sticker.SetName != "" { - var set *tg.StickerSet - 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 { - var exist bool - exist, err = dbAddSticker( - msg.From.ID, sticker.SetName, sticker.FileID, sticker.Emoji, - ) - errCheck(err) - - if !exist { - allExists = false - } - } - - log.Ln("All exists?", allExists) - if allExists { - reply.Text = T("error_already_add_pack", map[string]interface{}{ - "SetTitle": set.Title, - }) - } - } - - reply.ReplyMarkup = getCancelButton(T) - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/.travis.yml b/build/ci/.travis.yml similarity index 50% rename from .travis.yml rename to build/ci/.travis.yml index e32d8af..558c7a5 100644 --- a/.travis.yml +++ b/build/ci/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.9 - tip install: diff --git a/cancel.go b/cancel.go deleted file mode 100755 index 4d4150f..0000000 --- a/cancel.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import tg "github.com/toby3d/telegram" - -func commandCancel(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - state, err := dbGetUserState(msg.From.ID) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - var text string - switch state { - case stateAddSticker: - text = T("cancel_add_sticker") - case stateAddPack: - text = T("cancel_add_pack") - case stateDeleteSticker: - text = T("cancel_del_sticker") - case stateDeletePack: - text = T("cancel_del_pack") - case stateReset: - text = T("cancel_reset") - default: - text = T("cancel_error") - } - - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, text) - reply.ReplyMarkup = getMenuKeyboard(T) - - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/commands.go b/commands.go deleted file mode 100755 index 5ef76eb..0000000 --- a/commands.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -const ( - cmdAddPack = "addPack" - cmdAddSticker = "addSticker" - cmdCancel = "cancel" - cmdHelp = "help" - cmdDeleteSticker = "delSticker" - cmdDeletePack = "delPack" - cmdReset = "reset" - cmdStart = "start" -) - -func commands(msg *tg.Message) { - log.Ln("command:", msg.Command()) - switch { - case msg.IsCommand(cmdStart): - commandStart(msg) - case msg.IsCommand(cmdHelp): - commandHelp(msg) - case msg.IsCommand(cmdAddSticker): - commandAdd(msg, false) - case msg.IsCommand(cmdAddPack): - commandAdd(msg, true) - case msg.IsCommand(cmdDeleteSticker): - commandDelete(msg, false) - case msg.IsCommand(cmdDeletePack): - commandDelete(msg, true) - case msg.IsCommand(cmdReset): - commandReset(msg) - case msg.IsCommand(cmdCancel): - commandCancel(msg) - } -} diff --git a/config.example.yaml b/configs/config.example.yaml similarity index 66% rename from config.example.yaml rename to configs/config.example.yaml index 18713d5..484a4e0 100644 --- a/config.example.yaml +++ b/configs/config.example.yaml @@ -1,7 +1,7 @@ telegram: token: 123456789:ABCd1efGhjKLM23O4pqR5stuvwx678yz90 webhook: - set: https://www.google.com + set: https://toby3d.github.io listen: /bot serve: 0.0.0.0:2368 channel: -1000000000000 \ No newline at end of file diff --git a/database.go b/database.go deleted file mode 100755 index 1d297f6..0000000 --- a/database.go +++ /dev/null @@ -1,244 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "strings" - - log "github.com/kirillDanshin/dlog" - "github.com/tidwall/buntdb" -) - -const ( - stateNone = "none" - stateAddSticker = "addSticker" - stateAddPack = "addPack" - stateDeleteSticker = "delSticker" - stateDeletePack = "delPack" - stateReset = "reset" - - setUploaded = "?" -) - -var db *buntdb.DB - -func dbInit() { - log.Ln("Open database file...") - var err error - db, err = buntdb.Open("stickers.db") - errCheck(err) - - select {} -} - -func dbGetUsers() ([]int, error) { - var users []int - err := db.View(func(tx *buntdb.Tx) error { - return tx.AscendKeys( - "user:*:state", - func(key, val string) bool { - subKeys := strings.Split(key, ":") - id, err := strconv.Atoi(subKeys[1]) - if err == nil { - users = append(users, id) - } - - return true - }, - ) - }) - if err == buntdb.ErrNotFound { - return nil, nil - } - - return users, err -} - -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"), state, nil) - 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, setName, fileID, emoji string) (bool, error) { - log.Ln("Trying to add", fileID, "sticker from", userID, "user") - if setName == "" { - setName = setUploaded - } - - var exists bool - err := db.Update(func(tx *buntdb.Tx) error { - var err error - _, exists, err = tx.Set( - fmt.Sprint("user:", userID, ":set:", setName, ":sticker:", fileID), // key - emoji, // value - nil, // options - ) - - if err == buntdb.ErrIndexExists { - exists = true - return nil - } - - return err - }) - - return exists, err -} - -func dbDeleteSticker(userID int, setName, fileID string) (bool, error) { - log.Ln("Trying to remove", fileID, "sticker from", userID, "user") - if setName == "" { - setName = setUploaded - } - - err := db.Update(func(tx *buntdb.Tx) error { - _, err := tx.Delete( - fmt.Sprint("user:", userID, ":set:", setName, ":sticker:", fileID), - ) - return err - }) - - switch err { - case buntdb.ErrNotFound: - log.Ln(userID, "not found, create new one") - return true, nil - } - - return false, err -} - -func dbDeletePack(userID int, setName string) (bool, error) { - log.Ln("Trying to remove all", setName, "sticker from", userID, "user") - if setName == "" { - setName = setUploaded - } - - var fileIDs []string - err := db.View(func(tx *buntdb.Tx) error { - return tx.AscendKeys( - fmt.Sprint("user:", userID, ":set:", setName, ":*"), - func(key, val string) bool { - keys := strings.Split(key, ":") - fileIDs = append(fileIDs, keys[5]) - return true - }, - ) - }) - - if len(fileIDs) == 0 { - return true, nil - } - - for _, fileID := range fileIDs { - var notExist bool - notExist, err = dbDeleteSticker(userID, setName, fileID) - if err != nil { - return notExist, err - } - } - - switch err { - case buntdb.ErrNotFound: - log.Ln(userID, "not found") - 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 - if err := tx.AscendKeys( - fmt.Sprint("user:", userID, ":set:*"), // index - func(key, val string) bool { // iterator - subKeys := strings.Split(key, ":") - if subKeys[1] == strconv.Itoa(userID) { - keys = append(keys, key) - } - return true - }, - ); err != nil { - return err - } - - for i := range keys { - _, err := tx.Delete(keys[i]) - if err != nil { - break - } - } - - return nil - }) -} - -func dbGetUserStickers(userID, offset int, query string) ([]string, int, error) { - log.Ln("Trying to get", userID, "stickers") - var total, count int - var stickers []string - offset = offset * 50 - - err := db.View(func(tx *buntdb.Tx) error { - return tx.AscendKeys( - fmt.Sprint("user:", userID, ":set:*"), // index - func(key, val string) bool { // iterator - subKeys := strings.Split(key, ":") - if subKeys[1] != strconv.Itoa(userID) { - return true - } - - total++ - if count >= 51 { - return true - } - - if total < offset { - return true - } - - if query != "" && !strings.Contains(query, val) { - return true - } - - count++ - stickers = append(stickers, subKeys[5]) - return true - }, - ) - }) - - switch { - case err == buntdb.ErrNotFound: - log.Ln("Not found stickers") - return nil, total, nil - case err != nil: - return nil, total, err - } - - return stickers, total, nil -} diff --git a/delete.go b/delete.go deleted file mode 100755 index 2022b01..0000000 --- a/delete.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -func commandDelete(msg *tg.Message, pack bool) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, total, err := dbGetUserStickers(msg.From.ID, 0, "") - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - if total <= 0 { - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("error_empty_del")) - reply.ReplyMarkup = getMenuKeyboard(T) - _, err = bot.SendMessage(reply) - errCheck(err) - return - } - - reply := tg.NewMessage(msg.Chat.ID, T("reply_del_sticker")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getCancelButton(T) - - err = dbChangeUserState(msg.From.ID, stateDeleteSticker) - errCheck(err) - - if pack { - err = dbChangeUserState(msg.From.ID, stateDeletePack) - errCheck(err) - - reply.Text = T("reply_del_pack") - } - - _, err = bot.SendMessage(reply) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply = tg.NewMessage(msg.Chat.ID, T("reply_switch_button")) - reply.ReplyMarkup = getSwitchButton(T) - _, err = bot.SendMessage(reply) - errCheck(err) -} - -func actionDelete(msg *tg.Message, pack bool) { - if !msg.IsSticker() { - return - } - - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("success_del_sticker")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getCancelButton(T) - - var notExist bool - if pack { - var set *tg.StickerSet - set, err = bot.GetStickerSet(msg.Sticker.SetName) - errCheck(err) - - log.Ln("SetName:", set.Title) - reply.Text = T("success_del_pack", map[string]interface{}{ - "SetTitle": set.Title, - }) - - notExist, err = dbDeletePack(msg.From.ID, msg.Sticker.SetName) - if notExist { - reply.Text = T("error_already_del_pack") - } - } else { - notExist, err = dbDeleteSticker(msg.From.ID, msg.Sticker.SetName, msg.Sticker.FileID) - if notExist { - reply.Text = T("error_already_del_sticker") - } - } - errCheck(err) - - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/docker-compose.yaml b/deployments/docker-compose.yaml similarity index 71% rename from docker-compose.yaml rename to deployments/docker-compose.yaml index f4d64ef..6fbc5e3 100644 --- a/docker-compose.yaml +++ b/deployments/docker-compose.yaml @@ -7,7 +7,7 @@ services: - 2368 volumes: - ./MyPackBot:/go/MyPackBot - - ./i18n/:/go/i18n/ - - ./config.yaml:/go/config.yaml + - ./translations/:/go/translations/ + - ./configs/config.yaml:/go/configs/config.yaml - ./stickers.db:/go/stickers.db entrypoint: ["/go/MyPackBot", "-webhook"] diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d37e686 --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,39 @@ +# Contributor Covenant Code of Conduct +## Our Pledge +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at git@toby3d.ru. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..d60b37a --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Code standarts +Standards help to keep the code readable and understandable, although they may seem strange or uncomfortable. Code style described below is not strict, but I give priority to those contributors who follow it. :heart: + +## Rules +- Format the changes via `go fmt` before committing. +- Indents with tabs (with width 8), no spaces. +- In `import` external packages are separated from native by empty line. +- Maximum line lenght is 120 characters. +- Do not forget to comment what do you do. +- Check what you are writing, tests before commiting. +- Double check what you are writing, remove all [gometalinter](https://github.com/alecthomas/gometalinter) warnings. + +## Guidance +Keep in mind the following before, while and after writing the code: +- **Less is always more.** +Write the least amount of code possible to solve just the problem at hand. +- **Predicting the future is impossible.** +Try to distinguish between anticipating potential future problems and potential future features. The former is usually good, the latter is usually bad. +- **Functional programming is functional.** +Functions should be small and single-purpose. Large variable lists are a sign your function does too much. + +# Git workflow +`master` contains a stable version of the project, when as `develop` it is constantly updated and contains the latest changes. When proposing changes, you must specify `develop` as the target branch of PR. + +## Commits +- First line of commit message should be to 80 chars long as public description of what you have achieved with the commit. +- Leave a blank line after the first line. +- The 3rd line can reference issue with `issue #000` if you just want to mention an issue or `closes #000` if your commit closes an issue. If you don't have an issue to reference or close, think carefully about whether you need to raise one before opening a PR. +- Use bullet points on the following lines to explain what your changes achieve and in particular why you've used your approach. +- Add a contextual emoji before commit title [based on its content](https://gitmoji.carloscuesta.me) (or [use appropriate tool for commiting](https://github.com/carloscuesta/gitmoji-cli)). This **greatly** helps visually to distinguish commits among themselves. + +If you need to update your existing commit message, you can do this by running `git commit --amend` on your branch. + +## Pull requests +The easier it is for me to merge a PR, the faster we'll be able to do it. Please take steps to make merging easy and keep the history clean and useful. + +- **Always work on a branch.** +It will make your life much easier, really. Not touching the `master` branch will also simplify keeping your fork up-to-date. +- **Use issues properly.** +Bugs, changes and features are all different and should be treated differently. Use your commit message to close or reference issues. The more information you provide, the more likely your PR will get merged. + +## Issues +Feel free to pick up any issue which is not assigned. Please leave a comment on the issue to say you wish to pick it up, and it will get assigned to you. \ No newline at end of file diff --git a/docs/ISSUE_TEMPLATE.md b/docs/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..de1e68b --- /dev/null +++ b/docs/ISSUE_TEMPLATE.md @@ -0,0 +1,20 @@ + +### Summary + + +### How-To + \ No newline at end of file diff --git a/LICENSE b/docs/LICENSE similarity index 100% rename from LICENSE rename to docs/LICENSE diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..469fc23 --- /dev/null +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ + +### Summary + \ No newline at end of file diff --git a/README.md b/docs/README.md similarity index 93% rename from README.md rename to docs/README.md index 3da9c03..9218f78 100644 --- a/README.md +++ b/docs/README.md @@ -1,8 +1,9 @@ # [@MyPackBot](https://t.me/MyPackBot) [![discord](https://discordapp.com/api/guilds/208605007744860163/widget.png)](https://discord.gg/KYQB9FR) -[![License](https://img.shields.io/crates/l/rustc-serialize.svg)](LICENSE) +[![License](https://img.shields.io/crates/l/rustc-serialize.svg)](docs/LICENSE) [![Build Status](https://travis-ci.org/toby3d/MyPackBot.svg)](https://travis-ci.org/toby3d/MyPackBot) [![Go Report](https://goreportcard.com/badge/github.com/toby3d/MyPackBot)](https://goreportcard.com/report/github.com/toby3d/MyPackBot) +[![Release](https://img.shields.io/github/release/toby3d/MyPackBot.svg)](https://github.com/toby3d/MyPackBot/releases/latest) [![Patreon](https://img.shields.io/badge/support-patreon-E6461A.svg?maxAge=2592000)](https://www.patreon.com/toby3d) ![bot logo](https://raw.githubusercontent.com/toby3d/MyPackBot/gh-pages/static/social/og-image.jpg) @@ -16,7 +17,7 @@ This is a Telegram-bot that collects all the stickers sent to it in one (almost) - Keeps stickers belonging to their original sets; - Fully support the standard functionality of Telegram stickers (for example "add to favorites"); - Avaliable anywhere in Telegram by typing `@MyPackBot ` in the input field; -- Supports filtering of results by emoji: `@MyPackBot 😀`; +- Supports filtering of results by emoji's: `@MyPackBot 😀👍`; - Fast as f\*\*\*king Sonic; - Worked with uploadable WebP stickers; - Worked with blocked by rightholders sets (but this is not exact); @@ -66,7 +67,7 @@ Bot uses the following dependencies: ## Support ### GitHub -You can [request fix/add some things](https://github.com/toby3d/MyPackBot/issues/new), [make a patch](https://github.com/toby3d/MyPackBot/compare) or help with [translation and localization](https://github.com/toby3d/MyPackBot/tree/develop/i18n) on your language. +You can [request fix/add some things](https://github.com/toby3d/MyPackBot/issues/new), [make a patch](https://github.com/toby3d/MyPackBot/compare) or help with [translation and localization](https://github.com/toby3d/MyPackBot/tree/develop/translations) on your language. Ah, and star this repo, of course. diff --git a/docs/SUPPORT.md b/docs/SUPPORT.md new file mode 100644 index 0000000..c00fc72 --- /dev/null +++ b/docs/SUPPORT.md @@ -0,0 +1,8 @@ +# [Support me on Patreon!](https://www.patreon.com/bePatron?c=243288) +I develop this project in my spare time, and I do it and I will do it free of charge. However, you can make a donation or become a sponsor to make sure that I have enough coffee and pizza for night coding. + +**These people sponsored current version of the project:** +- Yami Odymel +- Daniil Tlenov +- Aurielb +- MoD21k \ No newline at end of file diff --git a/err_check.go b/err_check.go deleted file mode 100644 index 66ded4e..0000000 --- a/err_check.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import "fmt" - -// errCheck helps debug critical errors without warnings from 'gocyclo' linter -func errCheck(err error) { - if err != nil { - fmt.Sprintln(err.Error()) - waitForwards.Wait() // Wait what all users get announcement message - panic(err.Error()) - } -} diff --git a/error.go b/error.go deleted file mode 100644 index 5f2052c..0000000 --- a/error.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import tg "github.com/toby3d/telegram" - -func actionError(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply := tg.NewMessage( - msg.Chat.ID, T("error_unknown", map[string]interface{}{ - "AddStickerCommand": cmdAddSticker, - "AddPackCommand": cmdAddPack, - "DeleteStickerCommand": cmdDeleteSticker, - "DeletePackCommand": cmdDeletePack, - }), - ) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/get_updates_channel.go b/get_updates_channel.go deleted file mode 100644 index 98346b2..0000000 --- a/get_updates_channel.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -// allowedUpdates is a value for parameter of updates configuration -var allowedUpdates = []string{ - tg.UpdateInlineQuery, // For searching and sending stickers - tg.UpdateMessage, // For get commands and messages - tg.UpdateChannelPost, -} - -// getUpdatesChannel return webhook or long polling channel with bot updates -func getUpdatesChannel() tg.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(&tg.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 := tg.NewWebhook(fmt.Sprint(set, listen, bot.AccessToken), nil) - webhook.MaxConnections = 40 - webhook.AllowedUpdates = allowedUpdates - - return bot.NewWebhookChannel( - webhook, // params - "", // certFile - "", // keyFile - set, // set - fmt.Sprint(listen, bot.AccessToken), // listen - serve, // serve - ) -} diff --git a/githooks/prepare-commit-msg b/githooks/prepare-commit-msg new file mode 100644 index 0000000..d7252d0 --- /dev/null +++ b/githooks/prepare-commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +# gitmoji as a commit hook +exec < /dev/tty +gitmoji --hook $1 \ No newline at end of file diff --git a/help.go b/help.go deleted file mode 100755 index d6bf435..0000000 --- a/help.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import tg "github.com/toby3d/telegram" - -func commandHelp(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - reply := tg.NewMessage( - msg.Chat.ID, T("reply_help", map[string]interface{}{ - "AddStickerCommand": cmdAddSticker, - "AddPackCommand": cmdAddPack, - "DeleteStickerCommand": cmdDeleteSticker, - "DeletePackCommand": cmdDeletePack, - "ResetCommand": cmdReset, - "CancelCommand": cmdCancel, - "Username": bot.Self.Username, - }), - ) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 813c0a0..0000000 --- a/helpers.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "github.com/nicksnyder/go-i18n/i18n" - tg "github.com/toby3d/telegram" - "golang.org/x/text/runes" - "golang.org/x/text/transform" -) - -var bannedSkins = []rune{127995, 127996, 127997, 127998, 127999} - -var skinRemover = runes.Remove(runes.Predicate( - func(r rune) bool { - for _, skin := range bannedSkins { - if r == skin { - return true - } - } - return false - }, -)) - -func getMenuKeyboard(T i18n.TranslateFunc) *tg.ReplyKeyboardMarkup { - return tg.NewReplyKeyboardMarkup( - tg.NewReplyKeyboardRow( - tg.NewReplyKeyboardButton(T("button_add_sticker")), - tg.NewReplyKeyboardButton(T("button_add_pack")), - ), - tg.NewReplyKeyboardRow( - tg.NewReplyKeyboardButton(T("button_del_sticker")), - tg.NewReplyKeyboardButton(T("button_del_pack")), - ), - tg.NewReplyKeyboardRow( - tg.NewReplyKeyboardButton(T("button_reset")), - ), - ) -} - -func getCancelButton(T i18n.TranslateFunc) *tg.ReplyKeyboardMarkup { - return tg.NewReplyKeyboardMarkup( - tg.NewReplyKeyboardRow( - tg.NewReplyKeyboardButton(T("button_cancel")), - ), - ) -} - -func getSwitchButton(T i18n.TranslateFunc) *tg.InlineKeyboardMarkup { - return tg.NewInlineKeyboardMarkup( - tg.NewInlineKeyboardRow( - tg.NewInlineKeyboardButtonSwitchSelf(T("button_inline_select"), " "), - ), - ) -} - -func fixEmoji(raw string) (string, error) { - result, _, err := transform.String(skinRemover, raw) - return result, err -} diff --git a/init.go b/init.go deleted file mode 100644 index 181668b..0000000 --- a/init.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "flag" - "os" - "path/filepath" - "strings" - - log "github.com/kirillDanshin/dlog" - "github.com/nicksnyder/go-i18n/i18n" - "github.com/olebedev/config" -) - -var ( - // Variables with types from imports - cfg *config.Config - channelID int64 - - // Setted variables - 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) - - 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) - } - - channelID = int64(cfg.UInt("telegram.channel")) -} diff --git a/init/init.go b/init/init.go new file mode 100644 index 0000000..6e19d24 --- /dev/null +++ b/init/init.go @@ -0,0 +1,18 @@ +package init + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/config" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/i18n" +) + +// init prepare configuration and other things for successful start +func init() { + log.Ln("Initializing...") + i18n.Open("translations/") // Preload localization strings + config.Open("configs/config.yaml") // Preload configuration file + db.Open("stickers.db") // Open database or create new one + bot.New() // Create bot with credentials from config +} diff --git a/internal/actions/action.go b/internal/actions/action.go new file mode 100755 index 0000000..fe75ccd --- /dev/null +++ b/internal/actions/action.go @@ -0,0 +1,35 @@ +package actions + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Action function check Message update on commands, sended stickers or other +// user stuff if user state is not 'none' +func Action(msg *tg.Message) { + state, err := db.UserState(msg.From.ID) + errors.Check(err) + + log.Ln("state:", state) + switch state { + case models.StateAddSticker: + Add(msg, false) + case models.StateAddPack: + Add(msg, true) + case models.StateDeleteSticker: + Delete(msg, false) + case models.StateDeletePack: + Delete(msg, true) + case models.StateReset: + Reset(msg) + default: + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + Error(msg) + } +} diff --git a/internal/actions/add.go b/internal/actions/add.go new file mode 100644 index 0000000..503eaee --- /dev/null +++ b/internal/actions/add.go @@ -0,0 +1,85 @@ +package actions + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Add action add sticker or set to user's pack +func Add(msg *tg.Message, pack bool) { + if !msg.IsSticker() { + return + } + + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("success_add_sticker")) + reply.ParseMode = tg.ModeMarkdown + + if !pack { + var exist bool + sticker := msg.Sticker + exist, err = db.AddSticker( + msg.From.ID, sticker.SetName, sticker.FileID, sticker.Emoji, + ) + errors.Check(err) + + if exist { + reply.Text = T("error_already_add_sticker") + } + + reply.ReplyMarkup = helpers.CancelButton(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) + return + } + + reply.Text = T("error_empty_add_pack", map[string]interface{}{ + "AddStickerCommand": models.CommandAddSticker, + }) + + if msg.Sticker.SetName != "" { + var set *tg.StickerSet + set, err = bot.Bot.GetStickerSet(msg.Sticker.SetName) + errors.Check(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 { + var exist bool + exist, err = db.AddSticker( + msg.From.ID, sticker.SetName, sticker.FileID, sticker.Emoji, + ) + errors.Check(err) + + if !exist { + allExists = false + } + } + + log.Ln("All exists?", allExists) + if allExists { + reply.Text = T("error_already_add_pack", map[string]interface{}{ + "SetTitle": set.Title, + }) + } + } + + reply.ReplyMarkup = helpers.CancelButton(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/actions/delete.go b/internal/actions/delete.go new file mode 100644 index 0000000..75eb33b --- /dev/null +++ b/internal/actions/delete.go @@ -0,0 +1,58 @@ +package actions + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + tg "github.com/toby3d/telegram" +) + +// Delete action remove sticker or set from user's pack +func Delete(msg *tg.Message, pack bool) { + if !msg.IsSticker() { + return + } + + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("success_del_sticker")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.CancelButton(T) + + var notExist bool + if pack { + var set *tg.StickerSet + set, err = bot.Bot.GetStickerSet(msg.Sticker.SetName) + errors.Check(err) + + log.Ln("SetName:", set.Title) + reply.Text = T("success_del_pack", map[string]interface{}{ + "SetTitle": set.Title, + }) + + notExist, err = db.DeletePack(msg.From.ID, msg.Sticker.SetName) + if notExist { + reply.Text = T("error_already_del_pack") + } + } else { + notExist, err = db.DeleteSticker( + msg.From.ID, + msg.Sticker.SetName, + msg.Sticker.FileID, + ) + if notExist { + reply.Text = T("error_already_del_sticker") + } + } + errors.Check(err) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/actions/error.go b/internal/actions/error.go new file mode 100644 index 0000000..0b2ade9 --- /dev/null +++ b/internal/actions/error.go @@ -0,0 +1,33 @@ +package actions + +import ( + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Error action send error reply about invalid user request +func Error(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply := tg.NewMessage( + msg.Chat.ID, T("error_unknown", map[string]interface{}{ + "AddStickerCommand": models.CommandAddSticker, + "AddPackCommand": models.CommandAddPack, + "DeleteStickerCommand": models.CommandDeleteSticker, + "DeletePackCommand": models.CommandDeletePack, + }), + ) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/actions/reset.go b/internal/actions/reset.go new file mode 100644 index 0000000..d73a233 --- /dev/null +++ b/internal/actions/reset.go @@ -0,0 +1,44 @@ +package actions + +import ( + "strings" + + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Reset action checks key phrase and reset user's pack +func Reset(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + if !strings.EqualFold(msg.Text, T("key_phrase")) { + reply := tg.NewMessage(msg.Chat.ID, T("error_reset_phrase")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) + return + } + + err = db.ResetUser(msg.From.ID) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("success_reset")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/bot/new.go b/internal/bot/new.go new file mode 100644 index 0000000..bd09431 --- /dev/null +++ b/internal/bot/new.go @@ -0,0 +1,18 @@ +package bot + +import ( + "github.com/toby3d/MyPackBot/internal/config" + "github.com/toby3d/MyPackBot/internal/errors" + tg "github.com/toby3d/telegram" +) + +var Bot *tg.Bot + +// New just create new bot by configuration credentials +func New() { + accessToken, err := config.Config.String("telegram.token") + errors.Check(err) + + Bot, err = tg.NewBot(accessToken) + errors.Check(err) +} diff --git a/internal/commands/add.go b/internal/commands/add.go new file mode 100644 index 0000000..ed937b8 --- /dev/null +++ b/internal/commands/add.go @@ -0,0 +1,39 @@ +package commands + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Add command prepare user for adding some stickers or sets to his pack +func Add(msg *tg.Message, pack bool) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("reply_add_sticker")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.CancelButton(T) + + err = db.ChangeUserState(msg.From.ID, models.StateAddSticker) + errors.Check(err) + + if pack { + reply.Text = T("reply_add_pack") + + err = db.ChangeUserState(msg.From.ID, models.StateAddPack) + errors.Check(err) + } + + log.Ln("Sending add reply...") + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/commands/cancel.go b/internal/commands/cancel.go new file mode 100755 index 0000000..18068ca --- /dev/null +++ b/internal/commands/cancel.go @@ -0,0 +1,48 @@ +package commands + +import ( + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Cancel just cancel current user operation +func Cancel(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + state, err := db.UserState(msg.From.ID) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + var text string + switch state { + case models.StateAddSticker: + text = T("cancel_add_sticker") + case models.StateAddPack: + text = T("cancel_add_pack") + case models.StateDeleteSticker: + text = T("cancel_del_sticker") + case models.StateDeletePack: + text = T("cancel_del_pack") + case models.StateReset: + text = T("cancel_reset") + default: + text = T("cancel_error") + } + + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, text) + reply.ReplyMarkup = helpers.MenuKeyboard(T) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/commands/command.go b/internal/commands/command.go new file mode 100755 index 0000000..0e4e455 --- /dev/null +++ b/internal/commands/command.go @@ -0,0 +1,30 @@ +package commands + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Command check's got user command +func Command(msg *tg.Message) { + log.Ln("command:", msg.Command()) + switch { + case msg.IsCommand(models.CommandStart): + Start(msg) + case msg.IsCommand(models.CommandHelp): + Help(msg) + case msg.IsCommand(models.CommandAddSticker): + Add(msg, false) + case msg.IsCommand(models.CommandAddPack): + Add(msg, true) + case msg.IsCommand(models.CommandDeleteSticker): + Delete(msg, false) + case msg.IsCommand(models.CommandDeletePack): + Delete(msg, true) + case msg.IsCommand(models.CommandReset): + Reset(msg) + case msg.IsCommand(models.CommandCancel): + Cancel(msg) + } +} diff --git a/internal/commands/delete.go b/internal/commands/delete.go new file mode 100755 index 0000000..86f4c76 --- /dev/null +++ b/internal/commands/delete.go @@ -0,0 +1,59 @@ +package commands + +import ( + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Delete prepare user to remove some stickers or sets from his pack +func Delete(msg *tg.Message, pack bool) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, total, err := db.UserStickers(msg.From.ID, 0, "") + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + if total <= 0 { + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("error_empty_del")) + reply.ReplyMarkup = helpers.MenuKeyboard(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) + return + } + + reply := tg.NewMessage(msg.Chat.ID, T("reply_del_sticker")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.CancelButton(T) + + err = db.ChangeUserState(msg.From.ID, models.StateDeleteSticker) + errors.Check(err) + + if pack { + err = db.ChangeUserState(msg.From.ID, models.StateDeletePack) + errors.Check(err) + + reply.Text = T("reply_del_pack") + } + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply = tg.NewMessage(msg.Chat.ID, T("reply_switch_button")) + reply.ReplyMarkup = helpers.SwitchButton(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/commands/help.go b/internal/commands/help.go new file mode 100755 index 0000000..e3e3936 --- /dev/null +++ b/internal/commands/help.go @@ -0,0 +1,40 @@ +package commands + +import ( + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Help just send instructions about bot usage +func Help(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + reply := tg.NewMessage( + msg.Chat.ID, T("reply_help", map[string]interface{}{ + "AddStickerCommand": models.CommandAddSticker, + "AddPackCommand": models.CommandAddPack, + "DeleteStickerCommand": models.CommandDeleteSticker, + "DeletePackCommand": models.CommandDeletePack, + "ResetCommand": models.CommandReset, + "CancelCommand": models.CommandCancel, + "Username": bot.Bot.Self.Username, + }), + ) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/commands/reset.go b/internal/commands/reset.go new file mode 100644 index 0000000..6ddccee --- /dev/null +++ b/internal/commands/reset.go @@ -0,0 +1,47 @@ +package commands + +import ( + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Reset prepare user to reset his pack +func Reset(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + _, total, err := db.UserStickers(msg.From.ID, 0, "") + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + if total <= 0 { + err = db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("error_already_reset")) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) + return + } + + err = db.ChangeUserState(msg.From.ID, models.StateReset) + errors.Check(err) + + reply := tg.NewMessage(msg.Chat.ID, T("reply_reset", map[string]interface{}{ + "KeyPhrase": T("key_phrase"), + "CancelCommand": models.CommandCancel, + })) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.CancelButton(T) + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/commands/start.go b/internal/commands/start.go new file mode 100644 index 0000000..cccbfdb --- /dev/null +++ b/internal/commands/start.go @@ -0,0 +1,47 @@ +package commands + +import ( + "strings" + + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Start just send introduction about bot to user +func Start(msg *tg.Message) { + err := db.ChangeUserState(msg.From.ID, models.StateNone) + errors.Check(err) + + _, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) + errors.Check(err) + + if msg.HasCommandArgument() { + log.Ln("Received a", msg.Command(), "command with", msg.CommandArgument(), "argument") + if strings.EqualFold(msg.CommandArgument(), models.CommandHelp) { + Help(msg) + return + } + } + + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + reply := tg.NewMessage( + msg.Chat.ID, + T("reply_start", map[string]interface{}{ + "Username": bot.Bot.Self.Username, + "ID": bot.Bot.Self.ID, + }), + ) + reply.ParseMode = tg.ModeMarkdown + reply.ReplyMarkup = helpers.MenuKeyboard(T) + + _, err = bot.Bot.SendMessage(reply) + errors.Check(err) +} diff --git a/internal/config/open.go b/internal/config/open.go new file mode 100644 index 0000000..614025f --- /dev/null +++ b/internal/config/open.go @@ -0,0 +1,20 @@ +package config + +import ( + "github.com/olebedev/config" + "github.com/toby3d/MyPackBot/internal/errors" +) + +var ( + Config *config.Config + ChannelID int64 +) + +// Open just open configuration file for parsing some data in other functions +func Open(path string) { + var err error + Config, err = config.ParseYamlFile(path) + errors.Check(err) + + ChannelID = int64(Config.UInt("telegram.channel")) +} diff --git a/internal/db/add_sticker.go b/internal/db/add_sticker.go new file mode 100644 index 0000000..7680aee --- /dev/null +++ b/internal/db/add_sticker.go @@ -0,0 +1,35 @@ +package db + +import ( + "fmt" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" + "github.com/toby3d/MyPackBot/internal/models" +) + +// AddSticker add sticker FileID, Emoji and SetName meta for UserID +func AddSticker(userID int, setName, fileID, emoji string) (bool, error) { + log.Ln("Trying to add", fileID, "sticker from", userID, "user") + if setName == "" { + setName = models.SetUploaded + } + + var exists bool + err := DB.Update(func(tx *buntdb.Tx) error { + var err error + _, exists, err = tx.Set( + fmt.Sprint("user:", userID, ":set:", setName, ":sticker:", fileID), // key + emoji, // value + nil, // options + ) + if err == buntdb.ErrIndexExists { + exists = true + return nil + } + + return err + }) + + return exists, err +} diff --git a/internal/db/change_user_state.go b/internal/db/change_user_state.go new file mode 100644 index 0000000..3ab9442 --- /dev/null +++ b/internal/db/change_user_state.go @@ -0,0 +1,17 @@ +package db + +import ( + "fmt" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" +) + +// ChangeUserState change current user state on input state. +func ChangeUserState(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"), state, nil) + return err + }) +} diff --git a/internal/db/delete_pack.go b/internal/db/delete_pack.go new file mode 100644 index 0000000..310025d --- /dev/null +++ b/internal/db/delete_pack.go @@ -0,0 +1,50 @@ +package db + +import ( + "fmt" + "strings" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" + "github.com/toby3d/MyPackBot/internal/models" +) + +// DeletePack remove all keys for UserID which contains input SetName +func DeletePack(userID int, setName string) (bool, error) { + log.Ln("Trying to remove all", setName, "sticker from", userID, "user") + if setName == "" { + setName = models.SetUploaded + } + + var fileIDs []string + err := DB.View(func(tx *buntdb.Tx) error { + return tx.AscendKeys( + fmt.Sprint("user:", userID, ":set:", setName, ":*"), + func(key, val string) bool { + keys := strings.Split(key, ":") + fileIDs = append(fileIDs, keys[5]) + return true + }, + ) + }) + + if len(fileIDs) == 0 { + return true, nil + } + + for _, fileID := range fileIDs { + var notExist bool + notExist, err = DeleteSticker(userID, setName, fileID) + if err != nil { + return notExist, err + } + } + + switch err { + case buntdb.ErrNotFound: + log.Ln(userID, "not found") + return true, nil + } + + return false, err +} diff --git a/internal/db/delete_sticker.go b/internal/db/delete_sticker.go new file mode 100644 index 0000000..4b982be --- /dev/null +++ b/internal/db/delete_sticker.go @@ -0,0 +1,31 @@ +package db + +import ( + "fmt" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" + "github.com/toby3d/MyPackBot/internal/models" +) + +// DeleteSticker just remove specified sticker key from database. +func DeleteSticker(userID int, setName, fileID string) (bool, error) { + log.Ln("Trying to remove", fileID, "sticker from", userID, "user") + if setName == "" { + setName = models.SetUploaded + } + + err := DB.Update(func(tx *buntdb.Tx) error { + _, err := tx.Delete( + fmt.Sprint("user:", userID, ":set:", setName, ":sticker:", fileID), + ) + return err + }) + + if err == buntdb.ErrNotFound { + log.Ln(userID, "not found, create new one") + return true, nil + } + + return false, err +} diff --git a/internal/db/open.go b/internal/db/open.go new file mode 100644 index 0000000..51d8103 --- /dev/null +++ b/internal/db/open.go @@ -0,0 +1,19 @@ +package db + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" + "github.com/toby3d/MyPackBot/internal/errors" +) + +var DB *buntdb.DB + +// Open just open connection to database for work +func Open(path string) { + log.Ln("Open database file...") + go func() { + var err error + DB, err = buntdb.Open(path) + errors.Check(err) + }() +} diff --git a/internal/db/reset.go b/internal/db/reset.go new file mode 100644 index 0000000..6789810 --- /dev/null +++ b/internal/db/reset.go @@ -0,0 +1,39 @@ +package db + +import ( + "fmt" + "strconv" + "strings" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" +) + +// ResetUser just drop out all stickers keys for input UserID +func ResetUser(userID int) error { + log.Ln("Trying reset all stickers of", userID, "user") + return DB.Update(func(tx *buntdb.Tx) error { + var keys []string + if err := tx.AscendKeys( + fmt.Sprint("user:", userID, ":set:*"), // index + func(key, val string) bool { // iterator + subKeys := strings.Split(key, ":") + if subKeys[1] == strconv.Itoa(userID) { + keys = append(keys, key) + } + return true + }, + ); err != nil { + return err + } + + for i := range keys { + _, err := tx.Delete(keys[i]) + if err != nil { + break + } + } + + return nil + }) +} diff --git a/internal/db/user_state.go b/internal/db/user_state.go new file mode 100644 index 0000000..6eec82b --- /dev/null +++ b/internal/db/user_state.go @@ -0,0 +1,30 @@ +package db + +import ( + "fmt" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" + "github.com/toby3d/MyPackBot/internal/models" +) + +// UserState return current state for UserID +func UserState(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 = ChangeUserState(userID, models.StateNone); err != nil { + return state, err + } + } + + return state, err +} diff --git a/internal/db/user_stickers.go b/internal/db/user_stickers.go new file mode 100644 index 0000000..83ba13f --- /dev/null +++ b/internal/db/user_stickers.go @@ -0,0 +1,54 @@ +package db + +import ( + "fmt" + "strconv" + "strings" + + log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" +) + +// UserStickers return array of saved stickers for input UserID and his total count +func UserStickers(userID, offset int, query string) ([]string, int, error) { + log.Ln("Trying to get", userID, "stickers") + var total, count int + var stickers []string + offset = offset * 50 + + err := DB.View(func(tx *buntdb.Tx) error { + return tx.AscendKeys( + fmt.Sprint("user:", userID, ":set:*"), // index + func(key, val string) bool { // iterator + subKeys := strings.Split(key, ":") + if subKeys[1] != strconv.Itoa(userID) { + return true + } + + total++ + if count >= 51 { + return true + } + + if total < offset { + return true + } + + if query != "" && !strings.Contains(query, val) { + return true + } + + count++ + stickers = append(stickers, subKeys[5]) + return true + }, + ) + }) + + if err == buntdb.ErrNotFound { + log.Ln("Not found stickers") + return nil, total, nil + } + + return stickers, total, err +} diff --git a/internal/db/users.go b/internal/db/users.go new file mode 100644 index 0000000..4790e5e --- /dev/null +++ b/internal/db/users.go @@ -0,0 +1,33 @@ +package db + +import ( + "strconv" + "strings" + + // log "github.com/kirillDanshin/dlog" + "github.com/tidwall/buntdb" +) + +// Users return array of all avaliable UserID in database +func Users() ([]int, error) { + var users []int + err := DB.View(func(tx *buntdb.Tx) error { + return tx.AscendKeys( + "user:*:state", + func(key, val string) bool { + subKeys := strings.Split(key, ":") + id, err := strconv.Atoi(subKeys[1]) + if err == nil { + users = append(users, id) + } + + return true + }, + ) + }) + if err == buntdb.ErrNotFound { + return nil, nil + } + + return users, err +} diff --git a/internal/errors/check.go b/internal/errors/check.go new file mode 100644 index 0000000..bbb1d25 --- /dev/null +++ b/internal/errors/check.go @@ -0,0 +1,26 @@ +package errors + +import ( + "log" + "log/syslog" + "os" + "sync" +) + +var ( + WaitForwards = new(sync.WaitGroup) + sysLogger *syslog.Writer +) + +// Check helps debug critical errors without warnings from 'gocyclo' linter +func Check(err error) { + if err != nil { + log.Println(err.Error()) + + // Wait what all users get announcement message first + WaitForwards.Wait() + + sysLogger.Crit(err.Error()) + os.Exit(1) + } +} diff --git a/internal/helpers/cancel_button.go b/internal/helpers/cancel_button.go new file mode 100644 index 0000000..a621e4a --- /dev/null +++ b/internal/helpers/cancel_button.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "github.com/nicksnyder/go-i18n/i18n" + tg "github.com/toby3d/telegram" +) + +// CancelButton helper just generate ReplyMarkup with cancel button +func CancelButton(T i18n.TranslateFunc) *tg.ReplyKeyboardMarkup { + return tg.NewReplyKeyboardMarkup( + tg.NewReplyKeyboardRow( + tg.NewReplyKeyboardButton(T("button_cancel")), + ), + ) +} diff --git a/internal/helpers/fix_emoji.go b/internal/helpers/fix_emoji.go new file mode 100644 index 0000000..69bcd7c --- /dev/null +++ b/internal/helpers/fix_emoji.go @@ -0,0 +1,27 @@ +package helpers + +import ( + "golang.org/x/text/runes" + "golang.org/x/text/transform" +) + +// Skin colors for remove +var bannedSkins = []rune{127995, 127996, 127997, 127998, 127999} + +// Transformer for remove skin colors +var skinRemover = runes.Remove(runes.Predicate( + func(r rune) bool { + for _, skin := range bannedSkins { + if r == skin { + return true + } + } + return false + }, +)) + +// FixEmoji fixes user input by remove all potential skin colors +func FixEmoji(raw string) (string, error) { + result, _, err := transform.String(skinRemover, raw) + return result, err +} diff --git a/internal/helpers/menu_keyboard.go b/internal/helpers/menu_keyboard.go new file mode 100644 index 0000000..070f8b6 --- /dev/null +++ b/internal/helpers/menu_keyboard.go @@ -0,0 +1,23 @@ +package helpers + +import ( + "github.com/nicksnyder/go-i18n/i18n" + tg "github.com/toby3d/telegram" +) + +// MenuKeyboard helper just generate ReplyMarkup with menu buttons +func MenuKeyboard(T i18n.TranslateFunc) *tg.ReplyKeyboardMarkup { + return tg.NewReplyKeyboardMarkup( + tg.NewReplyKeyboardRow( + tg.NewReplyKeyboardButton(T("button_add_sticker")), + tg.NewReplyKeyboardButton(T("button_add_pack")), + ), + tg.NewReplyKeyboardRow( + tg.NewReplyKeyboardButton(T("button_del_sticker")), + tg.NewReplyKeyboardButton(T("button_del_pack")), + ), + tg.NewReplyKeyboardRow( + tg.NewReplyKeyboardButton(T("button_reset")), + ), + ) +} diff --git a/internal/helpers/switch_button.go b/internal/helpers/switch_button.go new file mode 100644 index 0000000..4631f8d --- /dev/null +++ b/internal/helpers/switch_button.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "github.com/nicksnyder/go-i18n/i18n" + tg "github.com/toby3d/telegram" +) + +// SwitchButton helper just generate ReplyMarkup with SelfSwitch button +func SwitchButton(T i18n.TranslateFunc) *tg.InlineKeyboardMarkup { + return tg.NewInlineKeyboardMarkup( + tg.NewInlineKeyboardRow( + tg.NewInlineKeyboardButtonSwitchSelf(T("button_inline_select"), " "), + ), + ) +} diff --git a/internal/i18n/open.go b/internal/i18n/open.go new file mode 100644 index 0000000..7bd0d8c --- /dev/null +++ b/internal/i18n/open.go @@ -0,0 +1,20 @@ +package i18n + +import ( + "os" + "path/filepath" + "strings" + + "github.com/nicksnyder/go-i18n/i18n" +) + +// Open just walk in input path for preloading localization files +func Open(path string) error { + return filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if !strings.HasSuffix(path, ".all.yaml") { + return err + } + + return i18n.LoadTranslationFile(path) + }) +} diff --git a/internal/i18n/switch_to.go b/internal/i18n/switch_to.go new file mode 100644 index 0000000..1a3023c --- /dev/null +++ b/internal/i18n/switch_to.go @@ -0,0 +1,13 @@ +package i18n + +import ( + "github.com/nicksnyder/go-i18n/i18n" + "github.com/toby3d/MyPackBot/internal/models" +) + +// SwitchTo try load locales by input language codes and return TranslateFunc +func SwitchTo(codes ...string) (T i18n.TranslateFunc, err error) { + codes = append(codes, models.LanguageFallback) + T, err = i18n.Tfunc(codes[0], codes[1:]...) + return +} diff --git a/internal/messages/message.go b/internal/messages/message.go new file mode 100644 index 0000000..ba2ee12 --- /dev/null +++ b/internal/messages/message.go @@ -0,0 +1,35 @@ +package messages + +import ( + "strings" + + "github.com/toby3d/MyPackBot/internal/actions" + "github.com/toby3d/MyPackBot/internal/commands" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/i18n" + tg "github.com/toby3d/telegram" +) + +// Message checks user message on response, stickers, reset key phrase, else do +// Actions +func Message(msg *tg.Message) { + T, err := i18n.SwitchTo(msg.From.LanguageCode) + errors.Check(err) + + switch { + case strings.EqualFold(msg.Text, T("button_add_sticker")): + commands.Add(msg, false) + case strings.EqualFold(msg.Text, T("button_add_pack")): + commands.Add(msg, true) + case strings.EqualFold(msg.Text, T("button_del_sticker")): + commands.Delete(msg, false) + case strings.EqualFold(msg.Text, T("button_del_pack")): + commands.Delete(msg, true) + case strings.EqualFold(msg.Text, T("button_reset")): + commands.Reset(msg) + case strings.EqualFold(msg.Text, T("button_cancel")): + commands.Cancel(msg) + default: + actions.Action(msg) + } +} diff --git a/internal/models/models.go b/internal/models/models.go new file mode 100644 index 0000000..ae70bd0 --- /dev/null +++ b/internal/models/models.go @@ -0,0 +1,31 @@ +package models + +import tg "github.com/toby3d/telegram" + +const ( + CommandAddPack = "addPack" + CommandAddSticker = "addSticker" + CommandCancel = "cancel" + CommandHelp = "help" + CommandDeleteSticker = "delSticker" + CommandDeletePack = "delPack" + CommandReset = "reset" + CommandStart = "start" + + StateNone = "none" + StateAddSticker = CommandAddSticker + StateAddPack = CommandAddPack + StateDeleteSticker = CommandDeleteSticker + StateDeletePack = CommandDeletePack + StateReset = CommandReset + + SetUploaded = "?" + + LanguageFallback = "en" +) + +var AllowedUpdates = []string{ + tg.UpdateInlineQuery, // For searching and sending stickers + tg.UpdateMessage, // For get commands and messages + tg.UpdateChannelPost, // For forwarding announcements +} diff --git a/internal/updates/channel.go b/internal/updates/channel.go new file mode 100644 index 0000000..c7e9916 --- /dev/null +++ b/internal/updates/channel.go @@ -0,0 +1,64 @@ +package updates + +import ( + "fmt" + + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/config" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/models" + tg "github.com/toby3d/telegram" +) + +// Channel return webhook or long polling channel with bot updates +func Channel(webhookMode bool) tg.UpdatesChannel { + log.Ln("Preparing channel for updates...") + if !webhookMode { + log.Ln("Use LongPolling updates") + + info, err := bot.Bot.GetWebhookInfo() + errors.Check(err) + + if info.URL != "" { + log.Ln("Deleting webhook...") + _, err := bot.Bot.DeleteWebhook() + errors.Check(err) + } + + return bot.Bot.NewLongPollingChannel(&tg.GetUpdatesParameters{ + Offset: 0, + Limit: 100, + Timeout: 60, + AllowedUpdates: models.AllowedUpdates, + }) + } + + var err error + var set, listen, serve string + set, err = config.Config.String("telegram.webhook.set") + errors.Check(err) + listen, err = config.Config.String("telegram.webhook.listen") + errors.Check(err) + serve, err = config.Config.String("telegram.webhook.serve") + errors.Check(err) + + log.Ln( + "Trying set webhook on address:", + fmt.Sprint(set, listen, bot.Bot.AccessToken), + ) + + log.Ln("Creating new webhook...") + webhook := tg.NewWebhook(fmt.Sprint(set, listen, bot.Bot.AccessToken), nil) + webhook.MaxConnections = 40 + webhook.AllowedUpdates = models.AllowedUpdates + + return bot.Bot.NewWebhookChannel( + webhook, // params + "", // certFile + "", // keyFile + set, // set + fmt.Sprint(listen, bot.Bot.AccessToken), // listen + serve, // serve + ) +} diff --git a/internal/updates/channel_post.go b/internal/updates/channel_post.go new file mode 100644 index 0000000..8ce302c --- /dev/null +++ b/internal/updates/channel_post.go @@ -0,0 +1,35 @@ +package updates + +import ( + "time" + + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/config" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + tg "github.com/toby3d/telegram" +) + +// ChannelPost checks ChannelPost update for forwarding content to bot users +func ChannelPost(post *tg.Message) { + if post.Chat.ID != config.ChannelID { + log.Ln(post.Chat.ID, "!=", config.ChannelID) + return + } + + users, err := db.Users() + errors.Check(err) + + for i := range users { + errors.WaitForwards.Add(1) + if _, err = bot.Bot.ForwardMessage( + tg.NewForwardMessage(post.Chat.ID, int64(users[i]), post.ID), + ); err != nil { + log.Ln(err.Error()) + } + errors.WaitForwards.Done() + + time.Sleep(time.Second / 10) // For avoid spamming + } +} diff --git a/update_inline_query.go b/internal/updates/inline_query.go similarity index 64% rename from update_inline_query.go rename to internal/updates/inline_query.go index 63d733d..c52abcb 100755 --- a/update_inline_query.go +++ b/internal/updates/inline_query.go @@ -1,14 +1,21 @@ -package main +package updates import ( "strconv" log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/db" + "github.com/toby3d/MyPackBot/internal/errors" + "github.com/toby3d/MyPackBot/internal/helpers" + "github.com/toby3d/MyPackBot/internal/i18n" + "github.com/toby3d/MyPackBot/internal/models" tg "github.com/toby3d/telegram" ) -func updateInlineQuery(inlineQuery *tg.InlineQuery) { - fixedQuery, err := fixEmoji(inlineQuery.Query) +// InlineQuery checks InlineQuery updates for answer with personal results +func InlineQuery(inlineQuery *tg.InlineQuery) { + fixedQuery, err := helpers.FixEmoji(inlineQuery.Query) if err == nil { inlineQuery.Query = fixedQuery } @@ -19,27 +26,27 @@ func updateInlineQuery(inlineQuery *tg.InlineQuery) { answer.IsPersonal = true if len([]rune(inlineQuery.Query)) >= 256 { - _, err = bot.AnswerInlineQuery(answer) - errCheck(err) + _, err = bot.Bot.AnswerInlineQuery(answer) + errors.Check(err) return } log.Ln("Let's preparing answer...") - T, err := switchLocale(inlineQuery.From.LanguageCode) - errCheck(err) + T, err := i18n.SwitchTo(inlineQuery.From.LanguageCode) + errors.Check(err) log.Ln("INLINE OFFSET:", inlineQuery.Offset) if inlineQuery.Offset == "" { inlineQuery.Offset = "-1" } offset, err := strconv.Atoi(inlineQuery.Offset) - errCheck(err) + errors.Check(err) offset++ - stickers, packSize, err := dbGetUserStickers( + stickers, packSize, err := db.UserStickers( inlineQuery.From.ID, offset, inlineQuery.Query, ) - errCheck(err) + errors.Check(err) totalStickers := len(stickers) if totalStickers == 0 { @@ -51,11 +58,11 @@ func updateInlineQuery(inlineQuery *tg.InlineQuery) { "Query": inlineQuery.Query, }, ) - answer.SwitchPrivateMessageParameter = cmdAddSticker + answer.SwitchPrivateMessageParameter = models.CommandAddSticker } else { // If query is empty and get 0 stickers answer.SwitchPrivateMessageText = T("button_inline_empty") - answer.SwitchPrivateMessageParameter = cmdAddSticker + answer.SwitchPrivateMessageParameter = models.CommandAddSticker } answer.Results = nil } @@ -80,12 +87,12 @@ func updateInlineQuery(inlineQuery *tg.InlineQuery) { "Count": packSize, }, ) - answer.SwitchPrivateMessageParameter = cmdHelp + answer.SwitchPrivateMessageParameter = models.CommandHelp answer.Results = results } log.Ln("CacheTime:", answer.CacheTime) - _, err = bot.AnswerInlineQuery(answer) - errCheck(err) + _, err = bot.Bot.AnswerInlineQuery(answer) + errors.Check(err) } diff --git a/internal/updates/message.go b/internal/updates/message.go new file mode 100644 index 0000000..d15f9aa --- /dev/null +++ b/internal/updates/message.go @@ -0,0 +1,29 @@ +package updates + +import ( + log "github.com/kirillDanshin/dlog" + "github.com/toby3d/MyPackBot/internal/actions" + "github.com/toby3d/MyPackBot/internal/bot" + "github.com/toby3d/MyPackBot/internal/commands" + "github.com/toby3d/MyPackBot/internal/messages" + tg "github.com/toby3d/telegram" +) + +// Message checks Message updates for answer to user commands, replies or sended +// stickers +func Message(msg *tg.Message) { + if bot.Bot.IsMessageFromMe(msg) || + bot.Bot.IsForwardFromMe(msg) { + log.Ln("Ignore message update") + return + } + + switch { + case bot.Bot.IsCommandToMe(msg): + commands.Command(msg) + case msg.IsText(): + messages.Message(msg) + default: + actions.Action(msg) + } +} diff --git a/main.go b/main.go index 55bb60a..8d5340b 100644 --- a/main.go +++ b/main.go @@ -1,42 +1,31 @@ package main import ( + "flag" + log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" + _ "github.com/toby3d/MyPackBot/init" + "github.com/toby3d/MyPackBot/internal/updates" ) -// bot is general structure of the bot -var bot *tg.Bot +var flagWebhook = flag.Bool( + "webhook", false, + "enable work via webhooks (required valid certificates)", +) // main function is a general function for work of this bot func main() { - log.Ln("Let'g Get It Started...") - var err error + flag.Parse() // Parse flagWebhook - go dbInit() - defer func() { - err = db.Close() - errCheck(err) - }() - - log.Ln("Initializing new bot via checking access_token...") - bot, err = tg.NewBot(cfg.UString("telegram.token")) - errCheck(err) - - log.Ln("Let's check updates channel!") - for update := range getUpdatesChannel() { + for update := range updates.Channel(*flagWebhook) { + log.D(update) switch { case update.IsInlineQuery(): - log.D(update.InlineQuery) - updateInlineQuery(update.InlineQuery) + updates.InlineQuery(update.InlineQuery) case update.IsMessage(): - log.D(update.Message) - updateMessage(update.Message) + updates.Message(update.Message) case update.IsChannelPost(): - log.D(update.ChannelPost) - updateChannelPost(update.ChannelPost) - default: - log.D(update) + updates.ChannelPost(update.ChannelPost) } } } diff --git a/messages.go b/messages.go deleted file mode 100644 index 74e11f3..0000000 --- a/messages.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "strings" - - tg "github.com/toby3d/telegram" -) - -func messages(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - switch { - case strings.EqualFold(msg.Text, T("button_add_sticker")): - commandAdd(msg, false) - case strings.EqualFold(msg.Text, T("button_add_pack")): - commandAdd(msg, true) - case strings.EqualFold(msg.Text, T("button_del_sticker")): - commandDelete(msg, false) - case strings.EqualFold(msg.Text, T("button_del_pack")): - commandDelete(msg, true) - case strings.EqualFold(msg.Text, T("button_reset")): - commandReset(msg) - case strings.EqualFold(msg.Text, T("button_cancel")): - commandCancel(msg) - case strings.EqualFold(msg.Text, T("meta_key_phrase")): - actions(msg) - } -} diff --git a/reset.go b/reset.go deleted file mode 100644 index a63a6fc..0000000 --- a/reset.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "strings" - - tg "github.com/toby3d/telegram" -) - -func commandReset(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - _, total, err := dbGetUserStickers(msg.From.ID, 0, "") - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - if total <= 0 { - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("error_already_reset")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - _, err = bot.SendMessage(reply) - errCheck(err) - return - } - - err = dbChangeUserState(msg.From.ID, stateReset) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("reply_reset", map[string]interface{}{ - "KeyPhrase": T("key_phrase"), - "CancelCommand": cmdCancel, - })) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getCancelButton(T) - _, err = bot.SendMessage(reply) - errCheck(err) -} - -func actionReset(msg *tg.Message) { - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - err = dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - if !strings.EqualFold(msg.Text, T("key_phrase")) { - reply := tg.NewMessage(msg.Chat.ID, T("error_reset_phrase")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - _, err = bot.SendMessage(reply) - errCheck(err) - return - } - - err = dbResetUserStickers(msg.From.ID) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("success_reset")) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/start.go b/start.go deleted file mode 100644 index d361b0a..0000000 --- a/start.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "strings" - - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -func commandStart(msg *tg.Message) { - err := dbChangeUserState(msg.From.ID, stateNone) - errCheck(err) - - _, err = bot.SendChatAction(msg.Chat.ID, tg.ActionTyping) - errCheck(err) - - if msg.HasCommandArgument() { - log.Ln("Received a", msg.Command(), "command with", msg.CommandArgument(), "argument") - if strings.ToLower(msg.CommandArgument()) == strings.ToLower(cmdHelp) { - commandHelp(msg) - return - } - } - - T, err := switchLocale(msg.From.LanguageCode) - errCheck(err) - - reply := tg.NewMessage(msg.Chat.ID, T("reply_start", map[string]interface{}{ - "Username": bot.Self.Username, - "ID": bot.Self.ID, - })) - reply.ParseMode = tg.ModeMarkdown - reply.ReplyMarkup = getMenuKeyboard(T) - - _, err = bot.SendMessage(reply) - errCheck(err) -} diff --git a/switch_locale.go b/switch_locale.go deleted file mode 100644 index 6655ab5..0000000 --- a/switch_locale.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - "github.com/nicksnyder/go-i18n/i18n" -) - -const langFallback = "en" - -func switchLocale(langCode string) (i18n.TranslateFunc, error) { - log.Ln("Check", langCode, "localization") - return i18n.Tfunc(langCode, langFallback) -} diff --git a/i18n/en.all.yaml b/translations/en.all.yaml old mode 100755 new mode 100644 similarity index 100% rename from i18n/en.all.yaml rename to translations/en.all.yaml diff --git a/i18n/en.untranslated.yaml b/translations/en.untranslated.yaml similarity index 100% rename from i18n/en.untranslated.yaml rename to translations/en.untranslated.yaml diff --git a/i18n/ru.all.yaml b/translations/ru.all.yaml old mode 100755 new mode 100644 similarity index 100% rename from i18n/ru.all.yaml rename to translations/ru.all.yaml diff --git a/i18n/ru.untranslated.yaml b/translations/ru.untranslated.yaml similarity index 100% rename from i18n/ru.untranslated.yaml rename to translations/ru.untranslated.yaml diff --git a/i18n/en/en.buttons.yaml b/translations/src/en/en.buttons.yaml similarity index 100% rename from i18n/en/en.buttons.yaml rename to translations/src/en/en.buttons.yaml diff --git a/i18n/en/en.cancel.yaml b/translations/src/en/en.cancel.yaml similarity index 100% rename from i18n/en/en.cancel.yaml rename to translations/src/en/en.cancel.yaml diff --git a/i18n/en/en.errors.yaml b/translations/src/en/en.errors.yaml similarity index 100% rename from i18n/en/en.errors.yaml rename to translations/src/en/en.errors.yaml diff --git a/i18n/en/en.replies.yaml b/translations/src/en/en.replies.yaml similarity index 100% rename from i18n/en/en.replies.yaml rename to translations/src/en/en.replies.yaml diff --git a/i18n/en/en.success.yaml b/translations/src/en/en.success.yaml similarity index 86% rename from i18n/en/en.success.yaml rename to translations/src/en/en.success.yaml index 80a15fe..c6e5218 100755 --- a/i18n/en/en.success.yaml +++ b/translations/src/en/en.success.yaml @@ -7,4 +7,4 @@ success_del_sticker: success_del_pack: other: The sticker pack *{{.SetTitle}}* was successfully removed from yours! success_reset: - other: The contents of your pack are completely reset!.. \ No newline at end of file + other: The contents of your pack are completely reset! \ No newline at end of file diff --git a/i18n/en/en.yaml b/translations/src/en/en.yaml similarity index 100% rename from i18n/en/en.yaml rename to translations/src/en/en.yaml diff --git a/i18n/ru/ru.buttons.yaml b/translations/src/ru/ru.buttons.yaml similarity index 100% rename from i18n/ru/ru.buttons.yaml rename to translations/src/ru/ru.buttons.yaml diff --git a/i18n/ru/ru.cancel.yaml b/translations/src/ru/ru.cancel.yaml similarity index 100% rename from i18n/ru/ru.cancel.yaml rename to translations/src/ru/ru.cancel.yaml diff --git a/i18n/ru/ru.errors.yaml b/translations/src/ru/ru.errors.yaml similarity index 100% rename from i18n/ru/ru.errors.yaml rename to translations/src/ru/ru.errors.yaml diff --git a/i18n/ru/ru.replies.yaml b/translations/src/ru/ru.replies.yaml similarity index 100% rename from i18n/ru/ru.replies.yaml rename to translations/src/ru/ru.replies.yaml diff --git a/i18n/ru/ru.success.yaml b/translations/src/ru/ru.success.yaml similarity index 96% rename from i18n/ru/ru.success.yaml rename to translations/src/ru/ru.success.yaml index eac6284..c8142bf 100755 --- a/i18n/ru/ru.success.yaml +++ b/translations/src/ru/ru.success.yaml @@ -7,4 +7,4 @@ success_del_sticker: success_del_pack: other: Набор *{{.SetTitle}}* успешно удалён из твоего набора! success_reset: - other: Сброс твоего набора успешно произведён!.. \ No newline at end of file + other: Сброс твоего набора успешно произведён! \ No newline at end of file diff --git a/i18n/ru/ru.yaml b/translations/src/ru/ru.yaml similarity index 100% rename from i18n/ru/ru.yaml rename to translations/src/ru/ru.yaml diff --git a/update_channel_post.go b/update_channel_post.go deleted file mode 100644 index f00796c..0000000 --- a/update_channel_post.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "sync" - "time" - - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -var waitForwards = new(sync.WaitGroup) - -func updateChannelPost(post *tg.Message) { - if post.Chat.ID != channelID { - log.Ln(post.Chat.ID, "!=", channelID) - return - } - - users, err := dbGetUsers() - errCheck(err) - - for i := range users { - waitForwards.Add(1) - if _, err = bot.ForwardMessage( - tg.NewForwardMessage(post.Chat.ID, int64(users[i]), post.ID), - ); err != nil { - log.Ln(err.Error()) - } - waitForwards.Done() - - time.Sleep(time.Second / 10) // For avoid spamming - } -} diff --git a/update_message.go b/update_message.go deleted file mode 100644 index bcda95d..0000000 --- a/update_message.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - log "github.com/kirillDanshin/dlog" - tg "github.com/toby3d/telegram" -) - -func updateMessage(msg *tg.Message) { - if bot.IsMessageFromMe(msg) || bot.IsForwardFromMe(msg) { - log.Ln("Ignore message update") - return - } - - switch { - case bot.IsCommandToMe(msg): - commands(msg) - case msg.IsText(): - messages(msg) - default: - actions(msg) - } -}