Compare commits

...

75 Commits

Author SHA1 Message Date
Maxim Lebedev 7ce10980cc
♻️ Refactored stores by using bolthold package 2020-02-28 19:31:59 +05:00
Maxim Lebedev 1b7945cb71
👽 Upgraded code due telegram package changes 2020-02-27 20:01:10 +05:00
Maxim Lebedev 6e555e5b3f
✏️ Fixed typos in localization strings 2019-12-26 16:11:34 +05:00
Maxim Lebedev 70cbac4ae7
🚸 Improved inline query searching 2019-12-25 19:55:11 +05:00
Maxim Lebedev 9427686124
🚨 Removed linter warnings 2019-12-24 13:45:02 +05:00
Maxim Lebedev 3880a984bd
🌐 Internationalization and localization 2019-12-24 13:43:26 +05:00
Maxim Lebedev d4573efbe7
♻️ Refactoring code
* Added photos collection support
* Added editing query searching for media
* Improved searching
2019-12-23 18:33:36 +05:00
Maxim Lebedev 0356043e9e
Improving performance 2019-11-29 19:36:54 +05:00
Maxim Lebedev abb8cc1000
♻️ Refactoring code 2019-11-29 18:33:13 +05:00
Maxim Lebedev 0163bc4250
🚚 Moved Errors to model, used command constants 2019-11-29 11:08:21 +05:00
Maxim Lebedev a92d262db4
Fixed tests 2019-11-29 11:07:11 +05:00
Maxim Lebedev 681adc2417
Added profiling flag 2019-11-19 17:38:57 +05:00
Maxim Lebedev 13982a89e0
Added ChatAction middleware 2019-11-19 17:08:40 +05:00
Maxim Lebedev 781b82ad3c
♻️ Refactored Callback actions
♻️ Refactored Callback actions
2019-11-19 17:08:39 +05:00
Maxim Lebedev 43687c7e96
♻️ Architecture refactoring 2019-11-19 16:40:00 +05:00
Maxim Lebedev 37ae8a0c60
Added /ping command
Just in case
2019-11-19 13:02:02 +05:00
Maxim Lebedev 1655495e02
♻️ Context refactoring, improved errors handling 2019-11-19 13:01:25 +05:00
Maxim Lebedev 83cf8e4640
Added HacktoberFest mention 2019-11-19 11:24:26 +05:00
Maxim Lebedev 0e032b366a
♻️ Context refactoring 2019-11-19 10:53:16 +05:00
Maxim Lebedev dc15309bd5
♻️ Added old commands support 2019-11-01 19:16:12 +05:00
Maxim Lebedev 470f03f0ad
🐛 Little fixes and improvements 2019-11-01 19:01:12 +05:00
Maxim Lebedev 36a95a03a4
🐛 Fixed migrator process 2019-11-01 19:00:40 +05:00
Maxim Lebedev 0ba205822b
🚨 Removed linter warnings 2019-11-01 16:33:16 +05:00
Maxim Lebedev b6ce8ae8cb
🗃️ Use real database 2019-10-31 20:31:32 +05:00
Maxim Lebedev ff052c888d
♻️ Changed migrator mechanism 2019-10-29 20:14:09 +05:00
Maxim Lebedev db48dcc904
♻️ Refactored events and middlewares
close #9
2019-10-16 20:26:28 +05:00
Maxim Lebedev 17aa5037c9
Added multilanguage support with inline switcher 2019-10-15 15:19:47 +05:00
Maxim Lebedev 1501ade093
🎨 Format of the code 2019-10-15 15:12:08 +05:00
Maxim Lebedev ef8d8b3cb0
Fixed store tests 2019-10-15 15:11:44 +05:00
Maxim Lebedev ca09c43b99
🐛 Fixed migrator 2019-10-15 15:09:31 +05:00
Maxim Lebedev fd141f0676
🚧 Saved WIP state 2019-10-11 18:37:05 +05:00
Maxim Lebedev 1726be5ab4
♻️ Refactored store 2019-10-11 12:05:32 +05:00
Maxim Lebedev 2ea08030e3
Use inline keyboard for stickers 2019-06-27 20:54:39 +05:00
Maxim Lebedev 717c9ac4cb
🐛 Changed button data format 2019-06-26 21:44:51 +05:00
Maxim Lebedev fcb3ee966f
Added sets caching 2019-06-26 21:18:10 +05:00
Maxim Lebedev 3559881329
♻️ Refactored database usage 2019-06-26 20:24:01 +05:00
Maxim Lebedev ea054b2e3c
♻️ Refactored store 2019-06-26 20:23:08 +05:00
Maxim Lebedev 875284ccbf
🔥 Removed messages 2019-06-26 20:22:09 +05:00
Maxim Lebedev 67400fbab3
🔥 Removed errors 2019-06-26 20:21:27 +05:00
Maxim Lebedev 5619f740c1
🔥 Removed commands 2019-06-26 20:21:15 +05:00
Maxim Lebedev 5e855fb5f2
🔥 Removed old updates 2019-06-25 18:33:39 +05:00
Maxim Lebedev 7d4ef75d72
🔥 Removed old actions 2019-06-25 18:33:27 +05:00
Maxim Lebedev 520fcd639e
🚧 Added Run method 2019-06-25 18:32:52 +05:00
Maxim Lebedev 2f9f98548c
Fixed tests 2019-06-24 17:02:26 +05:00
Maxim Lebedev ce736a80d9
🍱 Added old db testing file 2019-06-24 17:01:47 +05:00
Maxim Lebedev 1836b6dc63
🗃️ Improved database methods and models 2019-06-24 15:49:12 +05:00
Maxim Lebedev 93c8ce7863
Added db test 2019-06-24 15:48:17 +05:00
Maxim Lebedev 98847153e7
🗃️ Added db migrator for old data 2019-06-24 15:47:33 +05:00
Maxim Lebedev 900ec6fee1
♻️ Refactored some parts of the bot 2019-06-21 19:30:38 +05:00
Maxim Lebedev 0fa5661917
🔥 Removed old database code 2019-06-21 19:21:12 +05:00
Maxim Lebedev da611ab04b
♻️ Refactored database 2019-06-21 19:19:43 +05:00
Maxim Lebedev 5be629c14c
♻️ Use new interfaces in New method from internal
#12
2019-02-21 19:07:34 +05:00
Maxim Lebedev 96327f139d
♻️ Refactor config subpackage
#12
2019-02-21 19:07:28 +05:00
Maxim Lebedev feca5b3bd2
🙈 Fixed .gitignore
Ignore vendor folder, production config and build binary
2019-02-21 19:07:27 +05:00
Maxim Lebedev 66b6ce4e9b
🗃️ Database refactor
#12
2019-02-21 19:07:20 +05:00
Maxim Lebedev f95c2af53d
🐛 Fixed in set case in add operation 2018-08-31 18:56:04 +05:00
Maxim Lebedev 20a688c62d
🐛 Fixed flags 2018-08-31 18:54:43 +05:00
Maxim Lebedev 9f632d1686
🔀 Merge branch 'feature/custom-stickers' into develop 2018-08-31 18:54:15 +05:00
Maxim Lebedev 09b42aaae9
Remove single custom sticker in delete pack mode 2018-08-31 18:53:54 +05:00
Maxim Lebedev 29808690ea
Add custom stickers if add pack mode is active 2018-08-31 18:52:31 +05:00
Maxim Lebedev 4e21f2f296
Added config and db flags support 2018-08-31 18:30:44 +05:00
Maxim Lebedev 86df2de85c
🐛 Fixed environment variables in scripts 2018-08-31 18:25:52 +05:00
Maxim Lebedev db98b9e322
♻️ Updated Makefile 2018-08-21 13:04:39 +05:00
Maxim Lebedev bf162fdff4
🚚 Moved GitLab CI config to build/ci directory 2018-08-21 13:04:13 +05:00
Maxim Lebedev f2c691e8b8
🚚 Move main scripts to cmd directory 2018-08-21 12:59:04 +05:00
Maxim Lebedev 784e934128
Added helper script for set environment vars 2018-08-21 12:58:37 +05:00
Maxim Lebedev 2808ca58a9
🌐 Use gotext for localizations 2018-08-20 19:12:22 +05:00
Maxim Lebedev ecd223bc61
🔀 Merge branch 'bugfix/9-duplicated-pages' into develop 2018-08-20 04:13:35 +05:00
Maxim Lebedev 997315ed73
🐛 Fixed invalid pagination
close #9
2018-08-20 04:01:58 +05:00
Maxim Lebedev 412341b3b0
Added New method for errors
#9
2018-08-17 13:01:30 +05:00
Maxim Lebedev 5fdff653b9
🚧 Saved WIP state
#9
2018-08-17 13:01:30 +05:00
Maxim Lebedev fce573dd59
🐛 Fixed binary runnning
#9
2018-08-17 13:01:30 +05:00
Maxim Lebedev b0ab2c9226
🐛 Fixed keyboard resize
#9
2018-08-17 13:01:24 +05:00
Maxim Lebedev 17b1b162c9
🐛 Fixed config open
#9
2018-08-17 12:59:09 +05:00
Maxim Lebedev 5a59a23e20
♻️ Use UserID only for db queries
#9
2018-08-17 12:21:44 +05:00
109 changed files with 4000 additions and 1867 deletions

5
.gitignore vendored
View File

@ -16,8 +16,9 @@
.glide/
# Production files must not be in public repository
MyPackBot
vendor/
/mypackbot
stickers.db
configs/config.yaml
configs/production.yaml
cert.key
cert.pem

View File

@ -1,47 +0,0 @@
image: golang:alpine
cache:
paths:
- /go/src/github.com
- /go/src/gitlab.com
- /go/src/golang.org
- /go/src/google.golang.org
- /go/src/gopkg.in
stages:
- test
- build
before_script:
- apk add --no-cache git build-base bash
- mkdir -p /go/src/gitlab.com/$CI_PROJECT_NAMESPACE /go/src/_/builds
- cp -r $CI_PROJECT_DIR /go/src/gitlab.com/$CI_PROJECT_PATH
- ln -s /go/src/gitlab.com/$CI_PROJECT_NAMESPACE /go/src/_/builds/$CI_PROJECT_NAMESPACE
- make dep
unit_tests:
stage: test
script:
- make test
.race_detector:
stage: test
script:
- make race
code_coverage:
stage: test
script:
- make coverage
lint_code:
stage: test
script:
- go get github.com/go-critic/go-critic/cmd/gocritic
- go install github.com/go-critic/go-critic/cmd/gocritic
- make lint
build:
stage: build
script:
- make

View File

@ -1,32 +1,37 @@
PROJECT_NAMESPACE := $(CI_PROJECT_NAMESPACE)
PROJECT_NAME := $(CI_PROJECT_NAME)
PROJECT_PATH := $(PROJECT_NAMESPACE)/$(PROJECT_NAME)
PACKAGE_NAME := "gitlab.com/$(PROJECT_PATH)"
PACKAGE_NAME := gitlab.com/$(PROJECT_PATH)
PACKAGE_PATH := $(GOPATH)/src/$(PACKAGE_NAME)
PACKAGE_LIST := $(shell go list $(PACKAGE_NAME)/... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
GIT_COMMIT := $(shell git rev-list -1 HEAD)
.PHONY: all lint test rase coverage dep build clean
all: build
lint: ## Lint the files
@golangci-lint run $(PACKAGE_PATH)/...
@gocritic check-project $(PACKAGE_PATH)
test: ## Run unittests
@go test -short $(PACKAGE_NAME)/test/...
race: dep ## Run data race detector
@go test -race -short ${PACKAGE_LIST}
coverage: ## Generate global code coverage report
@go test -cover -v -coverpkg=$(PACKAGE_NAME) ${PACKAGE_LIST}
@go test -cover -v -coverpkg=$(PACKAGE_NAME)/... ${PACKAGE_LIST}
dep: ## Get the dependencies
@go get -v -d -t ${PACKAGE_LIST}
@go get -v -d -t $(PACKAGE_NAME)/...
build: dep ## Build the binary file
@go build -i -v $(PACKAGE_NAME)
build: dep ## Build the binary files
@go build -ldflags "-X main.gitCommit=$(GIT_COMMIT)" -i -v $(PACKAGE_NAME)/cmd/...
clean: ## Remove previous build
@rm -f $(PROJECT_NAME)
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

56
build/ci/gitlab-ci.yml Normal file
View File

@ -0,0 +1,56 @@
image: golang:alpine
stages:
- test
- review
- build
before_script:
- apk add --no-cache git build-base bash make
- mkdir -p /go/src/gitlab.com/$CI_PROJECT_NAMESPACE /go/src/_/builds
- cp -r $CI_PROJECT_DIR /go/src/gitlab.com/$CI_PROJECT_NAMESPACE
- ln -s /go/src/gitlab.com/$CI_PROJECT_NAMESPACE /go/src/_/builds/$CI_PROJECT_NAMESPACE
- make dep
unit_tests:
stage: test
script:
- make test
.race_detector:
stage: test
script:
- make race
.code_coverage:
stage: test
script:
- make coverage
coverage: '/^coverage:\s(\d+(?:\.\d+)?%)/'
lint_code:
stage: review
script:
- go get github.com/golangci/golangci-lint/cmd/golangci-lint github.com/go-critic/go-critic/cmd/gocritic
- go install github.com/golangci/golangci-lint/cmd/golangci-lint github.com/go-critic/go-critic/cmd/gocritic
- make lint
allow_failure: true
stable:
stage: build
only:
- master
except:
- develop
script:
- make
nightly:
stage: build
only:
- schedules
- develop
except:
- master
script:
- make

58
cmd/migrator/main.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"flag"
"log"
"path/filepath"
bunt "github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/migrator"
"gitlab.com/toby3d/mypackbot/internal/store"
tg "gitlab.com/toby3d/telegram"
)
func main() {
var (
flagGroup = flag.Int64("group", 0, "proxy group for migration")
flagNew = flag.String("new", filepath.Join(".", "new.db"), "filepath to new database file")
flagOld = flag.String("old", filepath.Join(".", "old.db"), "filepath to old database file")
flagToken = flag.String("token", "", "bot token")
)
flag.Parse()
bot, err := tg.New(*flagToken)
if err != nil {
log.Fatalln("ERROR:", err.Error())
}
oldDB, err := bunt.Open(*flagOld)
if err != nil {
log.Fatalln("ERROR OLD DB:", err.Error())
}
defer oldDB.Close()
newDB, err := db.Open(*flagNew)
if err != nil {
log.Fatalln("ERROR NEW DB:", err.Error())
}
defer newDB.Close()
users := store.NewUsersStore(newDB)
stickers := store.NewStickersStore(newDB)
usersStickers := store.NewUsersStickersStore(newDB, users, stickers)
if err = migrator.AutoMigrate(migrator.AutoMigrateConfig{
Bot: bot,
GroupID: *flagGroup,
Stickers: stickers,
UsersStickers: usersStickers,
Users: users,
OldDB: oldDB,
}); err != nil {
log.Fatalln("ERROR:", err.Error())
}
log.Println("Done!")
}

145
cmd/mypackbot/catalog.go Normal file
View File

@ -0,0 +1,145 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
type dictionary struct {
index []uint32
data string
}
func (d *dictionary) Lookup(key string) (data string, ok bool) {
p := messageKeyToIndex[key]
start, end := d.index[p], d.index[p+1]
if start == end {
return "", false
}
return d.data[start:end], true
}
func init() {
dict := map[string]catalog.Dictionary{
"en": &dictionary{index: enIndex, data: enData},
"ru": &dictionary{index: ruIndex, data: ruData},
}
fallback := language.MustParse("en")
cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
if err != nil {
panic(err)
}
message.DefaultCatalog = cat
}
var messageKeyToIndex = map[string]int{
"☺️ Oh, you missed @toby3d birthday on November 4th!\nIf you like this bot, why not send him some birthday greetings and a little birthday gift? It is not yet too late to make him happy!": 17,
"👋 Hi %s, I'm %s!\nThanks to me, you can collect almost any media content in Telegram without any limits, in any chat via inline mode.": 0,
"👍 Imported!": 5,
"👍 Removed!": 7,
"👍 Updated!": 6,
"💡 Add any text and/or emoji(s) as an argument of this command to change its search properties.": 12,
"💡 Use /del command as an reply to the sticker/photo to remove it from the feed of your collection.": 4,
"💡 Use the /add command as a reply to the sticker/photo to add this media to your collection feed. Given an argument, the result of this command will be equivalent to the /edit command.": 2,
"💡 Use the /edit command with an argument from any character set as a reply to a sticker/photo to change the search query of this media in the feed of your collection. If this media is not in the feed, then the result of this command will be equivalent to the /add command with the same argument.": 3,
"💸 Make a donation": 18,
"📥 Import %s set": 14,
"📥 Import this photo": 10,
"📥 Import this sticker": 13,
"🔥 Remove %s set": 15,
"🔥 Remove this photo": 11,
"🔧 Let's hack!": 21,
"🕵 Found %d result(s)": 8,
"🕺 HacktoberFest is here!\n\nIf you are a beginner or already an experienced golang-developer, now is a great time to help improve the quality of the code of this bot. Choose issue to your taste and offer your PR!": 20,
"🤔 What to do with this?": 9,
"🤖 Here is a list of commands that I understand, some of them [may] or (should) contain an argument:\n/start - start all over again\n/help [other command] - get a list of available commands or help and a demonstration of a specific command\n/add [query] - add media from reply to your collection [with custom search query]\n/edit (query) - change query to reply media\n/del - remove reply media from your collection": 1,
"🤝 Use referral links": 19,
"🥳 4 November? It's a @toby3d birthday!\nIf you like this bot, then why not send him a congratulation along with a small gift? This will make him incredibly happy!": 16,
}
var enIndex = []uint32{ // 23 elements
0x00000000, 0x0000008e, 0x0000022b, 0x000002e7,
0x00000412, 0x00000478, 0x00000487, 0x00000495,
0x000004a3, 0x000004be, 0x000004d9, 0x000004f0,
0x00000507, 0x00000569, 0x00000582, 0x00000598,
0x000005ae, 0x00000653, 0x00000710, 0x00000725,
0x0000073d, 0x00000813, 0x00000824,
} // Size: 116 bytes
const enData string = "" + // Size: 2084 bytes
"\x02👋 Hi %[1]s, I'm %[2]s!\x0aThanks to me, you can collect almost any m" +
"edia content in Telegram without any limits, in any chat via inline mode" +
".\x02🤖 Here is a list of commands that I understand, some of them [may] " +
"or (should) contain an argument:\x0a/start - start all over again\x0a/he" +
"lp [other command] - get a list of available commands or help and a demo" +
"nstration of a specific command\x0a/add [query] - add media from reply t" +
"o your collection [with custom search query]\x0a/edit (query) - change q" +
"uery to reply media\x0a/del - remove reply media from your collection" +
"\x02💡 Use the /add command as a reply to the sticker/photo to add this m" +
"edia to your collection feed. Given an argument, the result of this comm" +
"and will be equivalent to the /edit command.\x02💡 Use the /edit command " +
"with an argument from any character set as a reply to a sticker/photo to" +
" change the search query of this media in the feed of your collection. I" +
"f this media is not in the feed, then the result of this command will be" +
" equivalent to the /add command with the same argument.\x02💡 Use /del co" +
"mmand as an reply to the sticker/photo to remove it from the feed of you" +
"r collection.\x02👍 Imported!\x02👍 Updated!\x02👍 Removed!\x02🕵 Found %[1]" +
"d result(s)\x02🤔 What to do with this?\x02📥 Import this photo\x02🔥 Remov" +
"e this photo\x02💡 Add any text and/or emoji(s) as an argument of this co" +
"mmand to change its search properties.\x02📥 Import this sticker\x02📥 Imp" +
"ort %[1]s set\x02🔥 Remove %[1]s set\x02🥳 4 November? It's a @toby3d birt" +
"hday!\x0aIf you like this bot, then why not send him a congratulation al" +
"ong with a small gift? This will make him incredibly happy!\x02☺ Oh, yo" +
"u missed @toby3d birthday on November 4th!\x0aIf you like this bot, why " +
"not send him some birthday greetings and a little birthday gift? It is n" +
"ot yet too late to make him happy!\x02💸 Make a donation\x02🤝 Use referra" +
"l links\x02🕺 HacktoberFest is here!\x0a\x0aIf you are a beginner or alre" +
"ady an experienced golang-developer, now is a great time to help improve" +
" the quality of the code of this bot. Choose issue to your taste and off" +
"er your PR!\x02🔧 Let's hack!"
var ruIndex = []uint32{ // 23 elements
0x00000000, 0x00000106, 0x000003c1, 0x00000514,
0x00000714, 0x000007c6, 0x000007e7, 0x00000800,
0x00000815, 0x00000846, 0x0000086c, 0x00000895,
0x000008b0, 0x0000096f, 0x0000099c, 0x000009c2,
0x000009da, 0x00000b0f, 0x00000c68, 0x00000c88,
0x00000cb1, 0x00000dfd, 0x00000e0e,
} // Size: 116 bytes
const ruData string = "" + // Size: 3598 bytes
"\x02👋 Привет %[1]s, я %[2]s!\x0aБлагодаря мне, ты можешь коллекционирова" +
"ть в inline-режиме практически любой медиа-контент Telegram без огранич" +
"ений, в любом чате.\x02🤖 Вот список команд которые я понимаю, некоторые" +
" из них [могут] или (должны) содержать аргументы:\x0a/start - начать всё" +
" заново\x0a/help [другая команда] - получить список всех доступных коман" +
"д или справку о конкретной команде\x0a/add [текст] - добавить медиа из " +
"ответа в коллекцию [с указанным поисковым тегом]\x0a/edit (текст) - изм" +
"енить поисковый тег медиа из ответа\x0a/del - удалить медиа из ответа и" +
"з коллекции\x02💡 Используй команду /add как ответ на стикер/фото чтобы " +
"добавить это медиа в ленту твоей коллекции. При наличии аргумента резул" +
"ьтат действия этой команды будет эквивалентно команде /edit.\x02💡 Испол" +
"ьзуй команду /edit с аргументом из любого набора символов как ответ на " +
"стикер/фото чтобы изменить поисковую строку этого медиа в ленте твоей к" +
"оллекции. Если это медиа отсутствует в ленте, то результат действия это" +
"й команды будет эквивалентно команде /add с тем же аргументом.\x02💡 Исп" +
"ользуй команду /del как ответ на стикер/фото чтобы убрать это медиа из " +
"ленты твоей коллекции.\x02👍 Импортировано!\x02👍 Обновлено!\x02👍 Удалено" +
"!\x02🕵 Найдено %[1]d результатов\x02🤔 Что с этим делать?\x02📥 Импортиров" +
"ать фото\x02🔥 Убрать фото\x02💡 Добавь любой текст и/или эмодзи в качест" +
"ве аргумента этой команды чтобы изменить поисковые свойства.\x02📥 Импор" +
"тировать стикер\x02📥 Импортировать %[1]s\x02🔥 Убрать %[1]s\x02🥳 4-е Ноя" +
"бря? Это день рождения @toby3d!\x0aЕсли тебе нравится этот бот, то поче" +
"му бы не отправить ему поздравления вместе с небольшим подарком? Это не" +
"сказанно его осчастливит!\x02☺ Ой, ты пропустил день рождения @toby3d " +
"4-го Ноября!\x0aЕсли тебе нравится этот бот, то почему бы не отправить е" +
"му поздравления вместе с небольшим подарком? Ещё не слишком поздно его " +
"порадовать!\x02💸 Пожертвование\x02🤝 Реферальные ссылки\x02🕺 Хактоберфес" +
"т уже здесь!\x0a\x0aЕсли ты начинающий или уже опытный golang-разработч" +
"ик, то сейчас хорошее время помочь улучшить качество кода этого бота. В" +
"ыбери issue на свой вкус и предложи PR!\x02🔧 Let's hack!"
// Total table size 5914 bytes (5KiB); checksum: 490F86A

55
cmd/mypackbot/main.go Normal file
View File

@ -0,0 +1,55 @@
//go:generate gotext -dir=./../../ -srclang=en update -out=catalog.go -lang=en,ru .
package main
import (
"flag"
"log"
"os"
"os/signal"
"path/filepath"
"syscall"
"gitlab.com/toby3d/mypackbot/internal"
"gitlab.com/toby3d/mypackbot/internal/common"
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func init() {
_ = message.Set(language.English, "🕵 Found %d result(s)", plural.Selectf(1, "%d",
"zero", "🤷 Nothing found",
"one", "🕵 Found %d result",
"many", "🕵 Found %[1]d results",
))
_ = message.Set(language.Russian, "🕵 Found %d result(s)", plural.Selectf(1, "%d",
"zero", "🤷 Ничего не найдено",
"one", "🕵 Найден %[1]d результат",
"<5", "🕵 Найдено %[1]d результата",
"many", "🕵 Найдено %[1]d результатов",
))
}
func main() {
flagConfig := flag.String("config", filepath.Join(".", "config.yaml"), "set specific path to config")
flag.Parse()
log.Println("Current build version:", common.Version.String())
bot, err := internal.New(*flagConfig)
if err != nil {
log.Fatalln("ERROR:", err.Error())
}
go func() {
if err = bot.Run(); err != nil {
log.Fatalln("ERROR:", err.Error())
}
}()
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
<-sig
}

View File

@ -4,4 +4,19 @@ telegram:
set: https://toby3d.github.io
listen: /bot
serve: 0.0.0.0:2368
channel: -1000000000000
allowed_updates:
- inline_query
- message
- channel_post
- callback_query
long_poll:
limit: 100
offset: 0
timeout: 1
allowed_updates:
- inline_query
- message
- channel_post
- callback_query
database:
filepath: ./development.db

32
go.mod Normal file
View File

@ -0,0 +1,32 @@
module gitlab.com/toby3d/mypackbot
go 1.13
require (
github.com/Masterminds/semver v1.5.0
github.com/json-iterator/go v1.1.9
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9
github.com/klauspost/compress v1.10.2 // indirect
github.com/pelletier/go-toml v1.6.0 // indirect
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.3.0
github.com/tidwall/buntdb v1.1.2
github.com/tidwall/gjson v1.5.0 // indirect
github.com/tidwall/pretty v1.0.1 // indirect
github.com/timshannon/bolthold v0.0.0-20200224174833-bf6268f136e7
github.com/valyala/fasthttp v1.9.0
github.com/valyala/fastjson v1.5.0
gitlab.com/toby3d/telegram v0.0.0-20200228130040-d70608c147c0
go.etcd.io/bbolt v1.3.3
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
golang.org/x/text v0.3.2
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
gopkg.in/ini.v1 v1.52.0 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)
replace go.etcd.io/bbolt v1.3.3 => github.com/etcd-io/bbolt v1.3.3

236
go.sum Normal file
View File

@ -0,0 +1,236 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9 h1:mA7k8E2Vrmyj5CW/D1XZBFmohVNi7jf757vibGwzRbo=
github.com/kirillDanshin/dlog v0.0.0-20170728000807-97d876b12bf9/go.mod h1:l8CN7iyX1k2xlsTYVTpCtwBPcxThf/jLWDGVcF6T/bM=
github.com/kirillDanshin/myutils v0.0.0-20160713214838-182269b1fbcc h1:OkOhOn3WBUmfATC1NsA3rBlgHGkjk0KGnR5akl/8uXc=
github.com/kirillDanshin/myutils v0.0.0-20160713214838-182269b1fbcc/go.mod h1:Bt95qRxLvpdmASW9s2tTxGdQ5ma4o4n8QFhCvzCew/M=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7 h1:hYW1gP94JUmAhBtJ+LNz5My+gBobDxPR1iVuKug26aA=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.2 h1:Znfn6hXZAHaLPNnlqUYRrBSReFHYybslgv4PTiyz6P0=
github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.5.0 h1:QCssIUI7J0RStkzIcI4A7O6P8rDA5wi5IPf70uqKSxg=
github.com/tidwall/gjson v1.5.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
github.com/timshannon/bolthold v0.0.0-20200224174833-bf6268f136e7 h1:jLXW7TX5THBQxw+sFH+TjBQuD8lksMwygI4BARqHdJI=
github.com/timshannon/bolthold v0.0.0-20200224174833-bf6268f136e7/go.mod h1:jUigdmrbdCxcIDEFrq82t4X9805XZfwFZoYUap0ET/U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.7.1 h1:UHtt5/7O70RSUZTR/hSu0PNWMAfWx5AtsPp9Jk+g17M=
github.com/valyala/fasthttp v1.7.1/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fastjson v1.5.0 h1:DGrb4wEYso2HdGLyLmNoyNCQnCWfjd8yhghPv5/5YQg=
github.com/valyala/fastjson v1.5.0/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
gitlab.com/toby3d/telegram v0.0.0-20200218162808-858deeef8eeb h1:KY5aTKkQZGNFfjBoJf3lq374ekqRYQy4MB/tyi54MoQ=
gitlab.com/toby3d/telegram v0.0.0-20200218162808-858deeef8eeb/go.mod h1:vV0ZvEaNCSTcjGgN8Y4osyg1Fv8eyCvPaIfSMAqfltE=
gitlab.com/toby3d/telegram v0.0.0-20200228130040-d70608c147c0 h1:tVrOHMDJ6x0gA/t1f9S6tcyyygSQENHjvFEuLE0x6Fk=
gitlab.com/toby3d/telegram v0.0.0-20200228130040-d70608c147c0/go.mod h1:vV0ZvEaNCSTcjGgN8Y4osyg1Fv8eyCvPaIfSMAqfltE=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,101 +0,0 @@
button_add_pack:
other: "\U0001F4E6 Add pack"
button_add_sticker:
other: Add sticker
button_cancel:
other: ❌ Cancel
button_del_pack:
other: "\U0001F5D1 Delete pack"
button_del_sticker:
other: "\U0001F5D1 Delete sticker"
button_inline_empty:
other: Your pack is empty
button_inline_nothing:
other: Not found stickers for {{.Query}}
button_inline_search:
one: You have {{.Count}} sticker
other: You have {{.Count}} stickers
button_inline_select:
other: Select sticker
button_reset:
other: "\U0001F525 Reset pack"
button_share:
other: Use your stickers pack!
cancel_add_pack:
other: You cancelled the process of adding a new packs to yours.
cancel_add_sticker:
other: You cancelled the process of adding a new stickers to your pack.
cancel_del_pack:
other: You cancelled the process of removing a sticker packs from yours.
cancel_del_sticker:
other: You cancelled the process of removing a stickers from your pack.
cancel_error:
other: Nothing to cancel.
cancel_reset:
other: You cancelled the process of reseting your stickers 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_pack:
other: Maybe this pack is already removed from yours.
error_already_del_sticker:
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_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}}, /{{.DeleteStickerCommand}} or /{{.DeletePackCommand}} command first.
key_phrase:
other: Yes, I am totally sure.
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_pack:
other: Send an existing stickers from your pack to delete its entire set.
reply_del_sticker:
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
/{{.DeleteStickerCommand}} - remove a single sticker from your pack
/{{.DeletePackCommand}} - remove a sticker set 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_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.
reply_switch_button:
other: This button will help you quickly call your pack to select the sticker you
want.
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_pack:
other: The sticker pack *{{.SetTitle}}* was successfully removed from yours!
success_del_sticker:
other: The sticker was successfully removed from your pack!
success_reset:
other: The contents of your pack are completely reset!..

View File

@ -1 +0,0 @@
{}

View File

@ -1,101 +0,0 @@
button_add_pack:
other: "\U0001F4E6 Добавить набор"
button_add_sticker:
other: Добавить стикер
button_cancel:
other: ❌ Отменить
button_del_pack:
other: "\U0001F5D1 Удалить набор"
button_del_sticker:
other: "\U0001F5D1 Удалить стикер"
button_inline_empty:
other: Твой набор пуст
button_inline_nothing:
other: Не найдены стикеры для {{.Query}}
button_inline_search:
few: У тебя {{.Count}} стикера
many: У тебя {{.Count}} стикеров
one: У тебя {{.Count}} стикер
other: У тебя {{.Count}} стикеров
button_inline_select:
other: Выбрать стикер
button_reset:
other: "\U0001F525 Сбросить набор"
button_share:
other: Воспользоваться твоим набором!
cancel_add_pack:
other: Ты отменил процесс добавления новых наборов в твой.
cancel_add_sticker:
other: Ты отменил процесс добавления новых стикеров в твой набор.
cancel_del_pack:
other: Ты отменил процесс удаления наборов из твоего набора.
cancel_del_sticker:
other: Ты отменил процесс удаления стикера из твоего набора.
cancel_error:
other: Нечего отменять.
cancel_reset:
other: Ты отменил процесс сброса твоего набора.
error_already_add_pack:
other: Все стикеры *{{.SetTitle}}* уже в твоём наборе.
error_already_add_sticker:
other: Этот стикер уже в твоём наборе.
error_already_del_pack:
other: Вероятно этот набор уже удалён из твоего.
error_already_del_sticker:
other: Вероятно этот стикер уже удалён из твоего набора.
error_already_reset:
other: Нечего сбрасывать, набор уже пуст.
error_empty_add_pack:
other: Кажется ты пытаешься добавить собственный стикер. Используй для этого /{{.AddStickerCommand}}.
error_empty_del:
other: Нечего удалять, набор уже пуст.
error_reset_phrase:
other: Неправильная фраза для сброса. Действие было отменено.
error_unknown:
other: |-
Я понятия не имею что делать с этим стикером.
Пожалуйста, сначала примени /{{.AddStickerCommand}}, /{{.AddPackCommand}}, /{{.DeleteStickerCommand}} или /{{.DeletePackCommand}}.
key_phrase:
other: Да, я абсолютно уверен.
reply_add_pack:
other: Пришли стикеры из любых других наборов чтобы целиком добавить их наборы в
свой.
reply_add_sticker:
other: Пришли стикеры из любых других наборов чтобы по-одному добавить их в свой.
reply_del_pack:
other: Пришли стикер из своего набора чтобы удалить весь его набор.
reply_del_sticker:
other: Пришли стикер из своего набора чтобы удалить его.
reply_help:
other: |
/{{.AddStickerCommand}} - по-одному добавляет стикеры в твой набор
/{{.AddPackCommand}} - добавляет сразу весь набор в твой
/{{.DeleteStickerCommand}} - по-одному удаляет стикер из твоего набора
/{{.DeletePackCommand}} - удаляет набор стикеров из твоего набора
/{{.ResetCommand}} - удаляет все стикеры из твоего набора
/{{.CancelCommand}} - отменяет текущую операцию
Для просмотра и отправки стикеров из твоего набора просто набери `@{{.Username}}` (и пробел) в любом чате.
reply_reset:
other: |
Эта операция удалит *все* стикеры из твоего набора и *это не может быть отменено*.
Напиши `{{.KeyPhrase}}` чтобы подтвердить своё намерение обнулить мои мозги (о боже зачем).
Или используй /{{.CancelCommand}} чтобы отменить текущую операцию.
reply_start:
other: |
Привет, я [@{{.Username}}](tg://user?id={{.ID}})!
Я могу создать твой персональный набор стикеров из других наборов.
Без ограничений и установки. В любых чатах. Бесплатно.
reply_switch_button:
other: Эта кнопка поможет тебе быстро вызвать твой набор для выбора нужного стикера.
success_add_pack:
other: Набор *{{.SetTitle}}* успешно добавлен в твой!
success_add_sticker:
other: Стикер успешно добавлен в твой набор!
success_del_pack:
other: Набор *{{.SetTitle}}* успешно удалён из твоего набора!
success_del_sticker:
other: Стикер успешно удалён из твоего набора!
success_reset:
other: Сброс твоего набора успешно произведён!..

View File

@ -1 +0,0 @@
{}

View File

@ -1,23 +0,0 @@
button_inline_empty:
other: Your pack is empty
button_inline_nothing:
other: Not found stickers for {{.Query}}
button_inline_search:
one: You have {{.Count}} sticker
other: You have {{.Count}} stickers
button_inline_select:
other: Select sticker
button_share:
other: Use your stickers pack!
button_add_sticker:
other: Add sticker
button_add_pack:
other: 📦 Add set
button_del_sticker:
other: 🗑 Delete sticker
button_del_pack:
other: 🗑 Delete set
button_reset:
other: 🔥 Reset pack
button_cancel:
other: ❌ Cancel

View File

@ -1,12 +0,0 @@
cancel_add_sticker:
other: You cancelled the process of adding a new stickers to your pack.
cancel_add_pack:
other: You cancelled the process of adding a new sets to your pack.
cancel_del_sticker:
other: You cancelled the process of removing a stickers from your pack.
cancel_del_pack:
other: You cancelled the process of removing a sets from your pack.
cancel_reset:
other: You cancelled the process of reseting your stickers pack.
cancel_error:
other: Nothing to cancel.

View File

@ -1,20 +0,0 @@
error_already_add_sticker:
other: This sticker is already in your pack.
error_already_add_pack:
other: All stickers from *{{.SetTitle}}* set is already in yours.
error_already_del_sticker:
other: Maybe this sticker is already removed from your pack.
error_already_del_pack:
other: Maybe this set 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, your 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}}, /{{.DeleteStickerCommand}} or /{{.DeletePackCommand}} command first.

View File

@ -1,31 +0,0 @@
reply_start:
other: |
Hello, I'm the [@{{.Username}}](tg://user?id={{.ID}})!
I can create your personal stickers pack with stickers from others sets.
Without limits and installing. In any chat. For free.
reply_add_sticker:
other: Send an existing stickers from any other sets to add it to your pack.
reply_add_pack:
other: Send an existing stickers from any other sets to add the entire sets to your pack.
reply_del_sticker:
other: Send an existing stickers from your pack for removing it.
reply_del_pack:
other: Send an existing stickers from your pack to delete its entire set.
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 stickers set to your pack
/{{.DeleteStickerCommand}} - remove a single sticker from your pack
/{{.DeletePackCommand}} - remove a sticker set 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_switch_button:
other: This button will help you quickly call your pack to select the sticker you want.

View File

@ -1,10 +0,0 @@
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_sticker:
other: The sticker was successfully removed from your pack!
success_del_pack:
other: The sticker pack *{{.SetTitle}}* was successfully removed from yours!
success_reset:
other: The contents of your pack are completely reset!

View File

@ -1,2 +0,0 @@
key_phrase:
other: Yes, I am totally sure.

View File

@ -1,25 +0,0 @@
button_inline_empty:
other: Твой набор пуст
button_inline_nothing:
other: Не найдены стикеры для {{.Query}}
button_inline_search:
few: У тебя {{.Count}} стикера
one: У тебя {{.Count}} стикер
many: У тебя {{.Count}} стикеров
other: У тебя {{.Count}} стикеров
button_inline_select:
other: Выбрать стикер
button_share:
other: Воспользоваться твоим набором!
button_add_sticker:
other: Добавить стикер
button_add_pack:
other: 📦 Добавить набор
button_del_sticker:
other: 🗑 Удалить стикер
button_del_pack:
other: 🗑 Удалить набор
button_reset:
other: 🔥 Сбросить набор
button_cancel:
other: ❌ Отменить

View File

@ -1,12 +0,0 @@
cancel_add_sticker:
other: Ты отменил процесс добавления новых стикеров в твой набор.
cancel_add_pack:
other: Ты отменил процесс добавления новых наборов в твой.
cancel_del_sticker:
other: Ты отменил процесс удаления стикера из твоего набора.
cancel_del_pack:
other: Ты отменил процесс удаления наборов из твоего набора.
cancel_reset:
other: Ты отменил процесс сброса твоего набора.
cancel_error:
other: Нечего отменять.

View File

@ -1,20 +0,0 @@
error_already_add_sticker:
other: Этот стикер уже в твоём наборе.
error_already_add_pack:
other: Все стикеры *{{.SetTitle}}* уже в твоём наборе.
error_already_del_sticker:
other: Вероятно этот стикер уже удалён из твоего набора.
error_already_del_pack:
other: Вероятно этот набор уже удалён из твоего.
error_already_reset:
other: Нечего сбрасывать, набор уже пуст.
error_reset_phrase:
other: Неправильная фраза для сброса. Действие было отменено.
error_empty_del:
other: Нечего удалять, набор уже пуст.
error_empty_add_pack:
other: Кажется ты пытаешься добавить собственный стикер. Используй для этого /{{.AddStickerCommand}}.
error_unknown:
other: |
Я понятия не имею что делать с этим стикером.
Пожалуйста, сначала примени /{{.AddStickerCommand}}, /{{.AddPackCommand}}, /{{.DeleteStickerCommand}} или /{{.DeletePackCommand}}.

View File

@ -1,31 +0,0 @@
reply_start:
other: |
Привет, я [@{{.Username}}](tg://user?id={{.ID}})!
Я могу создать твой персональный набор стикеров из других наборов.
Без ограничений и установки. В любых чатах. Бесплатно.
reply_add_sticker:
other: Пришли стикеры из любых других наборов чтобы по-одному добавить их в свой.
reply_add_pack:
other: Пришли стикеры из любых других наборов чтобы целиком добавить их наборы в свой.
reply_del_sticker:
other: Пришли стикер из своего набора чтобы удалить его.
reply_del_pack:
other: Пришли стикер из своего набора чтобы удалить весь его набор.
reply_reset:
other: |
Эта операция удалит *все* стикеры из твоего набора и *это не может быть отменено*.
Напиши `{{.KeyPhrase}}` чтобы подтвердить своё намерение обнулить мои мозги (о боже зачем).
Или используй /{{.CancelCommand}} чтобы отменить текущую операцию.
reply_help:
other: |
/{{.AddStickerCommand}} - по-одному добавляет стикеры в твой набор
/{{.AddPackCommand}} - добавляет сразу весь набор в твой
/{{.DeleteStickerCommand}} - по-одному удаляет стикер из твоего набора
/{{.DeletePackCommand}} - удаляет набор стикеров из твоего набора
/{{.ResetCommand}} - удаляет все стикеры из твоего набора
/{{.CancelCommand}} - отменяет текущую операцию
Для просмотра и отправки стикеров из твоего набора просто набери `@{{.Username}}` (и пробел) в любом чате.
reply_switch_button:
other: Эта кнопка поможет тебе быстро вызвать твой набор для выбора нужного стикера.

View File

@ -1,10 +0,0 @@
success_add_sticker:
other: Стикер успешно добавлен в твой набор!
success_add_pack:
other: Набор *{{.SetTitle}}* успешно добавлен в твой!
success_del_sticker:
other: Стикер успешно удалён из твоего набора!
success_del_pack:
other: Набор *{{.SetTitle}}* успешно удалён из твоего набора!
success_reset:
other: Сброс твоего набора успешно произведён!

View File

@ -1,2 +0,0 @@
key_phrase:
other: Да, я абсолютно уверен.

View File

@ -1,35 +0,0 @@
package actions
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.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.DB.GetUserState(msg.From)
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.DB.ChangeUserState(msg.From, models.StateNone)
errors.Check(err)
Error(msg)
}
}

View File

@ -1,81 +0,0 @@
package actions
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.StyleMarkdown
if !pack {
var exist bool
sticker := msg.Sticker
exist, err = db.DB.AddSticker(msg.From, sticker)
errors.Check(err)
if exist {
reply.Text = t("error_already_add_sticker")
}
reply.ReplyMarkup = utils.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 i := range set.Stickers {
var exist bool
exist, err = db.DB.AddSticker(msg.From, &set.Stickers[i])
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 = utils.CancelButton(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,54 +0,0 @@
package actions
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.StyleMarkdown
reply.ReplyMarkup = utils.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.DB.DeletePack(msg.From, msg.Sticker)
if notExist {
reply.Text = t("error_already_del_pack")
}
} else {
notExist, err = db.DB.DeleteSticker(msg.From, msg.Sticker)
if notExist {
reply.Text = t("error_already_del_sticker")
}
}
errors.Check(err)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,33 +0,0 @@
package actions
import (
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,44 +0,0 @@
package actions
import (
"strings"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.DB.ChangeUserState(msg.From, 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.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
return
}
err = db.DB.ResetUser(msg.From)
errors.Check(err)
reply := tg.NewMessage(msg.Chat.ID, t("success_reset"))
reply.ParseMode = tg.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,11 +0,0 @@
package bot
import tg "gitlab.com/toby3d/telegram"
// Bot is a main object of Telegram bot
var Bot *tg.Bot
// New just create new bot by configuration credentials
func New(accessToken string) (bot *tg.Bot, err error) {
return tg.New(accessToken)
}

View File

@ -1,39 +0,0 @@
package commands
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.StyleMarkdown
reply.ReplyMarkup = utils.CancelButton(t)
err = db.DB.ChangeUserState(msg.From, models.StateAddSticker)
errors.Check(err)
if pack {
reply.Text = t("reply_add_pack")
err = db.DB.ChangeUserState(msg.From, models.StateAddPack)
errors.Check(err)
}
log.Ln("Sending add reply...")
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,48 +0,0 @@
package commands
import (
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.DB.GetUserState(msg.From)
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.DB.ChangeUserState(msg.From, models.StateNone)
errors.Check(err)
reply := tg.NewMessage(msg.Chat.ID, text)
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,30 +0,0 @@
package commands
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// Command check's got user command
func Command(msg *tg.Message) {
log.Ln("command:", msg.Command())
switch {
case msg.IsCommandEqual(tg.CommandStart):
Start(msg)
case msg.IsCommandEqual(tg.CommandHelp):
Help(msg)
case msg.IsCommandEqual(models.CommandAddSticker):
Add(msg, false)
case msg.IsCommandEqual(models.CommandAddPack):
Add(msg, true)
case msg.IsCommandEqual(models.CommandDeleteSticker):
Delete(msg, false)
case msg.IsCommandEqual(models.CommandDeletePack):
Delete(msg, true)
case msg.IsCommandEqual(models.CommandReset):
Reset(msg)
case msg.IsCommandEqual(models.CommandCancel):
Cancel(msg)
}
}

View File

@ -1,59 +0,0 @@
package commands
import (
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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)
stickers, err := db.DB.GetUserStickers(msg.From, &tg.InlineQuery{})
errors.Check(err)
_, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping)
errors.Check(err)
if len(stickers) <= 0 {
err = db.DB.ChangeUserState(msg.From, models.StateNone)
errors.Check(err)
reply := tg.NewMessage(msg.Chat.ID, t("error_empty_del"))
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
return
}
reply := tg.NewMessage(msg.Chat.ID, t("reply_del_sticker"))
reply.ParseMode = tg.StyleMarkdown
reply.ReplyMarkup = utils.CancelButton(t)
err = db.DB.ChangeUserState(msg.From, models.StateDeleteSticker)
errors.Check(err)
if pack {
err = db.DB.ChangeUserState(msg.From, 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 = utils.SwitchButton(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,40 +0,0 @@
package commands
import (
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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.DB.ChangeUserState(msg.From, 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.Username,
}),
)
reply.ParseMode = tg.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,47 +0,0 @@
package commands
import (
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.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)
stickers, err := db.DB.GetUserStickers(msg.From, &tg.InlineQuery{})
errors.Check(err)
_, err = bot.Bot.SendChatAction(msg.Chat.ID, tg.ActionTyping)
errors.Check(err)
if len(stickers) <= 0 {
err = db.DB.ChangeUserState(msg.From, models.StateNone)
errors.Check(err)
reply := tg.NewMessage(msg.Chat.ID, t("error_already_reset"))
reply.ParseMode = tg.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
return
}
err = db.DB.ChangeUserState(msg.From, 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.StyleMarkdown
reply.ReplyMarkup = utils.CancelButton(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

View File

@ -1,47 +0,0 @@
package commands
import (
"strings"
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/models"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.com/toby3d/telegram"
)
// Start just send introduction about bot to user
func Start(msg *tg.Message) {
err := db.DB.ChangeUserState(msg.From, 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(), tg.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.Username,
"ID": bot.Bot.ID,
}),
)
reply.ParseMode = tg.StyleMarkdown
reply.ReplyMarkup = utils.MenuKeyboard(t)
_, err = bot.Bot.SendMessage(reply)
errors.Check(err)
}

48
internal/common/common.go Normal file
View File

@ -0,0 +1,48 @@
//nolint: gochecknoglobals
package common
import (
"github.com/Masterminds/semver"
tg "gitlab.com/toby3d/telegram"
)
const (
CommandEdit string = "edit"
// NOTE(toby3d): DEPRECATED
CommandAddPack string = "addpack"
CommandAddSticker string = "add" + tg.TypeSticker
CommandCancel string = "cancel"
CommandDelPack string = "addpack"
CommandDelSticker string = "add" + tg.TypeSticker
CommandReset string = "reset"
)
const (
DataSeparator string = "@"
DataAdd string = "add"
DataDel string = "del"
DataSet string = "set"
DataAddSet string = DataAdd + DataSeparator + DataSet
DataDelSet string = DataDel + DataSeparator + DataSet
)
const SetNameUploaded string = "uploaded_by_mypackbot"
var Version = semver.MustParse("2.0.0")
var (
BucketPhotos = []byte("photos")
BucketStickers = []byte("stickers")
BucketUsers = []byte("users")
BucketUsersPhotos = []byte("users_photos")
BucketUsersStickers = []byte("users_stickers")
Buckets = [...][]byte{
BucketPhotos,
BucketStickers,
BucketUsers,
BucketUsersPhotos,
BucketUsersStickers,
}
)

View File

@ -0,0 +1,25 @@
package config
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOpen(t *testing.T) {
rootPath, err := os.Getwd()
assert.NoError(t, err)
t.Run("invalid", func(t *testing.T) {
cfg, err := Open(filepath.Join("/", "invalid", "directory"))
assert.Error(t, err)
assert.Nil(t, cfg)
})
t.Run("valid", func(t *testing.T) {
cfg, err := Open(filepath.Join(rootPath, "..", "..", "configs", "config.example.yaml"))
assert.NoError(t, err)
assert.NotNil(t, cfg)
})
}

View File

@ -1,17 +1,35 @@
package config
import "github.com/spf13/viper"
import (
"errors"
"path/filepath"
var Config *viper.Viper
"github.com/spf13/viper"
)
type Reader interface {
GetString(string) string
GetInt64(string) int64
}
// Open just open configuration file for parsing some data in other functions
func Open(path string) (*viper.Viper, error) {
cfg := viper.New()
dir, file := filepath.Split(path)
ext := filepath.Ext(file)
cfg.AddConfigPath(path)
cfg.SetConfigName("config")
cfg.SetConfigType("yaml")
if file == "" || ext == "" {
return nil, errors.New("invalid path to config file")
}
err := cfg.ReadInConfig()
return cfg, err
fileExt := ext[1:]
fileName := file[:(len(file)-len(fileExt))-1]
v := viper.New()
v.AddConfigPath(dir)
v.SetConfigName(fileName)
v.SetConfigType(fileExt)
err := v.ReadInConfig()
return v, err
}

View File

@ -1,36 +0,0 @@
package db
import (
"fmt"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// AddSticker add sticker FileID, Emoji and SetName meta for UserID
func (db *DataBase) AddSticker(user *tg.User, sticker *tg.Sticker) (bool, error) {
log.Ln("Trying to add", sticker.FileID, "sticker from", user.ID, "user")
if sticker.SetName == "" {
sticker.SetName = models.SetUploaded
}
var exists bool
err := db.Update(func(tx *buntdb.Tx) error {
var err error
_, exists, err = tx.Set(
fmt.Sprint("user:", user.ID, ":set:", sticker.SetName, ":sticker:", sticker.FileID), // key
sticker.Emoji, // value
nil, // options
)
if err == buntdb.ErrIndexExists {
exists = true
return nil
}
return err
})
return exists, err
}

View File

@ -1,18 +0,0 @@
package db
import (
"fmt"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
tg "gitlab.com/toby3d/telegram"
)
// ChangeUserState change current user state on input state.
func (db *DataBase) ChangeUserState(user *tg.User, state string) error {
log.Ln("Trying to change", user.ID, "state to", state)
return db.Update(func(tx *buntdb.Tx) error {
_, _, err := tx.Set(fmt.Sprint("user:", user.ID, ":state"), state, nil)
return err
})
}

30
internal/db/db.go Normal file
View File

@ -0,0 +1,30 @@
package db
import (
"os"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
bolt "go.etcd.io/bbolt"
)
func Open(path string) (*bolthold.Store, error) {
db, err := bolthold.Open(path, os.ModePerm, nil)
if err != nil {
return nil, err
}
err = db.Bolt().Update(func(tx *bolt.Tx) error {
for i := range common.Buckets {
if _, err := tx.CreateBucketIfNotExists(common.Buckets[i]); err == nil {
continue
}
return err
}
return nil
})
return db, err
}

30
internal/db/db_test.go Normal file
View File

@ -0,0 +1,30 @@
package db
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOpen(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
db, err := Open(filepath.Join("/", "invalid", "path"))
assert.Error(t, err)
assert.Nil(t, db)
})
t.Run("valid", func(t *testing.T) {
rootPath, err := os.Getwd()
assert.NoError(t, err)
testPath := filepath.Join(rootPath, "..", "..", "test", "testing.db")
db, err := Open(testPath)
assert.NoError(t, err)
assert.NotNil(t, db)
defer func() {
assert.NoError(t, db.Close())
assert.NoError(t, os.Remove(testPath))
}()
})
}

View File

@ -1,50 +0,0 @@
package db
import (
"fmt"
"strings"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// DeletePack remove all keys for UserID which contains input SetName
func (db *DataBase) DeletePack(user *tg.User, sticker *tg.Sticker) (bool, error) {
log.Ln("Trying to remove all", sticker.SetName, "sticker from", user.ID, "user")
if sticker.SetName == "" {
sticker.SetName = models.SetUploaded
}
var ids []string
err := db.View(func(tx *buntdb.Tx) error {
return tx.AscendKeys(
fmt.Sprint("user:", user.ID, ":set:", sticker.SetName, ":*"),
func(key, val string) bool {
keys := strings.Split(key, ":")
ids = append(ids, keys[5])
return true
},
)
})
if len(ids) == 0 {
return true, nil
}
for _, id := range ids {
var notExist bool
notExist, err = db.DeleteSticker(user, &tg.Sticker{FileID: id})
if err != nil {
return notExist, err
}
}
if err == buntdb.ErrNotFound {
log.Ln(user.ID, "not found")
return true, nil
}
return false, err
}

View File

@ -1,31 +0,0 @@
package db
import (
"fmt"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// DeleteSticker just remove specified sticker key from database.
func (db *DataBase) DeleteSticker(user *tg.User, sticker *tg.Sticker) (bool, error) {
log.Ln("Trying to remove", sticker.FileID, "sticker from", user.ID, "user")
if sticker.SetName == "" {
sticker.SetName = models.SetUploaded
}
err := db.Update(func(tx *buntdb.Tx) error {
_, err := tx.Delete(
fmt.Sprint("user:", user.ID, ":set:", sticker.SetName, ":sticker:", sticker.FileID),
)
return err
})
if err == buntdb.ErrNotFound {
log.Ln(user.ID, "not found, create new one")
return true, nil
}
return false, err
}

View File

@ -1,29 +0,0 @@
package db
import (
"fmt"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// UserState return current state for UserID
func (db *DataBase) GetUserState(user *tg.User) (string, error) {
log.Ln("Trying to get", user.ID, "state")
var state string
err := db.View(func(tx *buntdb.Tx) error {
var err error
state, err = tx.Get(fmt.Sprint("user:", user.ID, ":state"))
return err
})
if err == buntdb.ErrNotFound {
log.Ln(user.ID, "not found, create new one")
if err = db.ChangeUserState(user, models.StateNone); err != nil {
return state, err
}
}
return state, err
}

View File

@ -1,55 +0,0 @@
package db
import (
"fmt"
"strconv"
"strings"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
tg "gitlab.com/toby3d/telegram"
)
// GetUserStickers return array of saved stickers for input UserID and his total count
func (db *DataBase) GetUserStickers(user *tg.User, query *tg.InlineQuery) ([]string, error) {
log.Ln("Trying to get", user.ID, "stickers")
var i int
var stickers []string
offset, _ := strconv.Atoi(query.Offset)
offset *= 50
err := db.View(func(tx *buntdb.Tx) error {
return tx.AscendKeys(
fmt.Sprint("user:", user.ID, ":set:*"), // index
func(key, val string) bool { // iterator
subKeys := strings.Split(key, ":")
if subKeys[1] != strconv.Itoa(user.ID) {
return true
}
if len(stickers) == 50 {
return false
}
i++
if i < offset {
return true
}
if query.Query != "" && !strings.Contains(query.Query, val) {
return true
}
stickers = append(stickers, subKeys[5])
return true
},
)
})
if err == buntdb.ErrNotFound {
log.Ln("Not found stickers")
return nil, nil
}
return stickers, err
}

View File

@ -1,33 +0,0 @@
package db
import (
"strconv"
"strings"
// log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
)
// GetUsers return array of all available UserID in database
func (db *DataBase) GetUsers() ([]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
}

View File

@ -1,18 +0,0 @@
package db
import (
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
)
type DataBase struct{ *buntdb.DB }
// DB is a main object of current database connection
var DB *DataBase
// Open just open connection to database for work
func Open(path string) (*DataBase, error) {
log.Ln("Open database file...")
db, err := buntdb.Open(path)
return &DataBase{db}, err
}

View File

@ -1,40 +0,0 @@
package db
import (
"fmt"
"strconv"
"strings"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
tg "gitlab.com/toby3d/telegram"
)
// ResetUser just drop out all stickers keys for input UserID
func (db *DataBase) ResetUser(user *tg.User) error {
log.Ln("Trying reset all stickers of", user.ID, "user")
return db.Update(func(tx *buntdb.Tx) error {
var keys []string
if err := tx.AscendKeys(
fmt.Sprint("user:", user.ID, ":set:*"), // index
func(key, val string) bool { // iterator
subKeys := strings.Split(key, ":")
if subKeys[1] == strconv.Itoa(user.ID) {
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
})
}

View File

@ -1,30 +0,0 @@
package db
import (
"fmt"
log "github.com/kirillDanshin/dlog"
"github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// UserState return current state for UserID
func (db *DataBase) UserState(usr *tg.User) (string, error) {
log.Ln("Trying to get", usr.ID, "state")
var state string
err := DB.View(func(tx *buntdb.Tx) error {
var err error
state, err = tx.Get(fmt.Sprint("user:", usr.ID, ":state"))
return err
})
if err == buntdb.ErrNotFound {
log.Ln(usr.ID, "not found, create new one")
if err = db.ChangeUserState(usr, models.StateNone); err != nil {
return state, err
}
}
return state, err
}

View File

@ -1,32 +0,0 @@
package errors
import (
"log"
"log/syslog"
"os"
"sync"
)
var (
// WaitForwards is a wait group which wait send all announcements before panic
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()
err = sysLogger.Crit(err.Error())
if err != nil {
log.Panicln(err.Error())
}
os.Exit(1)
}
}

View File

@ -0,0 +1,110 @@
package handler
import (
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
)
func (h *Handler) IsCallbackQuery(ctx *model.Context) (err error) {
if !ctx.Request.IsCallbackQuery() {
return nil
}
switch ctx.Request.CallbackQuery.Data {
case common.DataAdd, common.DataAddSet:
err = h.CallbackAdd(ctx)
case common.DataDel, common.DataDelSet:
err = h.CallbackDel(ctx)
}
if err != nil {
return err
}
_, err = ctx.AnswerCallbackQuery(tg.NewAnswerCallback(ctx.Request.CallbackQuery.ID))
return err
}
func (h *Handler) CallbackAdd(ctx *model.Context) (err error) {
if !ctx.Request.IsCallbackQuery() {
return err
}
editMessage := tg.EditMessageReplyMarkup{
ChatID: ctx.Request.CallbackQuery.Message.Chat.ID,
InlineMessageID: ctx.Request.CallbackQuery.InlineMessageID,
MessageID: ctx.Request.CallbackQuery.Message.ID,
}
switch {
case ctx.Photo != nil:
if err = h.CommandAddPhoto(ctx); err != nil {
return err
}
editMessage.ReplyMarkup = h.GetPhotoKeyboard(ctx)
case ctx.Sticker != nil:
if ctx.Request.CallbackQuery.Data == common.DataAddSet {
err = h.CommandAddSet(ctx)
ctx.HasSet = true
} else {
err = h.CommandAddSticker(ctx)
}
if err != nil {
return err
}
ctx.HasSticker = true
editMessage.ReplyMarkup = h.GetStickerKeyboard(ctx)
default:
return err
}
_, err = ctx.EditMessageReplyMarkup(editMessage)
return err
}
func (h *Handler) CallbackDel(ctx *model.Context) (err error) {
if !ctx.Request.IsCallbackQuery() {
return err
}
editMessage := tg.EditMessageReplyMarkup{
ChatID: ctx.Request.CallbackQuery.Message.Chat.ID,
InlineMessageID: ctx.Request.CallbackQuery.InlineMessageID,
MessageID: ctx.Request.CallbackQuery.Message.ID,
}
switch {
case ctx.Photo != nil:
if err = h.CommandDelPhoto(ctx); err != nil {
return err
}
editMessage.ReplyMarkup = h.GetPhotoKeyboard(ctx)
case ctx.Sticker != nil:
if ctx.Request.CallbackQuery.Data == common.DataDelSet {
err = h.CommandDelSet(ctx)
ctx.HasSet = false
} else {
err = h.CommandDelSticker(ctx)
}
if err != nil {
return err
}
ctx.HasSticker = false
editMessage.ReplyMarkup = h.GetStickerKeyboard(ctx)
default:
return err
}
_, err = ctx.EditMessageReplyMarkup(editMessage)
return err
}

200
internal/handler/command.go Normal file
View File

@ -0,0 +1,200 @@
package handler
import (
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
// IsCommand defines actions for commands only
func (h *Handler) IsCommand(ctx *model.Context) (err error) {
if !ctx.Request.Message.IsCommand() {
return nil
}
switch ctx.Request.Message.Command() {
case tg.CommandStart:
err = h.CommandStart(ctx)
case tg.CommandHelp:
err = h.CommandHelp(ctx)
case "ping":
err = h.CommandPing(ctx)
case "add":
err = h.CommandAdd(ctx)
case "del":
err = h.CommandDel(ctx)
case "edit":
err = h.CommandEdit(ctx)
case "addsticker":
err = h.CommandAddSticker(ctx)
case "addpack":
err = h.CommandAddSet(ctx)
case "delsticker":
err = h.CommandDelSticker(ctx)
case "delpack":
err = h.CommandDelSet(ctx)
case "reset", "cancel", tg.CommandSettings:
}
return err
}
// CommandPing send common ping message.
func (h *Handler) CommandPing(ctx *model.Context) (err error) {
reply := tg.NewMessage(int64(ctx.User.ID), "🏓")
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
// CommandStart send common welcome message.
// NOTE(toby3d): REQUIRED by Telegram Bot API platform
func (h *Handler) CommandStart(ctx *model.Context) (err error) {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("👋 Hi %s, I'm %s!\nThanks to me, you can collect almost any"+
" media content in Telegram without any limits, in any chat via inline mode.",
ctx.Request.Message.From.FullName(), ctx.FullName()))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
// CommandHelp send common message with list of available commands
// NOTE(toby3d): REQUIRED by Telegram Bot API platform
func (h *Handler) CommandHelp(ctx *model.Context) (err error) {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("🤖 Here is a list of commands that I understand, some of"+
" them [may] or (should) contain an argument:\n/start - start all over again\n/help [other command] "+
"- get a list of available commands or help and a demonstration of a specific command\n/add [query] "+
"- add media from reply to your collection [with custom search query]\n/edit (query) - change query "+
"to reply media\n/del - remove reply media from your collection"))
if !ctx.Request.Message.HasCommandArgument() {
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
switch ctx.Request.Message.CommandArgument() {
case "add":
reply.Text = p.Sprintf("💡 Use the /add command as a reply to the sticker/photo to add this media to " +
"your collection feed. Given an argument, the result of this command will be equivalent to " +
"the /edit command.")
case "edit":
reply.Text = p.Sprintf("💡 Use the /edit command with an argument from any character set as a reply " +
"to a sticker/photo to change the search query of this media in the feed of your collection." +
" If this media is not in the feed, then the result of this command will be equivalent to " +
"the /add command with the same argument.")
case "del":
reply.Text = p.Sprintf("💡 Use /del command as an reply to the sticker/photo to remove it from the " +
"feed of your collection.")
default: // NOTE(toby3d): do nothing
return nil
}
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
// CommandSettings send common message with settings buttons
// NOTE(toby3d): REQUIRED by Telegram Bot API platform
func (h *Handler) CommandSettings(ctx *model.Context) (err error) {
return nil
}
// CommandUnknown reply common error message to any unkwnon commands.
func (h *Handler) CommandUnknown(ctx *model.Context) (err error) {
return nil
}
func (h *Handler) CommandAdd(ctx *model.Context) (err error) {
switch {
case ctx.Photo != nil:
err = h.CommandAddPhoto(ctx)
case ctx.Sticker != nil:
err = h.CommandAddSticker(ctx)
default:
return nil
}
if err != nil {
return err
}
if !ctx.Request.Message.HasCommandArgument() {
return err
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("👍 Imported!"))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
func (h *Handler) CommandEdit(ctx *model.Context) (err error) {
switch {
case ctx.Photo != nil:
err = h.CommandEditPhoto(ctx)
case ctx.Sticker != nil:
err = h.CommandEditSticker(ctx)
default:
return nil
}
if err != nil {
return err
}
if !ctx.Request.Message.HasCommandArgument() {
return err
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("👍 Updated!"))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
func (h *Handler) CommandDel(ctx *model.Context) (err error) {
switch {
case ctx.Photo != nil:
err = h.CommandDelPhoto(ctx)
case ctx.Sticker != nil:
err = h.CommandDelSticker(ctx)
default:
return nil
}
if err != nil {
return err
}
if !ctx.Request.Message.HasCommandArgument() {
return err
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("👍 Removed!"))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}

View File

@ -0,0 +1,45 @@
package handler
import (
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/photos"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
"gitlab.com/toby3d/mypackbot/internal/model/users"
up "gitlab.com/toby3d/mypackbot/internal/model/users/photos"
us "gitlab.com/toby3d/mypackbot/internal/model/users/stickers"
"gitlab.com/toby3d/mypackbot/internal/store"
)
type Handler struct {
users users.ReadWriter
stickers stickers.ReadWriter
photos photos.ReadWriter
usersStickers us.ReadWriter
usersPhotos up.ReadWriter
store *store.Store
}
func NewHandler(us users.ReadWriter, ss stickers.ReadWriter, ps photos.ReadWriter, uss us.ReadWriter,
ups up.ReadWriter) *Handler {
return &Handler{
photos: ps,
stickers: ss,
users: us,
usersPhotos: ups,
usersStickers: uss,
store: store.NewStore(uss, ups),
}
}
func (h *Handler) UpdateHandler(ctx *model.Context) (err error) {
switch {
case ctx.Request.IsMessage():
err = h.IsMessage(ctx)
case ctx.Request.IsCallbackQuery():
err = h.IsCallbackQuery(ctx)
case ctx.Request.IsInlineQuery():
err = h.IsInlineQuery(ctx)
}
return err
}

View File

@ -0,0 +1,96 @@
package handler
import (
"strconv"
"strings"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/store"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
const defaultLimit int = 50
func (h *Handler) IsInlineQuery(ctx *model.Context) (err error) {
answer := tg.NewAnswerInline(ctx.Request.InlineQuery.ID)
answer.CacheTime = 1 // NOTE(toby3d): add setting for change this
filter := getFilter(ctx.Request.InlineQuery)
if answer.IsPersonal = !strings.Contains(ctx.Request.InlineQuery.Query, "personal:false"); answer.IsPersonal {
filter.UserID = ctx.User.ID
}
results, count, _ := h.store.GetList(filter.Offset, filter.Limit, filter)
if filter.Offset+filter.Limit < count {
answer.NextOffset = strconv.Itoa(filter.Offset + filter.Limit)
}
for i := range results {
switch results[i].GetType() {
case tg.TypeSticker:
answer.Results = append(answer.Results, tg.NewInlineQueryResultCachedSticker(
tg.TypeSticker+common.DataSeparator+results[i].GetID(), results[i].GetFileID(),
))
case tg.TypePhoto:
answer.Results = append(answer.Results, tg.NewInlineQueryResultCachedPhoto(
tg.TypePhoto+common.DataSeparator+results[i].GetID(), results[i].GetFileID(),
))
}
}
p := ctx.Get("printer").(*message.Printer)
answer.SwitchPrivateMessageParameter = "inline"
answer.SwitchPrivateMessageText = p.Sprintf("🕵 Found %d result(s)", count)
_, err = ctx.AnswerInlineQuery(answer)
return err
}
func getFilter(iq *tg.InlineQuery) *store.Filter {
f := new(store.Filter)
f.Limit = defaultLimit
f.Offset, _ = strconv.Atoi(iq.Offset)
if !strings.Contains(iq.Query, "photos:false") {
f.AllowedTypes = append(f.AllowedTypes, tg.TypePhoto)
}
if !strings.Contains(iq.Query, "stickers:false") {
f.AllowedTypes = append(f.AllowedTypes, tg.TypeSticker)
}
if !iq.HasQuery() {
return f
}
f.Query, _ = utils.FixEmojiTone(strings.TrimSpace(iq.Query))
for _, field := range strings.Fields(f.Query) {
i := strings.Index(f.Query, field)
switch {
case strings.HasPrefix(field, "offset:"):
f.Offset, _ = strconv.Atoi(strings.TrimPrefix(field, "offset:"))
/*
case strings.HasPrefix(field, "animated:"):
isAnimated, _ := strconv.ParseBool(strings.TrimPrefix(field, "animated:"))
f.IsAnimated = &isAnimated
case strings.HasPrefix(field, "set:"):
f.SetName = strings.TrimPrefix(field, "set:")
*/
case strings.HasPrefix(field, "photos:"), strings.HasPrefix(field, "stickers:"):
default:
continue
}
f.Query = f.Query[:i] + strings.TrimPrefix(f.Query[i:], field)
}
f.Query = strings.ReplaceAll(f.Query, " ", "")
return f
}

View File

@ -0,0 +1,27 @@
package handler
import (
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
func (h *Handler) IsMessage(ctx *model.Context) (err error) {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.Request.Message.Chat.ID, p.Sprintf("🤔 What to do with this?"))
reply.ReplyToMessageID = ctx.Request.Message.ID
switch {
case ctx.Request.Message.IsCommand():
err = h.IsCommand(ctx)
case ctx.Request.Message.IsSticker():
reply.ReplyMarkup = h.GetStickerKeyboard(ctx)
_, err = ctx.SendMessage(reply)
case ctx.Request.Message.IsPhoto():
reply.ReplyMarkup = h.GetPhotoKeyboard(ctx)
_, err = ctx.SendMessage(reply)
}
return err
}

View File

@ -0,0 +1,94 @@
package handler
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
func (h *Handler) GetPhotoKeyboard(ctx *model.Context) *tg.InlineKeyboardMarkup {
if ctx.Photo == nil {
return nil
}
p := ctx.Get("printer").(*message.Printer)
userPhoto := h.usersPhotos.Get(&model.UserPhoto{
UserID: ctx.User.ID,
PhotoID: ctx.Photo.ID,
})
markup := tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton(p.Sprintf("📥 Import this photo"), common.DataAdd),
))
if userPhoto != nil {
markup = tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton(p.Sprintf("🔥 Remove this photo"), common.DataDel),
))
}
return &markup
}
func (h *Handler) CommandAddPhoto(ctx *model.Context) (err error) {
if ctx.Photo == nil || ctx.HasPhoto {
return nil
}
now := time.Now().UTC().Unix()
userPhoto := new(model.UserPhoto)
userPhoto.CreatedAt, userPhoto.UpdatedAt = now, now
userPhoto.UserID = ctx.User.ID
userPhoto.PhotoID = ctx.Photo.ID
if ctx.Request.IsMessage() && ctx.Request.Message.HasCommandArgument() {
userPhoto.Query = ctx.Request.Message.CommandArgument()
}
return h.usersPhotos.Add(userPhoto)
}
func (h *Handler) CommandEditPhoto(ctx *model.Context) (err error) {
if ctx.Photo == nil {
return nil
}
if !ctx.Request.Message.HasCommandArgument() {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("💡 Add any text and/or emoji(s) as an argument "+
"of this command to change its search properties."))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ParseMode = tg.ParseModeMarkdownV2
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
if !ctx.HasPhoto {
if err = h.CommandAddPhoto(ctx); err != nil {
return err
}
}
return h.usersPhotos.Update(&model.UserPhoto{
UserID: ctx.User.ID,
PhotoID: ctx.Photo.ID,
UpdatedAt: ctx.Request.Message.Date,
Query: ctx.Request.Message.CommandArgument(),
})
}
func (h *Handler) CommandDelPhoto(ctx *model.Context) (err error) {
if ctx.Photo == nil || !ctx.HasPhoto {
return nil
}
return h.usersPhotos.Remove(&model.UserPhoto{
UserID: ctx.User.ID,
PhotoID: ctx.Photo.ID,
})
}

View File

@ -0,0 +1,128 @@
package handler
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
func (h *Handler) GetStickerKeyboard(ctx *model.Context) *tg.InlineKeyboardMarkup {
if ctx.Sticker == nil {
return nil
}
p := ctx.Get("printer").(*message.Printer)
markup := tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton("🔥 Remove this sticker", common.DataDel),
))
if (ctx.Request.IsCallbackQuery() && ctx.Request.CallbackQuery.Data == common.DataDelSet) || !ctx.HasSticker {
markup = tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton(p.Sprintf("📥 Import this sticker"), common.DataAdd),
))
}
if ctx.Sticker.InSet() {
setName, _ := ctx.Get("set_name").(string)
markup.InlineKeyboard = append(markup.InlineKeyboard, tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton(p.Sprintf("📥 Import %s set", setName), common.DataAddSet),
))
if ctx.Request.IsCallbackQuery() && ctx.Request.CallbackQuery.Data == common.DataAddSet || ctx.HasSet {
markup.InlineKeyboard[1][0] = tg.NewInlineKeyboardButton(
p.Sprintf("🔥 Remove %s set", setName), common.DataDelSet,
)
}
}
return &markup
}
// CommandAddSticker import single Sticker by ReplyMessage.
// NOTE(toby3d): DEPRECATED, used for backward compatibility
func (h *Handler) CommandAddSticker(ctx *model.Context) (err error) {
if ctx.HasSticker || ctx.Sticker == nil {
return nil
}
now := time.Now().UTC().Unix()
userSticker := new(model.UserSticker)
userSticker.CreatedAt, userSticker.UpdatedAt = now, now
userSticker.Query = ctx.Sticker.Emoji
userSticker.StickerID = ctx.Sticker.ID
userSticker.UserID = ctx.User.ID
if ctx.Request.IsMessage() && ctx.Request.Message.HasCommandArgument() {
userSticker.Query = ctx.Request.Message.CommandArgument()
}
return h.usersStickers.Add(userSticker)
}
// CommandAddPack import whole Sticker pack by ReplyMessage.
// NOTE(toby3d): DEPRECATED, used for backward compatibility
func (h *Handler) CommandAddSet(ctx *model.Context) (err error) {
if ctx.Sticker == nil || !ctx.Sticker.InSet() {
return nil
}
return h.usersStickers.AddSet(ctx.User.ID, ctx.Sticker.SetName)
}
func (h *Handler) CommandEditSticker(ctx *model.Context) (err error) {
if ctx.Sticker == nil {
return nil
}
if !ctx.Request.Message.HasCommandArgument() {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(int64(ctx.User.ID), p.Sprintf("💡 Add any text and/or emoji(s) as an "+
"argument of this command to change its search properties."))
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ParseMode = tg.ParseModeMarkdownV2
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
return err
}
if !ctx.HasSticker {
if err = h.CommandAddSticker(ctx); err != nil {
return err
}
}
return h.usersStickers.Update(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: ctx.Sticker.ID,
UpdatedAt: ctx.Request.Message.Date,
Query: ctx.Request.Message.CommandArgument(),
})
}
// CommandDelSticker remove single Sticker by ReplyMessage.
// NOTE(toby3d): DEPRECATED, used for backward compatibility
func (h *Handler) CommandDelSticker(ctx *model.Context) (err error) {
if ctx.Sticker == nil || !ctx.HasSticker {
return nil
}
return h.usersStickers.Remove(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: ctx.Sticker.ID,
})
}
// CommandDelPack remove whole Sticker pack by ReplyMessage.
// NOTE(toby3d): DEPRECATED, used for backward compatibility
func (h *Handler) CommandDelSet(ctx *model.Context) (err error) {
if ctx.Sticker == nil || !ctx.Sticker.InSet() {
return nil
}
return h.usersStickers.RemoveSet(ctx.User.ID, ctx.Sticker.SetName)
}

View File

@ -1,20 +0,0 @@
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)
})
}

View File

@ -1,13 +0,0 @@
package i18n
import (
"github.com/nicksnyder/go-i18n/i18n"
"gitlab.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
}

View File

@ -1,35 +0,0 @@
package messages
import (
"strings"
"gitlab.com/toby3d/mypackbot/internal/actions"
"gitlab.com/toby3d/mypackbot/internal/commands"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
tg "gitlab.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)
}
}

View File

@ -0,0 +1,55 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/photos"
tg "gitlab.com/toby3d/telegram"
)
func AcquirePhoto(store photos.ReadWriter) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
switch {
case ctx.Request.IsMessage():
switch {
case ctx.Request.Message.IsPhoto():
ctx.Photo = photoToModel(ctx.Request.Message.Photo)
ctx.Photo.CreatedAt = ctx.Request.Message.Date
ctx.Photo.UpdatedAt = ctx.Request.Message.Date
case ctx.Request.Message.IsReply() && ctx.Request.Message.ReplyToMessage.IsPhoto():
ctx.Photo = photoToModel(ctx.Request.Message.ReplyToMessage.Photo)
ctx.Photo.CreatedAt = ctx.Request.Message.Date
ctx.Photo.UpdatedAt = ctx.Request.Message.Date
default:
return next(ctx)
}
case ctx.Request.IsCallbackQuery():
if !ctx.Request.CallbackQuery.Message.IsReply() ||
!ctx.Request.CallbackQuery.Message.ReplyToMessage.IsPhoto() {
return next(ctx)
}
ctx.Photo = photoToModel(ctx.Request.CallbackQuery.Message.ReplyToMessage.Photo)
ctx.Photo.CreatedAt = ctx.Request.CallbackQuery.Message.ReplyToMessage.Date
ctx.Photo.UpdatedAt = ctx.Request.CallbackQuery.Message.ReplyToMessage.Date
default:
return next(ctx)
}
if ctx.Photo, err = store.GetOrCreate(ctx.Photo); err != nil {
return err
}
return next(ctx)
}
}
func photoToModel(photoSize tg.Photo) *model.Photo {
p := photoSize[len(photoSize)-1]
photo := new(model.Photo)
photo.ID = p.FileUniqueID
photo.Width = p.Width
photo.Height = p.Height
photo.FileID = p.FileID
return photo
}

View File

@ -0,0 +1,24 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func AcquirePrinter() Interceptor {
matcher := language.NewMatcher(message.DefaultCatalog.Languages())
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
tag, err := language.Parse(ctx.User.LanguageCode)
if err != nil {
tag = language.English
}
tag, _, _ = matcher.Match(tag)
p := message.NewPrinter(tag)
ctx.Set("printer", p)
return next(ctx)
}
}

View File

@ -0,0 +1,100 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
tg "gitlab.com/toby3d/telegram"
)
func AcquireSticker(store stickers.Manager) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
switch {
case ctx.Request.IsMessage():
switch {
case ctx.Request.Message.IsSticker():
ctx.Sticker = stickerToModel(ctx.Request.Message.Sticker)
ctx.Sticker.CreatedAt = ctx.Request.Message.Date
ctx.Sticker.UpdatedAt = ctx.Request.Message.Date
case ctx.Request.Message.IsReply() && ctx.Request.Message.ReplyToMessage.IsSticker():
ctx.Sticker = stickerToModel(ctx.Request.Message.ReplyToMessage.Sticker)
ctx.Sticker.CreatedAt = ctx.Request.Message.Date
ctx.Sticker.UpdatedAt = ctx.Request.Message.Date
default:
return next(ctx)
}
case ctx.Request.IsCallbackQuery():
if !ctx.Request.CallbackQuery.Message.IsReply() ||
!ctx.Request.CallbackQuery.Message.ReplyToMessage.IsSticker() {
return next(ctx)
}
ctx.Sticker = stickerToModel(ctx.Request.CallbackQuery.Message.ReplyToMessage.Sticker)
ctx.Sticker.CreatedAt = ctx.Request.CallbackQuery.Message.ReplyToMessage.Date
ctx.Sticker.UpdatedAt = ctx.Request.CallbackQuery.Message.ReplyToMessage.Date
default:
return next(ctx)
}
if ctx.Sticker.InSet() {
migrateSet(ctx, store)
}
if ctx.Sticker, err = store.GetOrCreate(ctx.Sticker); err != nil {
return err
}
return next(ctx)
}
}
func stickerToModel(s *tg.Sticker) *model.Sticker {
sticker := new(model.Sticker)
sticker.ID = s.FileUniqueID
sticker.Emoji = s.Emoji
sticker.Width = s.Width
sticker.Height = s.Height
sticker.IsAnimated = s.IsAnimated
sticker.SetName = s.SetName
sticker.FileID = s.FileID
if !sticker.InSet() {
sticker.SetName = common.SetNameUploaded
}
return sticker
}
func migrateSet(ctx *model.Context, store stickers.Manager) {
tgSet, err := ctx.GetStickerSet(ctx.Sticker.SetName)
if err != nil || tgSet == nil || len(tgSet.Stickers) == 0 {
stickers, _, _ := store.GetList(0, 0, &model.Sticker{SetName: ctx.Sticker.SetName})
ctx.Sticker.SetName = common.SetNameUploaded
go func() {
for i := range stickers {
stickers[i].SetName = ctx.Sticker.SetName
stickers[i].UpdatedAt = ctx.Sticker.UpdatedAt
_ = store.Update(stickers[i])
}
}()
} else {
ctx.Set("set_name", tgSet.Title)
for i := range tgSet.Stickers {
for _, sticker := range store.GetSet(tgSet.Name) {
if sticker.ID == tgSet.Stickers[i].FileUniqueID {
continue
}
now := time.Now().UTC().Unix()
s := stickerToModel(tgSet.Stickers[i])
s.CreatedAt, s.UpdatedAt = now, now
_, _ = store.GetOrCreate(s)
}
}
}
}

View File

@ -0,0 +1,42 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/users"
)
func AcquireUser(us users.ReadWriter) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
ctx.User = new(model.User)
switch {
case ctx.Request.IsMessage():
ctx.User.ID = ctx.Request.Message.From.ID
ctx.User.CreatedAt = ctx.Request.Message.Date
ctx.User.UpdatedAt = ctx.Request.Message.Date
ctx.User.LanguageCode = ctx.Request.Message.From.LanguageCode
ctx.User.LastSeen = ctx.Request.Message.Date
case ctx.Request.IsInlineQuery():
now := time.Now().UTC().Unix()
ctx.User.ID = ctx.Request.InlineQuery.From.ID
ctx.User.CreatedAt = now
ctx.User.UpdatedAt = now
ctx.User.LanguageCode = ctx.Request.InlineQuery.From.LanguageCode
ctx.User.LastSeen = now
case ctx.Request.IsCallbackQuery():
ctx.User.ID = ctx.Request.CallbackQuery.From.ID
ctx.User.CreatedAt = ctx.Request.CallbackQuery.Message.Date
ctx.User.UpdatedAt = ctx.Request.CallbackQuery.Message.Date
ctx.User.LanguageCode = ctx.Request.CallbackQuery.From.LanguageCode
ctx.User.LastSeen = ctx.Request.CallbackQuery.Message.Date
}
if ctx.User, err = us.GetOrCreate(ctx.User); err != nil {
return err
}
return next(ctx)
}
}

View File

@ -0,0 +1,21 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/users/photos"
)
func AcquireUserPhoto(store photos.Reader) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) error {
if ctx.Photo == nil {
return next(ctx)
}
ctx.HasPhoto = store.Get(&model.UserPhoto{
UserID: ctx.User.ID,
PhotoID: ctx.Photo.ID,
}) != nil
return next(ctx)
}
}

View File

@ -0,0 +1,21 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/users/stickers"
)
func AcquireUserSticker(store stickers.Reader) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) error {
if ctx.Sticker == nil {
return next(ctx)
}
ctx.HasSticker = store.Get(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: ctx.Sticker.ID,
}) != nil
return next(ctx)
}
}

View File

@ -0,0 +1,56 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
func Birthday(bday time.Time) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
if !ctx.Request.IsMessage() {
return next(ctx)
}
lastSeen := time.Unix(ctx.User.LastSeen, 0)
date := ctx.Request.Message.Time()
before := time.Date(date.Year(), bday.Month(), bday.Day(), 0, 0, 0, 0, time.UTC)
after := before.AddDate(0, 0, 7)
if date.Before(before) || date.After(after) || lastSeen.After(before) {
return next(ctx)
}
// NOTE(toby3d): do this middleware only after sending all previous messages
if err = next(ctx); err != nil {
return err
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.Request.Message.Chat.ID, p.Sprintf("🥳 4 November? It's a @toby3d birthday!"+
"\nIf you like this bot, then why not send him a congratulation along with a small gift? This"+
" will make him incredibly happy!"))
if date.After(bday.AddDate(0, 0, 1)) {
reply.Text = p.Sprintf("☺️ Oh, you missed @toby3d birthday on November 4th!\nIf you like this" +
" bot, why not send him some birthday greetings and a little birthday gift? It is " +
"not yet too late to make him happy!")
}
reply.DisableNotification = false
reply.DisableWebPagePreview = false
reply.ParseMode = tg.ParseModeMarkdownV2
reply.ReplyMarkup = tg.NewInlineKeyboardMarkup(
tg.NewInlineKeyboardRow(tg.NewInlineKeyboardButtonURL(
p.Sprintf("💸 Make a donation"), "https://toby3d.me/donate",
)), tg.NewInlineKeyboardRow(tg.NewInlineKeyboardButtonURL(
p.Sprintf("🤝 Use referral links"), "https://toby3d.me/referrals",
)),
)
if _, err = ctx.SendMessage(reply); err != nil {
return err
}
return next(ctx)
}
}

View File

@ -0,0 +1,17 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
)
func ChatAction() Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
if !ctx.Request.IsMessage() || !ctx.Request.Message.Chat.IsPrivate() {
return next(ctx)
}
go func() { _, _ = ctx.SendChatAction(ctx.Request.Message.Chat.ID, tg.ActionTyping) }()
return next(ctx)
}
}

View File

@ -0,0 +1,49 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
"golang.org/x/text/message"
)
func Hacktober() Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
if !ctx.Request.IsMessage() {
return next(ctx)
}
lastSeen := time.Unix(ctx.User.LastSeen, 0)
date := ctx.Request.Message.Time()
before := time.Date(date.Year(), time.October, 1, 0, 0, 0, 0, time.UTC)
// NOTE(toby3d): not November 1, use October 31
after := before.AddDate(0, 1, 0).Add(-1 * 24 * time.Hour)
if date.Before(before) || date.After(after) || lastSeen.After(before) {
return next(ctx)
}
// NOTE(toby3d): do this middleware only after sending all previous messages
if err = next(ctx); err != nil {
return err
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.Request.Message.Chat.ID, p.Sprintf("🕺 HacktoberFest is here!\n\nIf you are"+
" a beginner or already an experienced golang-developer, now is a great time to help improve"+
" the quality of the code of this bot. Choose issue to your taste and offer your PR!"))
reply.DisableNotification = false
reply.DisableWebPagePreview = false
reply.ParseMode = tg.ParseModeMarkdownV2
reply.ReplyMarkup = tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(tg.NewInlineKeyboardButtonURL(
p.Sprintf("🔧 Let's hack!"),
"https://gitlab.com/toby3d/mypackbot/issues?label_name%5B%5D=hacktoberfest",
)))
if _, err = ctx.SendMessage(reply); err != nil {
return err
}
return next(ctx)
}
}

View File

@ -0,0 +1,26 @@
package middleware
import (
"gitlab.com/toby3d/mypackbot/internal/model"
)
type (
Interceptor func(*model.Context, model.UpdateFunc) error
UpdateHandler model.UpdateFunc
Chain []Interceptor
)
func (count UpdateHandler) Intercept(middleware Interceptor) UpdateHandler {
return func(ctx *model.Context) error { return middleware(ctx, model.UpdateFunc(count)) }
}
func (chain Chain) UpdateHandler(handler model.UpdateFunc) model.UpdateFunc {
current := UpdateHandler(handler)
for i := len(chain) - 1; i >= 0; i-- {
m := chain[i]
current = current.Intercept(m)
}
return model.UpdateFunc(current)
}

View File

@ -0,0 +1,32 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/users"
)
func UpdateLastSeen(us users.Manager) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
timeStamp := time.Now().UTC().Unix()
switch {
case ctx.Request.IsMessage():
timeStamp = ctx.Request.Message.Date
case ctx.Request.IsCallbackQuery():
timeStamp = ctx.Request.CallbackQuery.Message.Date
}
if time.Unix(ctx.User.LastSeen, 0).After(time.Unix(timeStamp, 0).Add(-1 * time.Hour)) {
return next(ctx)
}
ctx.User.LastSeen = timeStamp
if err = us.Update(ctx.User); err != nil {
return err
}
return next(ctx)
}
}

View File

@ -0,0 +1,333 @@
package migrator
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
json "github.com/json-iterator/go"
bunt "github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
"gitlab.com/toby3d/mypackbot/internal/model/users"
usersstickers "gitlab.com/toby3d/mypackbot/internal/model/users/stickers"
tg "gitlab.com/toby3d/telegram"
)
type (
Data struct {
Records Records
*Backup
}
Record struct {
UserID int
SetName string
FileID string
Emoji string
}
Records []*Record
Backup struct {
Users []int `json:"users"`
Stickers []string `json:"stickers"`
ImportedSets []string `json:"imported_sets"`
BlockedSets []string `json:"blocked_sets"`
}
AutoMigrateConfig struct {
OldDB *bunt.DB
Stickers stickers.Manager
UsersStickers usersstickers.ReadWriter
Users users.Manager
Bot *tg.Bot
GroupID int64
Marshler json.API
}
)
const (
partSet string = "set"
partSticker string = "sticker"
uploadedSetName string = "?"
)
func AutoMigrate(cfg AutoMigrateConfig) (err error) {
// NOTE(toby3d): preparing temp-stores for migrating
data, err := cfg.importOldData()
if err != nil {
return err
}
// NOTE(toby3d): STEP 1: migrate users
if err = cfg.migrateUsers(data); err != nil {
return err
}
// NOTE(toby3d): STEP 2: migrate sets
if err = cfg.migrateSets(data); err != nil {
return err
}
// NOTE(toby3d): STEP 3: migrate stickers
return cfg.migrateStickers(data)
}
func (cfg *AutoMigrateConfig) importOldData() (data *Data, err error) {
data = new(Data)
if data.Backup, err = cfg.readBackup(); err != nil && !os.IsNotExist(err) {
return nil, err
}
if err = cfg.OldDB.View(func(tx *bunt.Tx) error {
// NOTE(toby3d): read every key in buntdb database
return tx.AscendKeys("user:*", func(key, val string) bool {
r := new(Record)
// NOTE(toby3d): split key name on parts
parts := strings.Split(key, ":")
// NOTE(toby3d): this part always contains user/chat id
var err error
r.UserID, err = strconv.Atoi(parts[1])
if err != nil || r.UserID == 0 || !strings.EqualFold(parts[2], partSet) {
return true
}
switch parts[2] {
case partSet:
r.SetName = parts[3]
r.FileID = parts[5]
case partSticker:
r.SetName = common.SetNameUploaded
r.FileID = parts[3]
default:
return true
}
if containsString(data.ImportedSets, r.SetName) || containsString(data.Stickers, r.FileID) {
return true
}
if containsString(data.BlockedSets, r.SetName) || r.SetName == uploadedSetName {
r.SetName = common.SetNameUploaded
}
r.Emoji = val
data.Records = append(data.Records, r)
return true
})
}); err != nil {
return nil, err
}
sort.Slice(data.Records, func(i, j int) bool {
return data.Records[i].UserID < data.Records[j].UserID ||
data.Records[i].SetName < data.Records[j].SetName ||
data.Records[i].FileID < data.Records[j].FileID
})
if err = cfg.saveBackup(data.Backup); err != nil {
return nil, err
}
return data, nil
}
func (cfg *AutoMigrateConfig) migrateUsers(data *Data) (err error) {
if data.Backup, err = cfg.readBackup(); err != nil && !os.IsNotExist(err) {
return err
}
for i := range data.Records {
if containsInt(data.Users, data.Records[i].UserID) {
continue
}
now := time.Now().UTC().Unix()
_ = cfg.Users.Create(&model.User{
ID: data.Records[i].UserID,
LanguageCode: "en",
CreatedAt: now,
UpdatedAt: now,
})
data.Users = append(data.Users, data.Records[i].UserID)
_ = cfg.saveBackup(data.Backup)
}
return nil
}
func (cfg *AutoMigrateConfig) migrateSets(data *Data) (err error) {
if data.Backup, err = cfg.readBackup(); err != nil && !os.IsNotExist(err) {
return err
}
for i := range data.Records {
if data.Records[i].SetName == uploadedSetName || data.Records[i].SetName == common.SetNameUploaded {
data.Records[i].SetName = common.SetNameUploaded
continue
}
if containsString(data.ImportedSets, data.Records[i].SetName) {
continue
}
if containsString(data.BlockedSets, data.Records[i].SetName) {
data.Records[i].SetName = common.SetNameUploaded
continue
}
set, err := cfg.Bot.GetStickerSet(data.Records[i].SetName)
if err != nil {
data.BlockedSets = append(data.BlockedSets, data.Records[i].SetName)
data.Records[i].SetName = common.SetNameUploaded
_ = cfg.saveBackup(data.Backup)
continue
}
for _, setSticker := range set.Stickers {
setSticker := setSticker
_ = cfg.Stickers.Create(stickerToModel(setSticker))
}
u := cfg.Users.Get(data.Records[i].UserID)
_ = cfg.UsersStickers.AddSet(u.ID, set.Name)
data.ImportedSets = append(data.ImportedSets, set.Name)
_ = cfg.saveBackup(data.Backup)
}
return nil
}
func (cfg *AutoMigrateConfig) migrateStickers(data *Data) (err error) {
if data.Backup, err = cfg.readBackup(); err != nil && !os.IsNotExist(err) {
return err
}
for i := range data.Records {
if data.Records[i].SetName == uploadedSetName {
data.Records[i].SetName = common.SetNameUploaded
}
if data.Records[i].SetName != common.SetNameUploaded {
continue
}
if containsString(data.Stickers, data.Records[i].FileID) {
continue
}
// NOTE(toby3d): send old sticker ID to get new
result, err := cfg.Bot.SendSticker(tg.SendSticker{
ChatID: cfg.GroupID,
DisableNotification: true,
Sticker: &tg.InputFile{ID: data.Records[i].FileID},
})
if err != nil || !result.IsSticker() {
continue
}
s := stickerToModel(result.Sticker)
s.SetName = common.SetNameUploaded
if s.Emoji == "" {
s.Emoji = data.Records[i].Emoji
}
// NOTE(toby3d): store old-new stickers
_ = cfg.Stickers.Create(s)
u := cfg.Users.Get(data.Records[i].UserID)
s = cfg.Stickers.Get(s.FileID)
_ = cfg.UsersStickers.Add(&model.UserSticker{
UserID: u.ID,
StickerID: s.ID,
})
data.Stickers = append(data.Stickers, data.Records[i].FileID)
_ = cfg.saveBackup(data.Backup)
_, _ = cfg.Bot.DeleteMessage(result.Chat.ID, result.ID)
}
return nil
}
func containsInt(src []int, find int) bool {
for i := range src {
if src[i] != find {
continue
}
return true
}
return false
}
func containsString(src []string, find string) bool {
for i := range src {
if src[i] != find {
continue
}
return true
}
return false
}
func stickerToModel(s *tg.Sticker) *model.Sticker {
sticker := new(model.Sticker)
sticker.FileID = s.FileID
sticker.Emoji = s.Emoji
sticker.Width = s.Width
sticker.Height = s.Height
sticker.IsAnimated = s.IsAnimated
sticker.SetName = s.SetName
if !sticker.InSet() {
sticker.SetName = common.SetNameUploaded
}
return sticker
}
func (cfg *AutoMigrateConfig) readBackup() (bkp *Backup, err error) {
bkp = new(Backup)
filePath := filepath.Join(".", "backup.json")
if _, err = os.Stat(filePath); os.IsNotExist(err) {
return nil, err
}
src, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err
}
if err = cfg.Marshler.Unmarshal(src, bkp); err != nil {
return nil, err
}
return bkp, err
}
func (cfg *AutoMigrateConfig) saveBackup(bkt *Backup) (err error) {
src, err := cfg.Marshler.Marshal(bkt)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(".", "buckup.json"), src, 0644)
}

43
internal/model/context.go Normal file
View File

@ -0,0 +1,43 @@
package model
import (
"context"
tg "gitlab.com/toby3d/telegram"
)
type (
UpdateFunc func(*Context) error
Context struct {
*tg.Bot
Request *tg.Update
User *User
Sticker *Sticker
HasSticker bool
HasSet bool
Photo *Photo
HasPhoto bool
userValues context.Context
}
contextKey string
)
func (ctx *Context) Set(key string, val interface{}) {
if ctx.userValues == nil {
ctx.userValues = context.Background()
}
ctx.userValues = context.WithValue(ctx.userValues, contextKey(key), val)
}
func (ctx *Context) Get(key string) interface{} {
if ctx.userValues == nil {
ctx.userValues = context.Background()
}
return ctx.userValues.Value(contextKey(key))
}

30
internal/model/error.go Normal file
View File

@ -0,0 +1,30 @@
package model
import (
"fmt"
"golang.org/x/xerrors"
)
type Error struct {
Message string
Frame xerrors.Frame
}
func (err Error) Error() string {
return fmt.Sprint(err)
}
func (err Error) Format(f fmt.State, c rune) {
xerrors.FormatError(err, f, c)
}
func (err Error) FormatError(p xerrors.Printer) error {
p.Print(err.Message)
if p.Detail() {
err.Frame.Format(p)
}
return nil
}

94
internal/model/model.go Normal file
View File

@ -0,0 +1,94 @@
package model
import (
"strings"
"gitlab.com/toby3d/mypackbot/internal/common"
tg "gitlab.com/toby3d/telegram"
)
type (
User struct {
ID int `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
LanguageCode string
LastSeen int64
}
Users []*User
Sticker struct {
ID string `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
FileID string
Width int
Height int
IsAnimated bool
SetName string
Emoji string
}
Stickers []*Sticker
Photo struct {
ID string `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
FileID string
Width int
Height int
}
Photos []*Photo
UserSticker struct {
ID uint64 `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
UserID int
StickerID string
Query string
}
UserStickers []*UserSticker
UserPhoto struct {
ID uint64 `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
UserID int
PhotoID string
Query string
}
UserPhotos []*UserPhoto
InlineResult interface {
GetType() string
GetID() string
GetFileID() string
GetUpdatedAt() int64
}
)
func (s *Sticker) InSet() bool {
return s.SetName != "" && !strings.EqualFold(s.SetName, common.SetNameUploaded)
}
func (Sticker) GetType() string { return tg.TypeSticker }
func (s Sticker) GetID() string { return s.ID }
func (s Sticker) GetFileID() string { return s.FileID }
func (s Sticker) GetUpdatedAt() int64 { return s.UpdatedAt }
func (Photo) GetType() string { return tg.TypePhoto }
func (p Photo) GetID() string { return p.ID }
func (p Photo) GetFileID() string { return p.FileID }
func (p Photo) GetUpdatedAt() int64 { return p.UpdatedAt }

View File

@ -0,0 +1,26 @@
package photos
import "gitlab.com/toby3d/mypackbot/internal/model"
type (
Manager interface {
Reader
Writer
ReadWriter
}
ReadWriter interface {
GetOrCreate(*model.Photo) (*model.Photo, error)
}
Reader interface {
Get(string) *model.Photo
GetList(int, int, *model.Photo) (model.Photos, int, error)
}
Writer interface {
Create(*model.Photo) error
Update(*model.Photo) error
Remove(string) error
}
)

View File

@ -0,0 +1,27 @@
package stickers
import "gitlab.com/toby3d/mypackbot/internal/model"
type (
Manager interface {
Reader
Writer
ReadWriter
}
ReadWriter interface {
GetOrCreate(s *model.Sticker) (*model.Sticker, error)
}
Reader interface {
Get(id string) *model.Sticker
GetSet(name string) model.Stickers
GetList(offset int, limit int, filter *model.Sticker) (model.Stickers, int, error)
}
Writer interface {
Create(s *model.Sticker) error
Remove(id string) error
Update(s *model.Sticker) error
}
)

View File

@ -0,0 +1,21 @@
package photos
import "gitlab.com/toby3d/mypackbot/internal/model"
type (
ReadWriter interface {
Reader
Writer
}
Reader interface {
Get(up *model.UserPhoto) *model.Photo
GetList(offset int, limit int, filter *model.UserPhoto) (model.Photos, int, error)
}
Writer interface {
Add(up *model.UserPhoto) error
Update(up *model.UserPhoto) error
Remove(up *model.UserPhoto) error
}
)

View File

@ -0,0 +1,23 @@
package stickers
import "gitlab.com/toby3d/mypackbot/internal/model"
type (
ReadWriter interface {
Reader
Writer
}
Reader interface {
Get(up *model.UserSticker) *model.Sticker
GetList(offset int, limit int, filter *model.UserSticker) (model.Stickers, int, error)
}
Writer interface {
Add(up *model.UserSticker) error
AddSet(uid int, setName string) error
Update(up *model.UserSticker) error
Remove(up *model.UserSticker) error
RemoveSet(uid int, setName string) error
}
)

View File

@ -0,0 +1,25 @@
package users
import "gitlab.com/toby3d/mypackbot/internal/model"
type (
Manager interface {
Reader
Writer
ReadWriter
}
ReadWriter interface {
GetOrCreate(u *model.User) (*model.User, error)
}
Reader interface {
Get(id int) *model.User
GetList(offset, limit int, filter *model.User) (model.Users, int, error)
}
Writer interface {
Create(u *model.User) error
Update(u *model.User) error
}
)

View File

@ -1,37 +0,0 @@
package models
import tg "gitlab.com/toby3d/telegram"
// Commands... represents available and supported bot commands
const (
CommandAddPack = "addPack"
CommandAddSticker = "addSticker"
CommandCancel = "cancel"
CommandDeleteSticker = "delSticker"
CommandDeletePack = "delPack"
CommandReset = "reset"
)
// State... represents current state of user action
const (
StateNone = "none"
StateAddSticker = CommandAddSticker
StateAddPack = CommandAddPack
StateDeleteSticker = CommandDeleteSticker
StateDeletePack = CommandDeletePack
StateReset = CommandReset
)
// SetUploaded is a mimic set name of uploaded stickers without any parent set
const SetUploaded = "?"
// LanguageFallback is a default language code in case what current user language
// is not support yet
const LanguageFallback = "en"
// AllowedUpdates is
var AllowedUpdates = []string{
tg.UpdateInlineQuery, // For searching and sending stickers
tg.UpdateMessage, // For get commands and messages
tg.UpdateChannelPost, // For forwarding announcements
}

49
internal/new.go Normal file
View File

@ -0,0 +1,49 @@
package internal
import (
"github.com/spf13/viper"
"gitlab.com/toby3d/mypackbot/internal/config"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/model/photos"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
"gitlab.com/toby3d/mypackbot/internal/model/users"
usersphotos "gitlab.com/toby3d/mypackbot/internal/model/users/photos"
usersstickers "gitlab.com/toby3d/mypackbot/internal/model/users/stickers"
"gitlab.com/toby3d/mypackbot/internal/store"
tg "gitlab.com/toby3d/telegram"
)
type MyPackBot struct {
bot *tg.Bot
config *viper.Viper
photos photos.Manager
stickers stickers.Manager
users users.Manager
usersPhotos usersphotos.ReadWriter
usersStickers usersstickers.ReadWriter
}
func New(path string) (mpb *MyPackBot, err error) {
mpb = new(MyPackBot)
if mpb.config, err = config.Open(path); err != nil {
return nil, err
}
conn, err := db.Open(mpb.config.GetString("database.filepath"))
if err != nil {
return nil, err
}
mpb.photos = store.NewPhotosStore(conn)
mpb.stickers = store.NewStickersStore(conn)
mpb.users = store.NewUsersStore(conn)
mpb.usersPhotos = store.NewUsersPhotosStore(conn, mpb.users, mpb.photos)
mpb.usersStickers = store.NewUsersStickersStore(conn, mpb.users, mpb.stickers)
if mpb.bot, err = tg.New(mpb.config.GetString("telegram.token")); err != nil {
return nil, err
}
return mpb, nil
}

114
internal/run.go Normal file
View File

@ -0,0 +1,114 @@
package internal
import (
"net"
"time"
"github.com/kirillDanshin/dlog"
http "github.com/valyala/fasthttp"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/handler"
"gitlab.com/toby3d/mypackbot/internal/middleware"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
)
func (mpb *MyPackBot) Run() error {
shutdown, err := mpb.getUpdateChannel()
if err != nil {
return err
}
defer func() { _ = shutdown() }()
chain := middleware.Chain{
middleware.AcquireUser(mpb.users),
middleware.AcquirePrinter(),
middleware.ChatAction(),
middleware.AcquirePhoto(mpb.photos), middleware.AcquireUserPhoto(mpb.usersPhotos),
middleware.AcquireSticker(mpb.stickers), middleware.AcquireUserSticker(mpb.usersStickers),
func() middleware.Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) error {
if ctx.Sticker == nil || ctx.Sticker.SetName == common.SetNameUploaded {
return next(ctx)
}
for _, sticker := range mpb.stickers.GetSet(ctx.Sticker.SetName) {
if mpb.usersStickers.Get(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: sticker.ID,
}) == nil {
return next(ctx)
}
}
ctx.HasSet = true
return next(ctx)
}
}(),
middleware.Birthday(time.Date(0, time.November, 4, 0, 0, 0, 0, time.UTC)), middleware.Hacktober(),
middleware.UpdateLastSeen(mpb.users),
}
h := chain.UpdateHandler(handler.NewHandler(
mpb.users, mpb.stickers, mpb.photos, mpb.usersStickers, mpb.usersPhotos,
).UpdateHandler)
for update := range mpb.bot.Updates {
ctx := new(model.Context)
ctx.Request = update
ctx.Bot = mpb.bot
if err = h(ctx); err != nil {
dlog.D(err)
}
}
return nil
}
func (mpb *MyPackBot) getUpdateChannel() (func() error, error) {
switch {
case mpb.config.IsSet("telegram.webhook"):
cfg := mpb.config.Sub("telegram.webhook")
u := http.AcquireURI()
defer http.ReleaseURI(u)
cert := make([]string, 0, 2)
if cfg.IsSet("certificate") {
cert = append(cert, cfg.GetString("certificate"))
}
if cfg.IsSet("key") {
cert = append(cert, cfg.GetString("key"))
}
ln, err := net.Listen("tcp", cfg.GetString("serve"))
if err != nil {
return nil, err
}
updates, release := mpb.bot.NewWebhookChannel(u, tg.SetWebhook{
AllowedUpdates: cfg.GetStringSlice("allowed_updates"),
}, ln, cert...)
mpb.bot.Updates = updates
return release, nil
case mpb.config.IsSet("telegram.long_poll"):
if _, err := mpb.bot.DeleteWebhook(); err != nil {
return nil, err
}
cfg := mpb.config.Sub("telegram.long_poll")
mpb.bot.Updates = mpb.bot.NewLongPollingChannel(&tg.GetUpdates{
AllowedUpdates: cfg.GetStringSlice("allowed_updates"),
Limit: cfg.GetInt("limit"),
Offset: cfg.GetInt("offset"),
Timeout: cfg.GetInt("timeout"),
})
return nil, nil
}
return nil, nil
}

99
internal/store/photos.go Normal file
View File

@ -0,0 +1,99 @@
package store
import (
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
bolt "go.etcd.io/bbolt"
)
type PhotosStore struct {
conn *bolthold.Store
}
func NewPhotosStore(conn *bolthold.Store) *PhotosStore {
return &PhotosStore{conn: conn}
}
func (store *PhotosStore) Create(p *model.Photo) error {
if store.Get(p.ID) != nil {
return bolthold.ErrKeyExists
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.InsertIntoBucket(tx.Bucket(common.BucketPhotos), p.ID, p)
})
}
func (store *PhotosStore) Get(id string) *model.Photo {
result := new(model.Photo)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
return store.conn.GetFromBucket(tx.Bucket(common.BucketPhotos), id, result)
}); err != nil {
return nil
}
return result
}
func (store *PhotosStore) GetList(offset, limit int, filter *model.Photo) (list model.Photos, count int, err error) {
q := bolthold.Where("ID").Ne("")
if offset > 0 {
q = q.Skip(offset)
}
if limit > 0 {
q = q.Limit(limit)
}
// TODO(toby3d): implement filter here
list = make(model.Photos, limit)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketPhotos)
if count, err = store.conn.CountInBucket(bkt, &model.Photo{}, q); err != nil {
return err
}
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return list, count, err
}
return list, count, err
}
func (store *PhotosStore) Update(p *model.Photo) error {
if store.Get(p.ID) == nil {
return store.Create(p)
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.UpdateBucket(tx.Bucket(common.BucketPhotos), p.ID, p)
})
}
func (store *PhotosStore) Remove(id string) error {
if store.Get(id) == nil {
return bolthold.ErrNotFound
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.DeleteFromBucket(tx.Bucket(common.BucketPhotos), id, &model.Photo{})
})
}
func (store *PhotosStore) GetOrCreate(p *model.Photo) (*model.Photo, error) {
if photo := store.Get(p.ID); photo != nil {
return photo, nil
}
if err := store.Create(p); err != nil {
return nil, err
}
return store.GetOrCreate(p)
}

114
internal/store/stickers.go Normal file
View File

@ -0,0 +1,114 @@
package store
import (
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
bolt "go.etcd.io/bbolt"
)
type StickersStore struct {
conn *bolthold.Store
}
func NewStickersStore(conn *bolthold.Store) *StickersStore {
return &StickersStore{conn: conn}
}
func (store *StickersStore) Create(s *model.Sticker) error {
if store.Get(s.ID) != nil {
return bolthold.ErrKeyExists
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.InsertIntoBucket(tx.Bucket(common.BucketStickers), s.ID, s)
})
}
func (store *StickersStore) Get(id string) *model.Sticker {
result := new(model.Sticker)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
return store.conn.GetFromBucket(tx.Bucket(common.BucketStickers), id, result)
}); err != nil {
return nil
}
return result
}
func (store *StickersStore) GetSet(name string) model.Stickers {
list, _, _ := store.GetList(0, 0, &model.Sticker{SetName: name})
return list
}
func (store *StickersStore) GetList(offset, limit int, filter *model.Sticker) (list model.Stickers, count int,
err error) {
q := bolthold.Where("ID").Ne("")
if offset > 0 {
q = q.Skip(offset)
}
if limit > 0 {
q = q.Limit(limit)
}
if filter != nil {
if filter.Emoji != "" {
q = q.And("Emoji").ContainsAny(filter.Emoji)
}
if filter.SetName != "" {
q = q.And("SetName").Eq(filter.SetName)
}
}
list = make(model.Stickers, limit)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketStickers)
if count, err = store.conn.CountInBucket(bkt, &model.Sticker{}, q); err != nil {
return err
}
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return list, count, err
}
return list, count, err
}
func (store *StickersStore) Update(s *model.Sticker) error {
if store.Get(s.ID) == nil {
return store.Create(s)
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.UpdateBucket(tx.Bucket(common.BucketStickers), s.ID, s)
})
}
func (store *StickersStore) Remove(id string) error {
if store.Get(id) == nil {
return bolthold.ErrNotFound
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.DeleteFromBucket(tx.Bucket(common.BucketStickers), id, &model.Sticker{})
})
}
func (store *StickersStore) GetOrCreate(s *model.Sticker) (*model.Sticker, error) {
if sticker := store.Get(s.ID); sticker != nil {
return sticker, nil
}
if err := store.Create(s); err != nil {
return nil, err
}
return store.GetOrCreate(s)
}

100
internal/store/store.go Normal file
View File

@ -0,0 +1,100 @@
package store
import (
"sort"
"gitlab.com/toby3d/mypackbot/internal/model"
usersphotos "gitlab.com/toby3d/mypackbot/internal/model/users/photos"
usersstickers "gitlab.com/toby3d/mypackbot/internal/model/users/stickers"
tg "gitlab.com/toby3d/telegram"
)
type (
Store struct {
usersStickers usersstickers.ReadWriter
usersPhotos usersphotos.ReadWriter
}
Filter struct {
UserID int
AllowedTypes []string
Query string
Offset int
Limit int
}
)
func (f *Filter) offset() int {
if len(f.AllowedTypes) == 0 || f.Offset <= len(f.AllowedTypes) {
return f.Offset
}
return f.Offset / len(f.AllowedTypes)
}
func (f *Filter) limit() int {
if len(f.AllowedTypes) == 0 || f.Limit <= len(f.AllowedTypes) {
return f.Limit
}
return f.Limit / len(f.AllowedTypes)
}
func NewStore(us usersstickers.ReadWriter, up usersphotos.ReadWriter) *Store {
return &Store{
usersStickers: us,
usersPhotos: up,
}
}
func (store *Store) GetList(offset, limit int, filter *Filter) (list []model.InlineResult, count int, err error) {
if filter == nil {
filter = new(Filter)
filter.AllowedTypes = []string{tg.TypeSticker, tg.TypePhoto}
}
list = make([]model.InlineResult, 0)
for _, t := range filter.AllowedTypes {
switch t {
case tg.TypeSticker:
l, c, err := store.usersStickers.GetList(
filter.offset(), filter.limit(), &model.UserSticker{
UserID: filter.UserID,
Query: filter.Query,
},
)
if err != nil {
return list, count, err
}
for i := range l {
list = append(list, l[i])
}
count += c
case tg.TypePhoto:
l, c, err := store.usersPhotos.GetList(
filter.offset(), filter.limit(), &model.UserPhoto{
UserID: filter.UserID,
Query: filter.Query,
},
)
if err != nil {
return list, count, err
}
for i := range l {
list = append(list, l[i])
}
count += c
}
}
sort.Slice(list, func(i, j int) bool {
return list[i].GetUpdatedAt() < list[j].GetUpdatedAt()
})
return list, count, err
}

88
internal/store/users.go Normal file
View File

@ -0,0 +1,88 @@
package store
import (
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
bolt "go.etcd.io/bbolt"
)
type UsersStore struct {
conn *bolthold.Store
}
func NewUsersStore(conn *bolthold.Store) *UsersStore {
return &UsersStore{conn: conn}
}
func (store *UsersStore) Create(u *model.User) error {
if store.Get(u.ID) != nil {
return bolthold.ErrKeyExists
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.InsertIntoBucket(tx.Bucket(common.BucketUsers), u.ID, u)
})
}
func (store *UsersStore) Get(id int) *model.User {
result := new(model.User)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
return store.conn.GetFromBucket(tx.Bucket(common.BucketUsers), id, result)
}); err != nil {
return nil
}
return result
}
func (store *UsersStore) GetList(offset, limit int, filter *model.User) (list model.Users, count int, err error) {
list = make(model.Users, limit)
q := bolthold.Where("ID").Ne("")
if offset > 0 {
q = q.Skip(offset)
}
if limit > 0 {
q = q.Limit(limit)
}
// TODO(toby3d): implement filter here
if err := store.conn.Bolt().View(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsers)
if count, err = store.conn.CountInBucket(bkt, &model.User{}, q); err != nil {
return err
}
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return nil, 0, err
}
return list, count, err
}
func (store *UsersStore) Update(u *model.User) error {
if store.Get(u.ID) == nil {
return store.Create(u)
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) (err error) {
return store.conn.UpdateBucket(tx.Bucket(common.BucketUsers), u.ID, u)
})
}
func (store *UsersStore) GetOrCreate(u *model.User) (*model.User, error) {
if user := store.Get(u.ID); user != nil {
return user, nil
}
if err := store.Create(u); err != nil {
return nil, err
}
return u, nil
}

View File

@ -0,0 +1,133 @@
package store
import (
"strings"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/photos"
"gitlab.com/toby3d/mypackbot/internal/model/users"
bolt "go.etcd.io/bbolt"
"golang.org/x/xerrors"
)
type UsersPhotosStore struct {
conn *bolthold.Store
photos photos.Reader
users users.Reader
}
func NewUsersPhotosStore(conn *bolthold.Store, us users.Reader, ps photos.Reader) *UsersPhotosStore {
return &UsersPhotosStore{
conn: conn,
photos: ps,
users: us,
}
}
func (store *UsersPhotosStore) Add(up *model.UserPhoto) (err error) {
if store.users.Get(up.UserID) == nil || store.photos.Get(up.PhotoID) == nil {
return bolthold.ErrNotFound
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.InsertIntoBucket(tx.Bucket(common.BucketUsersPhotos), bolthold.NextSequence(), up)
})
}
func (store *UsersPhotosStore) Update(up *model.UserPhoto) (err error) {
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.UpdateMatchingInBucket(
tx.Bucket(common.BucketUsersPhotos), &model.UserPhoto{},
bolthold.Where("UserID").Eq(up.UserID).And("PhotoID").Eq(up.PhotoID),
func(record interface{}) error {
result, ok := record.(*model.UserPhoto) // record will always be a pointer
if !ok {
return xerrors.New("invalid type")
}
result.Query, result.UpdatedAt = up.Query, up.UpdatedAt
return nil
})
})
}
func (store *UsersPhotosStore) Get(up *model.UserPhoto) *model.Photo {
result := new(model.UserPhoto)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
return store.conn.FindOneInBucket(
tx.Bucket(common.BucketUsersPhotos), result,
bolthold.Where("UserID").Eq(up.UserID).And("PhotoID").Eq(up.PhotoID),
)
}); err != nil {
return nil
}
return store.photos.Get(result.PhotoID)
}
func (store *UsersPhotosStore) GetList(offset, limit int, filter *model.UserPhoto) (list model.Photos, count int,
err error) {
q := bolthold.Where("UserID").Ne(0).And("PhotoID").Ne("")
qCount := bolthold.Where("UserID").Ne(0).And("PhotoID").Ne("")
if offset != 0 {
q = q.Skip(offset)
}
if limit != 0 {
q = q.Limit(limit)
}
if filter != nil {
if filter.UserID != 0 {
q = q.And("UserID").Eq(filter.UserID)
qCount.And("UserID").Eq(filter.UserID)
}
if filter.Query != "" {
q = q.And("Query").MatchFunc(func(field string) (bool, error) {
return strings.ContainsAny(field, filter.Query), nil
})
qCount.And("Query").MatchFunc(func(field string) (bool, error) {
return strings.ContainsAny(field, filter.Query), nil
})
}
}
results := make(model.UserPhotos, 0)
if err = store.conn.Bolt().View(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsersPhotos)
if count, err = store.conn.CountInBucket(bkt, &model.UserPhoto{}, qCount); err != nil {
return err
}
return store.conn.FindInBucket(bkt, &results, q)
}); err != nil {
return nil, 0, err
}
list = make(model.Photos, 0)
for i := range results {
list = append(list, store.photos.Get(results[i].PhotoID))
}
return list, count, err
}
func (store *UsersPhotosStore) Remove(up *model.UserPhoto) (err error) {
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.DeleteMatchingFromBucket(
tx.Bucket(common.BucketUsersPhotos), &model.UserPhoto{},
bolthold.Where("UserID").Eq(up.UserID).And("PhotoID").Eq(up.PhotoID),
)
})
}

View File

@ -0,0 +1,169 @@
package store
import (
"strings"
"time"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
"gitlab.com/toby3d/mypackbot/internal/model/users"
bolt "go.etcd.io/bbolt"
"golang.org/x/xerrors"
)
type UsersStickersStore struct {
conn *bolthold.Store
stickers stickers.Reader
users users.Reader
}
func NewUsersStickersStore(conn *bolthold.Store, us users.Reader, ss stickers.Reader) *UsersStickersStore {
return &UsersStickersStore{
conn: conn,
stickers: ss,
users: us,
}
}
func (store *UsersStickersStore) Add(us *model.UserSticker) error {
if store.users.Get(us.UserID) == nil || store.stickers.Get(us.StickerID) == nil {
return bolthold.ErrNotFound
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.InsertIntoBucket(tx.Bucket(common.BucketUsersStickers), bolthold.NextSequence(), us)
})
}
func (store *UsersStickersStore) AddSet(uid int, setName string) error {
for _, sticker := range store.stickers.GetSet(setName) {
us := model.UserSticker{UserID: uid, StickerID: sticker.ID}
if store.Get(&us) != nil {
continue
}
now := time.Now().UTC().Unix()
us.Query = sticker.Emoji
us.CreatedAt = now
us.UpdatedAt = now
if err := store.Add(&us); err != nil {
return err
}
}
return nil
}
func (store *UsersStickersStore) Update(us *model.UserSticker) (err error) {
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.UpdateMatchingInBucket(
tx.Bucket(common.BucketUsersStickers), &model.UserSticker{},
bolthold.Where("UserID").Eq(us.UserID).And("StickerID").Eq(us.StickerID),
func(record interface{}) error {
result, ok := record.(*model.UserSticker) // record will always be a pointer
if !ok {
return xerrors.New("invalid type")
}
result.Query, result.UpdatedAt = us.Query, us.UpdatedAt
return nil
})
})
}
func (store *UsersStickersStore) Get(us *model.UserSticker) *model.Sticker {
result := new(model.UserSticker)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
return store.conn.FindOneInBucket(
tx.Bucket(common.BucketUsersStickers), result,
bolthold.Where("UserID").Eq(us.UserID).And("StickerID").Eq(us.StickerID),
)
}); err != nil {
return nil
}
return store.stickers.Get(result.StickerID)
}
func (store *UsersStickersStore) GetList(offset, limit int, filter *model.UserSticker) (list model.Stickers, count int,
err error) {
q := bolthold.Where("UserID").Ne(0).And("StickerID").Ne("")
qCount := bolthold.Where("UserID").Ne(0).And("StickerID").Ne("")
if offset != 0 {
q = q.Skip(offset)
}
if limit != 0 {
q = q.Limit(limit)
}
if filter != nil {
if filter.UserID != 0 {
q = q.And("UserID").Eq(filter.UserID)
qCount = qCount.And("UserID").Eq(filter.UserID)
}
if filter.Query != "" {
q = q.And("Query").MatchFunc(func(field string) (bool, error) {
return strings.ContainsAny(field, filter.Query), nil
})
qCount = qCount.And("Query").MatchFunc(func(field string) (bool, error) {
return strings.ContainsAny(field, filter.Query), nil
})
}
}
results := make(model.UserStickers, 0)
if err = store.conn.Bolt().View(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsersStickers)
if count, err = store.conn.CountInBucket(bkt, &model.UserSticker{}, qCount); err != nil {
return err
}
return store.conn.FindInBucket(bkt, &results, q)
}); err != nil {
return nil, 0, err
}
list = make(model.Stickers, 0)
for i := range results {
list = append(list, store.stickers.Get(results[i].StickerID))
}
return list, count, err
}
func (store *UsersStickersStore) Remove(us *model.UserSticker) error {
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.DeleteMatchingFromBucket(
tx.Bucket(common.BucketUsersStickers), &model.UserSticker{},
bolthold.Where("UserID").Eq(us.UserID).And("StickerID").Eq(us.StickerID),
)
})
}
func (store *UsersStickersStore) RemoveSet(uid int, setName string) error {
ids := make([]string, 0)
for _, sticker := range store.stickers.GetSet(setName) {
ids = append(ids, sticker.ID)
}
return store.conn.Bolt().Update(func(tx *bolt.Tx) error {
return store.conn.DeleteMatchingFromBucket(
tx.Bucket(common.BucketUsersStickers), &model.UserSticker{},
bolthold.Where("UserID").Eq(uid).And("StickerID").ContainsAny(ids),
)
})
}

View File

@ -1,68 +0,0 @@
package updates
import (
"fmt"
"net/url"
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/config"
"gitlab.com/toby3d/mypackbot/internal/models"
tg "gitlab.com/toby3d/telegram"
)
// Channel return webhook or long polling channel with bot updates
func Channel(webhookMode bool) (updates tg.UpdatesChannel, err error) {
log.Ln("Preparing channel for updates...")
if !webhookMode {
log.Ln("Use LongPolling updates")
var info *tg.WebhookInfo
info, err = bot.Bot.GetWebhookInfo()
if err != nil {
return
}
if info.URL != "" {
log.Ln("Deleting webhook...")
_, err = bot.Bot.DeleteWebhook()
return
}
updates = bot.Bot.NewLongPollingChannel(&tg.GetUpdatesParameters{
Offset: 0,
Limit: 100,
Timeout: 60,
AllowedUpdates: models.AllowedUpdates,
})
return
}
set, err := url.Parse(config.Config.GetString("telegram.webhook.set"))
if err != nil {
return nil, err
}
listen := config.Config.GetString("telegram.webhook.listen")
serve := config.Config.GetString("telegram.webhook.serve")
log.Ln(
"Trying set webhook on address:",
fmt.Sprint(set.String(), bot.Bot.AccessToken),
)
log.Ln("Creating new webhook...")
params := tg.NewWebhook(fmt.Sprint(set, listen, bot.Bot.AccessToken), nil)
params.MaxConnections = 40
params.AllowedUpdates = models.AllowedUpdates
updates = bot.Bot.NewWebhookChannel(
set,
params, // params
"", // certFile
"", // keyFile
serve, // serve
)
return
}

View File

@ -1,36 +0,0 @@
package updates
import (
"time"
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/config"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
tg "gitlab.com/toby3d/telegram"
)
// ChannelPost checks ChannelPost update for forwarding content to bot users
func ChannelPost(post *tg.Message) {
channelID := config.Config.GetInt64("telegram.channel")
if post.Chat.ID != channelID {
log.Ln(post.Chat.ID, "!=", channelID)
return
}
users, err := db.DB.GetUsers()
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
}
}

View File

@ -1,86 +0,0 @@
package updates
import (
"strconv"
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/errors"
"gitlab.com/toby3d/mypackbot/internal/i18n"
"gitlab.com/toby3d/mypackbot/internal/utils"
tg "gitlab.com/toby3d/telegram"
)
// InlineQuery checks InlineQuery updates for answer with personal results
func InlineQuery(inlineQuery *tg.InlineQuery) {
fixedQuery, err := utils.FixEmoji(inlineQuery.Query)
if err == nil {
inlineQuery.Query = fixedQuery
}
answer := new(tg.AnswerInlineQueryParameters)
answer.InlineQueryID = inlineQuery.ID
answer.CacheTime = 1
answer.IsPersonal = true
if len([]rune(inlineQuery.Query)) >= 256 {
_, err = bot.Bot.AnswerInlineQuery(answer)
errors.Check(err)
return
}
log.Ln("Let's preparing answer...")
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)
errors.Check(err)
offset++
stickers, err := db.DB.GetUserStickers(
inlineQuery.From,
&tg.InlineQuery{
Offset: strconv.Itoa(offset),
Query: inlineQuery.Query,
},
)
errors.Check(err)
if len(stickers) == 0 {
if offset == 0 && inlineQuery.Query != "" {
// If search stickers by emoji return 0 results
answer.SwitchPrivateMessageText = t(
"button_inline_nothing", map[string]interface{}{
"Query": inlineQuery.Query,
},
)
answer.SwitchPrivateMessageParameter = tg.CommandHelp
}
answer.Results = nil
} else {
log.Ln("STICKERS FROM REQUEST:", len(stickers))
if len(stickers) == 50 {
answer.NextOffset = strconv.Itoa(offset)
log.Ln("NEXT OFFSET:", answer.NextOffset)
}
var results = make([]interface{}, len(stickers))
for i, sticker := range stickers {
results[i] = tg.NewInlineQueryResultCachedSticker(sticker, sticker)
}
answer.Results = results
}
log.Ln("CacheTime:", answer.CacheTime)
_, err = bot.Bot.AnswerInlineQuery(answer)
errors.Check(err)
}

View File

@ -1,29 +0,0 @@
package updates
import (
log "github.com/kirillDanshin/dlog"
"gitlab.com/toby3d/mypackbot/internal/actions"
"gitlab.com/toby3d/mypackbot/internal/bot"
"gitlab.com/toby3d/mypackbot/internal/commands"
"gitlab.com/toby3d/mypackbot/internal/messages"
tg "gitlab.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)
}
}

View File

@ -1,15 +0,0 @@
package utils
import (
"github.com/nicksnyder/go-i18n/i18n"
tg "gitlab.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")),
),
)
}

View File

@ -1,27 +0,0 @@
package utils
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
}

Some files were not shown because too many files have changed in this diff Show More