♻️ Refactored stores by using bolthold package

This commit is contained in:
Maxim Lebedev 2020-02-28 12:47:25 +05:00
parent 1b7945cb71
commit 7ce10980cc
No known key found for this signature in database
GPG Key ID: F8978F46FF0FFA4F
35 changed files with 905 additions and 1365 deletions

View File

@ -5,7 +5,6 @@ import (
"log"
"path/filepath"
json "github.com/json-iterator/go"
bunt "github.com/tidwall/buntdb"
"gitlab.com/toby3d/mypackbot/internal/db"
"gitlab.com/toby3d/mypackbot/internal/migrator"
@ -40,10 +39,9 @@ func main() {
}
defer newDB.Close()
marshler := json.ConfigFastest
users := store.NewUsersStore(newDB, marshler)
stickers := store.NewStickersStore(newDB, marshler)
usersStickers := store.NewUsersStickersStore(newDB, users, stickers, marshler)
users := store.NewUsersStore(newDB)
stickers := store.NewStickersStore(newDB)
usersStickers := store.NewUsersStickersStore(newDB, users, stickers)
if err = migrator.AutoMigrate(migrator.AutoMigrateConfig{
Bot: bot,
@ -52,7 +50,6 @@ func main() {
UsersStickers: usersStickers,
Users: users,
OldDB: oldDB,
Marshler: marshler,
}); err != nil {
log.Fatalln("ERROR:", err.Error())
}

View File

@ -36,90 +36,81 @@ func init() {
}
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!": 1,
"👋 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.": 6,
"👍 Imported!": 11,
"👍 Removed!": 13,
"👍 Updated!": 12,
"💡 Add any text and/or emoji(s) as an argument of this command to change its search properties.": 18,
"💡 Use /del command as an reply to the sticker/photo to remove it from the feed of your collection.": 10,
"💡 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.": 8,
"💡 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.": 9,
"💸 Make a donation": 2,
"📥 Import %s set": 20,
"📥 Import this photo": 16,
"📥 Import this sticker": 19,
"🔥 Remove %s set": 21,
"🔥 Remove this photo": 17,
"🔧 Let's hack!": 5,
"🕵 Found %d result(s)": 14,
"🕺 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!": 4,
"🤔 What to do with this?": 15,
"🤖 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": 7,
"🤝 Use referral links": 3,
"🥳 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!": 0,
"☺️ 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, 0x000000a5, 0x00000162, 0x00000177,
0x0000018f, 0x00000265, 0x00000276, 0x00000304,
0x000004a1, 0x0000055d, 0x00000688, 0x000006ee,
0x000006fd, 0x0000070b, 0x00000719, 0x00000734,
0x0000074f, 0x00000766, 0x0000077d, 0x000007df,
0x000007f8, 0x0000080e, 0x00000824,
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🥳 4 November? It's a @toby3d birthday!\x0aIf you like this bot, then" +
" why not send him a congratulation along with a small gift? This will ma" +
"ke him incredibly happy!\x02☺ Oh, you missed @toby3d birthday on Novemb" +
"er 4th!\x0aIf you like this bot, why not send him some birthday greeting" +
"s and a little birthday gift? It is not yet too late to make him happy!" +
"\x02💸 Make a donation\x02🤝 Use referral links\x02🕺 HacktoberFest is here" +
"!\x0a\x0aIf you are a beginner or already an experienced golang-develope" +
"r, now is a great time to help improve the quality of the code of this b" +
"ot. Choose issue to your taste and offer your PR!\x02🔧 Let's hack!\x02👋 " +
"Hi %[1]s, I'm %[2]s!\x0aThanks to me, you can collect almost any media c" +
"ontent 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 (sh" +
"ould) contain an argument:\x0a/start - start all over again\x0a/help [ot" +
"her command] - get a list of available commands or help and a demonstrat" +
"ion of a specific command\x0a/add [query] - add media from reply to your" +
" collection [with custom search query]\x0a/edit (query) - change query t" +
"o 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 media to y" +
"our collection feed. Given an argument, the result of this command will " +
"be equivalent to the /edit command.\x02💡 Use the /edit command with an a" +
"rgument from any character set as a reply to a sticker/photo to change t" +
"he search query of this media in the feed of your collection. If this me" +
"dia is not in the feed, then the result of this command will be equivale" +
"nt to the /add command with the same argument.\x02💡 Use /del command as " +
"an reply to the sticker/photo to remove it from the feed of your collect" +
"ion.\x02👍 Imported!\x02👍 Updated!\x02👍 Removed!\x02🕵 Found %[1]d result(" +
"s)\x02🤔 What to do with this?\x02📥 Import this photo\x02🔥 Remove this ph" +
"oto\x02💡 Add any text and/or emoji(s) as an argument of this command to " +
"change its search properties.\x02📥 Import this sticker\x02📥 Import %[1]s" +
" set\x02🔥 Remove %[1]s set"
"\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, 0x00000135, 0x0000028e, 0x000002ae,
0x000002d7, 0x00000423, 0x00000434, 0x0000053a,
0x000007f5, 0x00000948, 0x00000b48, 0x00000bfa,
0x00000c1b, 0x00000c34, 0x00000c49, 0x00000c7a,
0x00000ca0, 0x00000cc9, 0x00000ce4, 0x00000da3,
0x00000dd0, 0x00000df6, 0x00000e0e,
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🥳 4-е Ноября? Это день рождения @toby3d!\x0aЕсли тебе нравится этот " +
"бот, то почему бы не отправить ему поздравления вместе с небольшим пода" +
"рком? Это несказанно его осчастливит!\x02☺ Ой, ты пропустил день рожде" +
"ния @toby3d 4-го Ноября!\x0aЕсли тебе нравится этот бот, то почему бы н" +
"е отправить ему поздравления вместе с небольшим подарком? Ещё не слишко" +
"м поздно его порадовать!\x02💸 Пожертвование\x02🤝 Реферальные ссылки\x02" +
"🕺 Хактоберфест уже здесь!\x0a\x0aЕсли ты начинающий или уже опытный g" +
"olang-разработчик, то сейчас хорошее время помочь улучшить качество кода" +
" этого бота. Выбери issue на свой вкус и предложи PR!\x02🔧 Let's hack!" +
"\x02👋 Привет %[1]s, я %[2]s!\x0aБлагодаря мне, ты можешь коллекционирова" +
"ть в inline-режиме практически любой медиа-контент Telegram без огранич" +
"ений, в любом чате.\x02🤖 Вот список команд которые я понимаю, некоторые" +
@ -140,6 +131,15 @@ const ruData string = "" + // Size: 3598 bytes
"!\x02🕵 Найдено %[1]d результатов\x02🤔 Что с этим делать?\x02📥 Импортиров" +
"ать фото\x02🔥 Убрать фото\x02💡 Добавь любой текст и/или эмодзи в качест" +
"ве аргумента этой команды чтобы изменить поисковые свойства.\x02📥 Импор" +
"тировать стикер\x02📥 Импортировать %[1]s\x02🔥 Убрать %[1]s"
"тировать стикер\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: 7091C75C
// Total table size 5914 bytes (5KiB); checksum: 490F86A

7
go.mod
View File

@ -4,7 +4,6 @@ go 1.13
require (
github.com/Masterminds/semver v1.5.0
github.com/etcd-io/bbolt v1.3.3
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
@ -18,12 +17,16 @@ require (
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-20200218162808-858deeef8eeb
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

6
go.sum
View File

@ -156,6 +156,8 @@ github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2K
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=
@ -171,8 +173,12 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
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=

View File

@ -3,31 +3,28 @@ package db
import (
"os"
bolt "github.com/etcd-io/bbolt"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
bolt "go.etcd.io/bbolt"
)
func Open(path string) (*bolt.DB, error) {
db, err := bolt.Open(path, os.ModePerm, nil)
func Open(path string) (*bolthold.Store, error) {
db, err := bolthold.Open(path, os.ModePerm, nil)
if err != nil {
return nil, err
}
if err = AutoMigrate(db); err != nil {
_ = db.Close()
}
return db, err
}
func AutoMigrate(db *bolt.DB) error {
return db.Update(func(tx *bolt.Tx) (err error) {
for _, bkt := range common.Buckets {
if _, err = tx.CreateBucketIfNotExists(bkt); err != nil {
return 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
}

View File

@ -12,10 +12,7 @@ func TestOpen(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
db, err := Open(filepath.Join("/", "invalid", "path"))
assert.Error(t, err)
t.Run("automigrate", func(t *testing.T) {
assert.Panics(t, func() { _ = AutoMigrate(db) })
})
assert.Nil(t, db)
})
t.Run("valid", func(t *testing.T) {
rootPath, err := os.Getwd()
@ -29,9 +26,5 @@ func TestOpen(t *testing.T) {
assert.NoError(t, db.Close())
assert.NoError(t, os.Remove(testPath))
}()
t.Run("automigrate", func(t *testing.T) {
assert.NoError(t, AutoMigrate(db))
})
})
}

View File

@ -48,6 +48,7 @@ func (h *Handler) CallbackAdd(ctx *model.Context) (err error) {
case ctx.Sticker != nil:
if ctx.Request.CallbackQuery.Data == common.DataAddSet {
err = h.CommandAddSet(ctx)
ctx.HasSet = true
} else {
err = h.CommandAddSticker(ctx)
}
@ -56,6 +57,7 @@ func (h *Handler) CallbackAdd(ctx *model.Context) (err error) {
return err
}
ctx.HasSticker = true
editMessage.ReplyMarkup = h.GetStickerKeyboard(ctx)
default:
return err
@ -87,6 +89,7 @@ func (h *Handler) CallbackDel(ctx *model.Context) (err error) {
case ctx.Sticker != nil:
if ctx.Request.CallbackQuery.Data == common.DataDelSet {
err = h.CommandDelSet(ctx)
ctx.HasSet = false
} else {
err = h.CommandDelSticker(ctx)
}
@ -95,6 +98,7 @@ func (h *Handler) CallbackDel(ctx *model.Context) (err error) {
return err
}
ctx.HasSticker = false
editMessage.ReplyMarkup = h.GetStickerKeyboard(ctx)
default:
return err

View File

@ -41,7 +41,7 @@ func (h *Handler) IsCommand(ctx *model.Context) (err error) {
// CommandPing send common ping message.
func (h *Handler) CommandPing(ctx *model.Context) (err error) {
reply := tg.NewMessage(ctx.User.UserID, "🏓")
reply := tg.NewMessage(int64(ctx.User.ID), "🏓")
reply.ReplyMarkup = tg.NewReplyKeyboardRemove(false)
reply.ReplyToMessageID = ctx.Request.Message.ID
_, err = ctx.SendMessage(reply)
@ -53,7 +53,7 @@ func (h *Handler) CommandPing(ctx *model.Context) (err error) {
// 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(ctx.User.UserID, p.Sprintf("👋 Hi %s, I'm %s!\nThanks to me, you can collect almost any"+
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)
@ -67,7 +67,7 @@ func (h *Handler) CommandStart(ctx *model.Context) (err error) {
// 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(ctx.User.UserID, p.Sprintf("🤖 Here is a list of commands that I understand, some of"+
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 "+
@ -137,7 +137,7 @@ func (h *Handler) CommandAdd(ctx *model.Context) (err error) {
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.User.UserID, p.Sprintf("👍 Imported!"))
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)
@ -164,7 +164,7 @@ func (h *Handler) CommandEdit(ctx *model.Context) (err error) {
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.User.UserID, p.Sprintf("👍 Updated!"))
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)
@ -191,7 +191,7 @@ func (h *Handler) CommandDel(ctx *model.Context) (err error) {
}
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.User.UserID, p.Sprintf("👍 Removed!"))
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)

View File

@ -11,15 +11,16 @@ import (
)
type Handler struct {
users users.Manager
stickers stickers.Manager
photos photos.Manager
usersStickers us.Manager
usersPhotos up.Manager
users users.ReadWriter
stickers stickers.ReadWriter
photos photos.ReadWriter
usersStickers us.ReadWriter
usersPhotos up.ReadWriter
store *store.Store
}
func NewHandler(us users.Manager, ss stickers.Manager, ps photos.Manager, uss us.Manager, ups up.Manager) *Handler {
func NewHandler(us users.ReadWriter, ss stickers.ReadWriter, ps photos.ReadWriter, uss us.ReadWriter,
ups up.ReadWriter) *Handler {
return &Handler{
photos: ps,
stickers: ss,

View File

@ -12,26 +12,32 @@ import (
"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
answer.IsPersonal = !strings.Contains(ctx.Request.InlineQuery.Query, "personal:false")
filter := getFilter(ctx.Request.InlineQuery)
items, count := h.store.GetList(ctx.User.ID, filter)
if filter.Offset+50 < count {
answer.NextOffset = strconv.Itoa(filter.Offset + 50)
if answer.IsPersonal = !strings.Contains(ctx.Request.InlineQuery.Query, "personal:false"); answer.IsPersonal {
filter.UserID = ctx.User.ID
}
for i := range items {
switch item := items[i].(type) {
case *model.Sticker:
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+strconv.FormatUint(item.ID, 10), item.FileID,
tg.TypeSticker+common.DataSeparator+results[i].GetID(), results[i].GetFileID(),
))
case *model.Photo:
case tg.TypePhoto:
answer.Results = append(answer.Results, tg.NewInlineQueryResultCachedPhoto(
tg.TypePhoto+common.DataSeparator+strconv.FormatUint(item.ID, 10), item.FileID,
tg.TypePhoto+common.DataSeparator+results[i].GetID(), results[i].GetFileID(),
))
}
}
@ -46,7 +52,7 @@ func (h *Handler) IsInlineQuery(ctx *model.Context) (err error) {
func getFilter(iq *tg.InlineQuery) *store.Filter {
f := new(store.Filter)
f.Limit = 50
f.Limit = defaultLimit
f.Offset, _ = strconv.Atoi(iq.Offset)
if !strings.Contains(iq.Query, "photos:false") {
@ -62,17 +68,20 @@ func getFilter(iq *tg.InlineQuery) *store.Filter {
}
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, "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

View File

@ -1,6 +1,8 @@
package handler
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
@ -13,16 +15,15 @@ func (h *Handler) GetPhotoKeyboard(ctx *model.Context) *tg.InlineKeyboardMarkup
}
p := ctx.Get("printer").(*message.Printer)
ctx.UserPhoto = h.usersPhotos.Get(&model.UserPhoto{
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 ctx.UserPhoto != nil {
if userPhoto != nil {
markup = tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton(p.Sprintf("🔥 Remove this photo"), common.DataDel),
))
@ -32,11 +33,13 @@ func (h *Handler) GetPhotoKeyboard(ctx *model.Context) *tg.InlineKeyboardMarkup
}
func (h *Handler) CommandAddPhoto(ctx *model.Context) (err error) {
if ctx.Photo == nil || ctx.UserPhoto != nil {
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
@ -54,7 +57,7 @@ func (h *Handler) CommandEditPhoto(ctx *model.Context) (err error) {
if !ctx.Request.Message.HasCommandArgument() {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.User.UserID, p.Sprintf("💡 Add any text and/or emoji(s) as an argument "+
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
@ -65,22 +68,27 @@ func (h *Handler) CommandEditPhoto(ctx *model.Context) (err error) {
return err
}
if ctx.UserPhoto == nil {
if !ctx.HasPhoto {
if err = h.CommandAddPhoto(ctx); err != nil {
return err
}
}
ctx.UserPhoto.UpdatedAt = ctx.Request.Message.Date
ctx.UserPhoto.Query = ctx.Request.Message.CommandArgument()
return h.usersPhotos.Update(ctx.UserPhoto)
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.UserPhoto == nil {
if ctx.Photo == nil || !ctx.HasPhoto {
return nil
}
return h.usersPhotos.Remove(ctx.UserPhoto)
return h.usersPhotos.Remove(&model.UserPhoto{
UserID: ctx.User.ID,
PhotoID: ctx.Photo.ID,
})
}

View File

@ -1,6 +1,8 @@
package handler
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
tg "gitlab.com/toby3d/telegram"
@ -13,17 +15,11 @@ func (h *Handler) GetStickerKeyboard(ctx *model.Context) *tg.InlineKeyboardMarku
}
p := ctx.Get("printer").(*message.Printer)
ctx.UserSticker = h.usersStickers.Get(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: ctx.Sticker.ID,
})
markup := tg.NewInlineKeyboardMarkup(tg.NewInlineKeyboardRow(
tg.NewInlineKeyboardButton("🔥 Remove this sticker", common.DataDel),
))
if (ctx.Request.IsCallbackQuery() && ctx.Request.CallbackQuery.Data == common.DataDelSet) ||
ctx.UserSticker == nil {
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),
))
@ -35,8 +31,7 @@ func (h *Handler) GetStickerKeyboard(ctx *model.Context) *tg.InlineKeyboardMarku
tg.NewInlineKeyboardButton(p.Sprintf("📥 Import %s set", setName), common.DataAddSet),
))
if (ctx.Request.IsCallbackQuery() && ctx.Request.CallbackQuery.Data == common.DataAddSet) ||
ctx.UserSticker != nil {
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,
)
@ -49,13 +44,16 @@ func (h *Handler) GetStickerKeyboard(ctx *model.Context) *tg.InlineKeyboardMarku
// CommandAddSticker import single Sticker by ReplyMessage.
// NOTE(toby3d): DEPRECATED, used for backward compatibility
func (h *Handler) CommandAddSticker(ctx *model.Context) (err error) {
if ctx.Sticker == nil || ctx.UserSticker != nil {
if ctx.HasSticker || ctx.Sticker == nil {
return nil
}
now := time.Now().UTC().Unix()
userSticker := new(model.UserSticker)
userSticker.UserID = ctx.User.ID
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()
@ -81,8 +79,8 @@ func (h *Handler) CommandEditSticker(ctx *model.Context) (err error) {
if !ctx.Request.Message.HasCommandArgument() {
p := ctx.Get("printer").(*message.Printer)
reply := tg.NewMessage(ctx.User.UserID, p.Sprintf("💡 Add any text and/or emoji(s) as an argument "+
"of this command to change its search properties."))
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
@ -92,26 +90,31 @@ func (h *Handler) CommandEditSticker(ctx *model.Context) (err error) {
return err
}
if ctx.UserSticker == nil {
if !ctx.HasSticker {
if err = h.CommandAddSticker(ctx); err != nil {
return err
}
}
ctx.UserSticker.UpdatedAt = ctx.Request.Message.Date
ctx.UserSticker.Query = ctx.Request.Message.CommandArgument()
return h.usersStickers.Update(ctx.UserSticker)
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.UserSticker == nil {
if ctx.Sticker == nil || !ctx.HasSticker {
return nil
}
return h.usersStickers.Remove(ctx.UserSticker)
return h.usersStickers.Remove(&model.UserSticker{
UserID: ctx.User.ID,
StickerID: ctx.Sticker.ID,
})
}
// CommandDelPack remove whole Sticker pack by ReplyMessage.

View File

@ -6,7 +6,7 @@ import (
tg "gitlab.com/toby3d/telegram"
)
func AcquirePhoto(store photos.Manager) Interceptor {
func AcquirePhoto(store photos.ReadWriter) Interceptor {
return func(ctx *model.Context, next model.UpdateFunc) (err error) {
switch {
case ctx.Request.IsMessage():
@ -46,9 +46,10 @@ func AcquirePhoto(store photos.Manager) Interceptor {
func photoToModel(photoSize tg.Photo) *model.Photo {
p := photoSize[len(photoSize)-1]
photo := new(model.Photo)
photo.FileID = p.FileID
photo.ID = p.FileUniqueID
photo.Width = p.Width
photo.Height = p.Height
photo.FileID = p.FileID
return photo
}

View File

@ -1,6 +1,8 @@
package middleware
import (
"time"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"gitlab.com/toby3d/mypackbot/internal/model/stickers"
@ -50,12 +52,13 @@ func AcquireSticker(store stickers.Manager) Interceptor {
func stickerToModel(s *tg.Sticker) *model.Sticker {
sticker := new(model.Sticker)
sticker.FileID = s.FileID
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
@ -67,7 +70,7 @@ func stickerToModel(s *tg.Sticker) *model.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.GetSet(ctx.Sticker.SetName)
stickers, _, _ := store.GetList(0, 0, &model.Sticker{SetName: ctx.Sticker.SetName})
ctx.Sticker.SetName = common.SetNameUploaded
go func() {
@ -80,14 +83,17 @@ func migrateSet(ctx *model.Context, store stickers.Manager) {
} else {
ctx.Set("set_name", tgSet.Title)
dbSet, _ := store.GetSet(tgSet.Name)
for i := range tgSet.Stickers {
for j := range dbSet {
if tgSet.Stickers[i].FileID == dbSet[j].FileID {
for _, sticker := range store.GetSet(tgSet.Name) {
if sticker.ID == tgSet.Stickers[i].FileUniqueID {
continue
}
_ = store.Create(stickerToModel(tgSet.Stickers[i]))
now := time.Now().UTC().Unix()
s := stickerToModel(tgSet.Stickers[i])
s.CreatedAt, s.UpdatedAt = now, now
_, _ = store.GetOrCreate(s)
}
}
}

View File

@ -7,26 +7,26 @@ import (
"gitlab.com/toby3d/mypackbot/internal/model/users"
)
func AcquireUser(us users.Manager) Interceptor {
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.UserID = int64(ctx.Request.Message.From.ID)
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.UserID = int64(ctx.Request.InlineQuery.From.ID)
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.UserID = int64(ctx.Request.CallbackQuery.From.ID)
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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import (
"sort"
"strconv"
"strings"
"time"
json "github.com/json-iterator/go"
bunt "github.com/tidwall/buntdb"
@ -25,7 +26,7 @@ type (
}
Record struct {
UserID int64
UserID int
SetName string
FileID string
Emoji string
@ -34,7 +35,7 @@ type (
Records []*Record
Backup struct {
Users []int64 `json:"users"`
Users []int `json:"users"`
Stickers []string `json:"stickers"`
ImportedSets []string `json:"imported_sets"`
BlockedSets []string `json:"blocked_sets"`
@ -43,7 +44,7 @@ type (
AutoMigrateConfig struct {
OldDB *bunt.DB
Stickers stickers.Manager
UsersStickers usersstickers.Manager
UsersStickers usersstickers.ReadWriter
Users users.Manager
Bot *tg.Bot
GroupID int64
@ -95,7 +96,7 @@ func (cfg *AutoMigrateConfig) importOldData() (data *Data, err error) {
// NOTE(toby3d): this part always contains user/chat id
var err error
r.UserID, err = strconv.ParseInt(parts[1], 10, 64)
r.UserID, err = strconv.Atoi(parts[1])
if err != nil || r.UserID == 0 || !strings.EqualFold(parts[2], partSet) {
return true
}
@ -151,9 +152,13 @@ func (cfg *AutoMigrateConfig) migrateUsers(data *Data) (err error) {
continue
}
now := time.Now().UTC().Unix()
_ = cfg.Users.Create(&model.User{
UserID: data.Records[i].UserID,
ID: data.Records[i].UserID,
LanguageCode: "en",
CreatedAt: now,
UpdatedAt: now,
})
data.Users = append(data.Users, data.Records[i].UserID)
@ -197,7 +202,7 @@ func (cfg *AutoMigrateConfig) migrateSets(data *Data) (err error) {
_ = cfg.Stickers.Create(stickerToModel(setSticker))
}
u := cfg.Users.GetByUserID(data.Records[i].UserID)
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)
@ -243,8 +248,8 @@ func (cfg *AutoMigrateConfig) migrateStickers(data *Data) (err error) {
// NOTE(toby3d): store old-new stickers
_ = cfg.Stickers.Create(s)
u := cfg.Users.GetByUserID(data.Records[i].UserID)
s = cfg.Stickers.GetByFileID(s.FileID)
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,
@ -258,7 +263,7 @@ func (cfg *AutoMigrateConfig) migrateStickers(data *Data) (err error) {
return nil
}
func containsInt(src []int64, find int64) bool {
func containsInt(src []int, find int) bool {
for i := range src {
if src[i] != find {
continue

View File

@ -13,11 +13,12 @@ type (
*tg.Bot
Request *tg.Update
User *User
Sticker *Sticker
Photo *Photo
UserPhoto *UserPhoto
UserSticker *UserSticker
User *User
Sticker *Sticker
HasSticker bool
HasSet bool
Photo *Photo
HasPhoto bool
userValues context.Context
}

View File

@ -4,64 +4,91 @@ import (
"strings"
"gitlab.com/toby3d/mypackbot/internal/common"
tg "gitlab.com/toby3d/telegram"
)
type (
Model struct {
ID uint64 `json:"id"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
User struct {
Model
UserID int64 `json:"user_id"`
LanguageCode string `json:"language_code"`
LastSeen int64 `json:"last_seen"`
ID int `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
LanguageCode string
LastSeen int64
}
Users []*User
Sticker struct {
Model
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
IsAnimated bool `json:"is_animated"`
SetName string `json:"set_name"`
Emoji string `json:"emoji"`
ID string `boltholdKey:"ID"`
CreatedAt int64
UpdatedAt int64
FileID string
Width int
Height int
IsAnimated bool
SetName string
Emoji string
}
Stickers []*Sticker
UserSticker struct {
Model
StickerID uint64 `json:"sticker_id"`
UserID uint64 `json:"user_id"`
Query string `json:"query"`
}
UserStickers []*UserSticker
Photo struct {
Model
FileID string `json:"file_id"`
Width int `json:"width"`
Height int `json:"height"`
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 {
Model
PhotoID uint64 `json:"photo_id"`
UserID uint64 `json:"user_id"`
Query string `json:"query"`
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

@ -2,12 +2,25 @@ package photos
import "gitlab.com/toby3d/mypackbot/internal/model"
type Manager interface {
Create(*model.Photo) error
Get(uint64) *model.Photo
GetByFileID(string) *model.Photo
GetList(int, int) (model.Photos, int)
GetOrCreate(*model.Photo) (*model.Photo, error)
Remove(uint64) error
Update(*model.Photo) error
}
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

@ -2,13 +2,26 @@ package stickers
import "gitlab.com/toby3d/mypackbot/internal/model"
type Manager interface {
Create(*model.Sticker) error
Get(uint64) *model.Sticker
GetByFileID(string) *model.Sticker
GetList(int, int) (model.Stickers, int)
GetOrCreate(*model.Sticker) (*model.Sticker, error)
GetSet(string) (model.Stickers, int)
Remove(uint64) error
Update(*model.Sticker) error
}
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

@ -2,10 +2,20 @@ package photos
import "gitlab.com/toby3d/mypackbot/internal/model"
type Manager interface {
Add(*model.UserPhoto) error
Get(*model.UserPhoto) *model.UserPhoto
GetList(uint64, int, int, string) (model.Photos, int)
Remove(*model.UserPhoto) error
Update(*model.UserPhoto) error
}
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

@ -2,13 +2,22 @@ package stickers
import "gitlab.com/toby3d/mypackbot/internal/model"
type Manager interface {
Add(*model.UserSticker) error
AddSet(uint64, string) error
Get(*model.UserSticker) *model.UserSticker
GetList(uint64, int, int, string) (model.Stickers, int)
GetSet(uint64, int, int, string) (model.Stickers, int)
Remove(*model.UserSticker) error
RemoveSet(uint64, string) error
Update(*model.UserSticker) error
}
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

@ -2,10 +2,24 @@ package users
import "gitlab.com/toby3d/mypackbot/internal/model"
type Manager interface {
Create(*model.User) error
Get(uint64) *model.User
GetByUserID(int64) *model.User
GetOrCreate(*model.User) (*model.User, error)
Update(*model.User) error
}
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,7 +1,6 @@
package internal
import (
json "github.com/json-iterator/go"
"github.com/spf13/viper"
"gitlab.com/toby3d/mypackbot/internal/config"
"gitlab.com/toby3d/mypackbot/internal/db"
@ -20,8 +19,8 @@ type MyPackBot struct {
photos photos.Manager
stickers stickers.Manager
users users.Manager
usersPhotos usersphotos.Manager
usersStickers usersstickers.Manager
usersPhotos usersphotos.ReadWriter
usersStickers usersstickers.ReadWriter
}
func New(path string) (mpb *MyPackBot, err error) {
@ -36,12 +35,11 @@ func New(path string) (mpb *MyPackBot, err error) {
return nil, err
}
marshler := json.ConfigFastest
mpb.photos = store.NewPhotosStore(conn, marshler)
mpb.stickers = store.NewStickersStore(conn, marshler)
mpb.users = store.NewUsersStore(conn, marshler)
mpb.usersPhotos = store.NewUsersPhotosStore(conn, mpb.users, mpb.photos, marshler)
mpb.usersStickers = store.NewUsersStickersStore(conn, mpb.users, mpb.stickers, marshler)
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

View File

@ -6,6 +6,7 @@ import (
"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"
@ -24,27 +25,41 @@ func (mpb *MyPackBot) Run() error {
middleware.AcquireUser(mpb.users),
middleware.AcquirePrinter(),
middleware.ChatAction(),
middleware.AcquirePhoto(mpb.photos),
middleware.AcquireUserPhoto(mpb.usersPhotos),
middleware.AcquireSticker(mpb.stickers),
middleware.AcquireUserSticker(mpb.usersStickers),
middleware.Birthday(time.Date(0, time.November, 4, 0, 0, 0, 0, time.UTC)),
middleware.Hacktober(),
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,
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 {
if err = h(ctx); err != nil {
dlog.D(err)
}
}

View File

@ -1,226 +1,97 @@
package store
import (
"sort"
"strconv"
"time"
bolt "github.com/etcd-io/bbolt"
json "github.com/json-iterator/go"
"github.com/valyala/fastjson"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"golang.org/x/xerrors"
bolt "go.etcd.io/bbolt"
)
type PhotosStore struct {
conn *bolt.DB
marshler json.API
parser fastjson.Parser
conn *bolthold.Store
}
var (
ErrPhotoInvalid = model.Error{
Message: "Invalid photo",
}
ErrPhotoExist = model.Error{
Message: "Photo already exist",
}
ErrPhotoNotExist = model.Error{
Message: "Photo not exist",
}
)
func NewPhotosStore(conn *bolt.DB, marshler json.API) *PhotosStore {
return &PhotosStore{
conn: conn,
marshler: marshler,
parser: fastjson.Parser{},
}
func NewPhotosStore(conn *bolthold.Store) *PhotosStore {
return &PhotosStore{conn: conn}
}
func (store *PhotosStore) Create(p *model.Photo) error {
if p == nil || p.FileID == "" {
return ErrPhotoInvalid
if store.Get(p.ID) != nil {
return bolthold.ErrKeyExists
}
if store.Get(p.ID) != nil || store.GetByFileID(p.FileID) != nil {
return ErrPhotoExist
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
}
now := time.Now().UTC().Unix()
return result
}
if p.CreatedAt <= 0 {
p.CreatedAt = now
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 p.UpdatedAt <= 0 {
p.UpdatedAt = now
if limit > 0 {
q = q.Limit(limit)
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
// 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 p.ID, err = bkt.NextSequence(); err != nil {
if count, err = store.conn.CountInBucket(bkt, &model.Photo{}, q); err != nil {
return err
}
src, err := store.marshler.Marshal(p)
if err != nil {
return err
}
return bkt.Put([]byte(strconv.FormatUint(p.ID, 10)), src)
})
}
func (store *PhotosStore) Get(id uint64) *model.Photo {
p := new(model.Photo)
if err := store.conn.View(func(tx *bolt.Tx) error {
return store.marshler.Unmarshal(
tx.Bucket(common.BucketPhotos).Get([]byte(strconv.FormatUint(id, 10))), p,
)
}); err != nil || p.ID == 0 {
return nil
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return list, count, err
}
return p
}
func (store *PhotosStore) GetByFileID(id string) *model.Photo {
p := new(model.Photo)
if err := store.conn.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(common.BucketPhotos).ForEach(func(key, val []byte) error {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if string(v.GetStringBytes("file_id")) != id {
return nil
}
if err = store.marshler.Unmarshal(val, p); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
}); err != nil || p.ID == 0 {
return nil
}
return p
}
func (store *PhotosStore) GetList(offset, limit int) (model.Photos, int) {
if limit <= 0 {
limit = 0
}
count := 0
photos := make(model.Photos, 0, limit)
_ = store.conn.View(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketPhotos).ForEach(func(key, val []byte) (err error) {
count++
if count <= offset || (limit > 0 && count > offset+limit) {
return nil
}
p := new(model.Photo)
if err = store.marshler.Unmarshal(val, p); err != nil {
return err
}
photos = append(photos, p)
return nil
})
})
sort.Slice(photos, func(i, j int) bool {
return photos[i].UpdatedAt < photos[j].UpdatedAt
})
return photos, count
return list, count, err
}
func (store *PhotosStore) Update(p *model.Photo) error {
if p == nil || p.FileID == "" {
return ErrPhotoInvalid
}
if store.Get(p.ID) == nil && store.GetByFileID(p.FileID) == nil {
if store.Get(p.ID) == nil {
return store.Create(p)
}
if p.UpdatedAt <= 0 {
p.UpdatedAt = time.Now().UTC().Unix()
}
src, err := store.marshler.Marshal(p)
if err != nil {
return err
}
return store.conn.Update(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketPhotos).Put([]byte(strconv.FormatUint(p.ID, 10)), src)
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 uint64) error {
func (store *PhotosStore) Remove(id string) error {
if store.Get(id) == nil {
return ErrPhotoNotExist
return bolthold.ErrNotFound
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
if err = tx.Bucket(common.BucketPhotos).Delete([]byte(strconv.FormatUint(id, 10))); err != nil {
return err
}
bkt := tx.Bucket(common.BucketUsersPhotos)
if err = bkt.ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("photo_id") != id {
return nil
}
if err = bkt.Delete(key); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
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) (photo *model.Photo, err error) {
if photo = store.GetByFileID(p.FileID); photo != nil {
func (store *PhotosStore) GetOrCreate(p *model.Photo) (*model.Photo, error) {
if photo := store.Get(p.ID); photo != nil {
return photo, nil
}
if photo = store.Get(p.ID); photo != nil {
return photo, nil
}
if err = store.Create(p); err != nil {
if err := store.Create(p); err != nil {
return nil, err
}

View File

@ -1,271 +1,112 @@
package store
import (
"sort"
"strconv"
"strings"
"time"
bolt "github.com/etcd-io/bbolt"
json "github.com/json-iterator/go"
"github.com/valyala/fastjson"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"golang.org/x/xerrors"
bolt "go.etcd.io/bbolt"
)
type StickersStore struct {
conn *bolt.DB
marshler json.API
parser fastjson.Parser
conn *bolthold.Store
}
var (
ErrStickerInvalid = model.Error{
Message: "Invalid sticker",
}
ErrStickerExist = model.Error{
Message: "Sticker already exist",
}
ErrStickerNotExist = model.Error{
Message: "Sticker not exist",
}
)
func NewStickersStore(conn *bolt.DB, marshler json.API) *StickersStore {
return &StickersStore{
conn: conn,
marshler: marshler,
parser: fastjson.Parser{},
}
func NewStickersStore(conn *bolthold.Store) *StickersStore {
return &StickersStore{conn: conn}
}
func (store *StickersStore) Create(s *model.Sticker) error {
if s == nil || s.FileID == "" {
return ErrStickerInvalid
if store.Get(s.ID) != nil {
return bolthold.ErrKeyExists
}
if store.Get(s.ID) != nil || store.GetByFileID(s.FileID) != nil {
return ErrStickerExist
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
}
now := time.Now().UTC().Unix()
return result
}
if s.CreatedAt <= 0 {
s.CreatedAt = now
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 s.UpdatedAt <= 0 {
s.UpdatedAt = now
if limit > 0 {
q = q.Limit(limit)
}
if s.SetName == "" {
s.SetName = common.SetNameUploaded
if filter != nil {
if filter.Emoji != "" {
q = q.And("Emoji").ContainsAny(filter.Emoji)
}
if filter.SetName != "" {
q = q.And("SetName").Eq(filter.SetName)
}
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
list = make(model.Stickers, limit)
if err := store.conn.Bolt().View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketStickers)
if s.ID, err = bkt.NextSequence(); err != nil {
if count, err = store.conn.CountInBucket(bkt, &model.Sticker{}, q); err != nil {
return err
}
src, err := store.marshler.Marshal(s)
if err != nil {
return err
}
return bkt.Put([]byte(strconv.FormatUint(s.ID, 10)), src)
})
}
func (store *StickersStore) Get(id uint64) *model.Sticker {
s := new(model.Sticker)
if err := store.conn.View(func(tx *bolt.Tx) error {
return store.marshler.Unmarshal(
tx.Bucket(common.BucketStickers).Get([]byte(strconv.FormatUint(id, 10))), s,
)
}); err != nil || s.ID == 0 {
return nil
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return list, count, err
}
return s
}
func (store *StickersStore) GetByFileID(id string) *model.Sticker {
s := new(model.Sticker)
if err := store.conn.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(common.BucketStickers).ForEach(func(key, val []byte) error {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if string(v.GetStringBytes("file_id")) != id {
return nil
}
if err = store.marshler.Unmarshal(val, s); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
}); err != nil || s.ID == 0 {
return nil
}
return s
}
func (store *StickersStore) GetList(offset, limit int) (model.Stickers, int) {
if limit <= 0 {
limit = 0
}
count := 0
stickers := make(model.Stickers, 0, limit)
_ = store.conn.View(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketStickers).ForEach(func(key, val []byte) (err error) {
count++
if count <= offset || (limit > 0 && count > offset+limit) {
return nil
}
s := new(model.Sticker)
if err = store.marshler.Unmarshal(val, s); err != nil {
return err
}
stickers = append(stickers, s)
return nil
})
})
sort.Slice(stickers, func(i, j int) bool {
return stickers[i].SetName < stickers[j].SetName || stickers[i].UpdatedAt < stickers[j].UpdatedAt
})
return stickers, count
}
func (store *StickersStore) GetSet(name string) (model.Stickers, int) {
count := 0
stickers := make(model.Stickers, 0)
_ = store.conn.View(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketStickers).ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if !strings.EqualFold(string(v.GetStringBytes("set_name")), name) {
return nil
}
count++
s := new(model.Sticker)
if err = store.marshler.Unmarshal(val, s); err != nil {
return err
}
stickers = append(stickers, s)
return nil
})
})
sort.Slice(stickers, func(i, j int) bool {
return stickers[i].UpdatedAt < stickers[j].UpdatedAt
})
return stickers, count
return list, count, err
}
func (store *StickersStore) Update(s *model.Sticker) error {
if s == nil || s.FileID == "" {
return ErrStickerInvalid
}
if store.Get(s.ID) == nil && store.GetByFileID(s.FileID) == nil {
if store.Get(s.ID) == nil {
return store.Create(s)
}
if s.UpdatedAt <= 0 {
s.UpdatedAt = time.Now().UTC().Unix()
}
if s.SetName == "" {
s.SetName = common.SetNameUploaded
}
src, err := store.marshler.Marshal(s)
if err != nil {
return err
}
return store.conn.Update(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketStickers).Put([]byte(strconv.FormatUint(s.ID, 10)), src)
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 uint64) error {
func (store *StickersStore) Remove(id string) error {
if store.Get(id) == nil {
return ErrStickerNotExist
return bolthold.ErrNotFound
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
if err = tx.Bucket(common.BucketStickers).Delete([]byte(strconv.FormatUint(id, 10))); err != nil {
return err
}
bkt := tx.Bucket(common.BucketUsersStickers)
if err = bkt.ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("sticker_id") != id {
return nil
}
if err = bkt.Delete(key); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
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) (sticker *model.Sticker, err error) {
if sticker = store.GetByFileID(s.FileID); sticker != nil {
func (store *StickersStore) GetOrCreate(s *model.Sticker) (*model.Sticker, error) {
if sticker := store.Get(s.ID); sticker != nil {
return sticker, nil
}
if sticker = store.Get(s.ID); sticker != nil {
return sticker, nil
}
if err = store.Create(s); err != nil {
if err := store.Create(s); err != nil {
return nil, err
}

View File

@ -1,9 +1,7 @@
package store
import (
"errors"
"sort"
"time"
"gitlab.com/toby3d/mypackbot/internal/model"
usersphotos "gitlab.com/toby3d/mypackbot/internal/model/users/photos"
@ -13,97 +11,90 @@ import (
type (
Store struct {
usersStickers usersstickers.Manager
usersPhotos usersphotos.Manager
usersStickers usersstickers.ReadWriter
usersPhotos usersphotos.ReadWriter
}
Filter struct {
UserID int
AllowedTypes []string
Query string
Offset int
Limit int
IsPersonal bool
IsAnimated *bool
Width string
Height string
SetName string
}
)
// ErrForEachStop used in ForEach loops in database for forse stop iterations
var ErrForEachStop = errors.New("for each stop stop")
func (f *Filter) offset() int {
if len(f.AllowedTypes) == 0 || f.Offset <= len(f.AllowedTypes) {
return f.Offset
}
func NewStore(us usersstickers.Manager, up usersphotos.Manager) *Store {
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(uid uint64, f *Filter) ([]interface{}, int) {
results := make([]interface{}, 0)
count := 0
if len(f.AllowedTypes) == 0 {
return results, count
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}
}
for _, t := range f.AllowedTypes {
list = make([]model.InlineResult, 0)
for _, t := range filter.AllowedTypes {
switch t {
case tg.TypePhoto:
if f.IsAnimated != nil && *f.IsAnimated {
continue
}
photos, photosCount := store.usersPhotos.GetList(uid, 0, -1, f.Query)
for i := range photos {
results = append(results, photos[i])
}
count += photosCount
case tg.TypeSticker:
stickers, stickersCount := store.usersStickers.GetList(uid, 0, -1, f.Query)
for i := range stickers {
if f.IsAnimated != nil && stickers[i].IsAnimated != *f.IsAnimated ||
f.SetName != "" && stickers[i].SetName != f.SetName {
stickersCount--
continue
}
results = append(results, stickers[i])
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
}
count += stickersCount
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(results, func(i, j int) bool {
var a, b time.Time
switch result := results[i].(type) {
case *model.Sticker:
a = time.Unix(result.CreatedAt, 0)
case *model.Photo:
a = time.Unix(result.CreatedAt, 0)
}
switch result := results[j].(type) {
case *model.Sticker:
b = time.Unix(result.CreatedAt, 0)
case *model.Photo:
b = time.Unix(result.CreatedAt, 0)
}
return a.Before(b)
sort.Slice(list, func(i, j int) bool {
return list[i].GetUpdatedAt() < list[j].GetUpdatedAt()
})
if len(results) <= f.Offset {
return make([]interface{}, 0), count
}
if tail := len(results[f.Offset:]); tail < f.Limit {
return results[f.Offset : f.Offset+tail], count
}
return results[f.Offset : f.Offset+f.Limit-1], count
return list, count, err
}

View File

@ -1,152 +1,88 @@
package store
import (
"strconv"
"time"
bolt "github.com/etcd-io/bbolt"
json "github.com/json-iterator/go"
"github.com/valyala/fastjson"
"github.com/timshannon/bolthold"
"gitlab.com/toby3d/mypackbot/internal/common"
"gitlab.com/toby3d/mypackbot/internal/model"
"golang.org/x/xerrors"
bolt "go.etcd.io/bbolt"
)
type UsersStore struct {
conn *bolt.DB
marshler json.API
parser fastjson.Parser
conn *bolthold.Store
}
var (
ErrUserExist = model.Error{
Message: "User already exist",
}
ErrUserNotExist = model.Error{
Message: "User not exist",
}
)
func NewUsersStore(conn *bolt.DB, marshler json.API) *UsersStore {
return &UsersStore{
conn: conn,
marshler: marshler,
parser: fastjson.Parser{},
}
func NewUsersStore(conn *bolthold.Store) *UsersStore {
return &UsersStore{conn: conn}
}
func (store *UsersStore) Create(u *model.User) error {
if store.Get(u.ID) != nil || store.GetByUserID(u.UserID) != nil {
return ErrUserExist
if store.Get(u.ID) != nil {
return bolthold.ErrKeyExists
}
now := time.Now().UTC().Unix()
if u.CreatedAt <= 0 {
u.CreatedAt = now
}
if u.UpdatedAt <= 0 {
u.UpdatedAt = now
}
if u.LastSeen <= 0 {
u.LastSeen = now
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsers)
if u.ID, err = bkt.NextSequence(); err != nil {
return err
}
src, err := store.marshler.Marshal(u)
if err != nil {
return err
}
return bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), src)
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 uint64) *model.User {
u := new(model.User)
func (store *UsersStore) Get(id int) *model.User {
result := new(model.User)
if err := store.conn.View(func(tx *bolt.Tx) error {
return store.marshler.Unmarshal(
tx.Bucket(common.BucketUsers).Get([]byte(strconv.FormatUint(id, 10))), u,
)
}); err != nil || u.ID == 0 {
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 u
return result
}
func (store *UsersStore) GetByUserID(id int64) *model.User {
u := new(model.User)
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 err := store.conn.View(func(tx *bolt.Tx) error {
if err := tx.Bucket(common.BucketUsers).ForEach(func(key, val []byte) error {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if offset > 0 {
q = q.Skip(offset)
}
if v.GetInt64("user_id") != id {
return nil
}
if limit > 0 {
q = q.Limit(limit)
}
if err = store.marshler.Unmarshal(val, u); err != nil {
return err
}
// TODO(toby3d): implement filter here
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
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 nil
}); err != nil || u.ID == 0 {
return nil
return store.conn.FindInBucket(bkt, &list, q)
}); err != nil {
return nil, 0, err
}
return u
return list, count, err
}
func (store *UsersStore) Update(u *model.User) error {
if store.Get(u.ID) == nil && store.GetByUserID(u.UserID) == nil {
if store.Get(u.ID) == nil {
return store.Create(u)
}
if u.UpdatedAt <= 0 {
u.UpdatedAt = time.Now().UTC().Unix()
}
src, err := store.marshler.Marshal(u)
if err != nil {
return err
}
return store.conn.Update(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketUsers).Put([]byte(strconv.FormatUint(u.ID, 10)), src)
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) (user *model.User, err error) {
if user = store.Get(u.ID); user != nil {
func (store *UsersStore) GetOrCreate(u *model.User) (*model.User, error) {
if user := store.Get(u.ID); user != nil {
return user, nil
}
if user = store.GetByUserID(u.UserID); user != nil {
return user, nil
}
if err = store.Create(u); err != nil {
if err := store.Create(u); err != nil {
return nil, err
}
return store.GetOrCreate(u)
return u, nil
}

View File

@ -1,228 +1,133 @@
package store
import (
"sort"
"strconv"
"strings"
"time"
bolt "github.com/etcd-io/bbolt"
json "github.com/json-iterator/go"
"github.com/valyala/fastjson"
"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 *bolt.DB
marshler json.API
parser fastjson.Parser
photos photos.Manager
users users.Manager
conn *bolthold.Store
photos photos.Reader
users users.Reader
}
var (
ErrUserPhotoExist = model.Error{
Message: "Photo already imported",
}
ErrUserPhotoNotExist = model.Error{
Message: "Photo already removed",
}
)
func NewUsersPhotosStore(conn *bolt.DB, us users.Manager, ps photos.Manager, marshler json.API) *UsersPhotosStore {
func NewUsersPhotosStore(conn *bolthold.Store, us users.Reader, ps photos.Reader) *UsersPhotosStore {
return &UsersPhotosStore{
conn: conn,
marshler: marshler,
parser: fastjson.Parser{},
photos: ps,
users: us,
conn: conn,
photos: ps,
users: us,
}
}
func (store *UsersPhotosStore) Add(up *model.UserPhoto) (err error) {
if up == nil || up.UserID == 0 || up.PhotoID == 0 {
return nil
if store.users.Get(up.UserID) == nil || store.photos.Get(up.PhotoID) == nil {
return bolthold.ErrNotFound
}
userPhoto := store.Get(up)
if userPhoto != nil {
return ErrUserPhotoExist
}
timeStamp := time.Now().UTC().Unix()
if up.CreatedAt == 0 {
up.CreatedAt = timeStamp
}
if up.UpdatedAt == 0 {
up.UpdatedAt = timeStamp
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsersPhotos)
up.ID, err = bkt.NextSequence()
if err != nil {
return err
}
src, err := store.marshler.Marshal(up)
if err != nil {
return err
}
return bkt.Put([]byte(strconv.FormatUint(up.ID, 10)), src)
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) {
if up == nil || up.UserID == 0 || up.PhotoID == 0 {
return nil
}
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")
}
userPhoto := store.Get(up)
if userPhoto == nil {
return store.Add(up)
}
result.Query, result.UpdatedAt = up.Query, up.UpdatedAt
if up.ID == 0 {
up.ID = userPhoto.ID
}
if up.CreatedAt == 0 {
up.CreatedAt = userPhoto.CreatedAt
}
if up.UpdatedAt <= userPhoto.UpdatedAt {
up.UpdatedAt = time.Now().UTC().Unix()
}
src, err := store.marshler.Marshal(up)
if err != nil {
return err
}
return store.conn.Update(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketUsersPhotos).Put([]byte(strconv.FormatUint(up.ID, 10)), src)
return nil
})
})
}
func (store *UsersPhotosStore) Get(up *model.UserPhoto) *model.UserPhoto {
if up == nil || up.UserID == 0 || up.PhotoID == 0 {
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
}
userPhoto := new(model.UserPhoto)
if err := store.conn.View(func(tx *bolt.Tx) (err error) {
if err = tx.Bucket(common.BucketUsersPhotos).ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
return store.photos.Get(result.PhotoID)
}
if v.GetUint64("user_id") != up.UserID || v.GetUint64("photo_id") != up.PhotoID {
return nil
}
func (store *UsersPhotosStore) GetList(offset, limit int, filter *model.UserPhoto) (list model.Photos, count int,
err error) {
if err = store.marshler.Unmarshal(val, userPhoto); err != nil {
return err
}
q := bolthold.Where("UserID").Ne(0).And("PhotoID").Ne("")
qCount := bolthold.Where("UserID").Ne(0).And("PhotoID").Ne("")
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
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 nil
}); err != nil || userPhoto.PhotoID == 0 || userPhoto.UserID == 0 {
return nil
return store.conn.FindInBucket(bkt, &results, q)
}); err != nil {
return nil, 0, err
}
return userPhoto
}
list = make(model.Photos, 0)
func (store *UsersPhotosStore) GetList(uid uint64, offset, limit int, query string) (model.Photos, int) {
if limit <= 0 {
limit = 0
for i := range results {
list = append(list, store.photos.Get(results[i].PhotoID))
}
count := 0
photos := make(model.Photos, 0, limit)
_ = store.conn.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketPhotos)
return tx.Bucket(common.BucketUsersPhotos).ForEach(func(key, val []byte) error {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("user_id") != uid {
return nil
}
if query != "" && !strings.ContainsAny(string(v.GetStringBytes("query")), query) {
return nil
}
count++
if (offset != 0 && count <= offset) || (limit > 0 && count > offset+limit) {
return nil
}
p := new(model.Photo)
if err = store.marshler.Unmarshal(
bkt.Get([]byte(strconv.FormatUint(v.GetUint64("photo_id"), 10))), p,
); err != nil {
return err
}
photos = append(photos, p)
return nil
})
})
sort.Slice(photos, func(i, j int) bool {
return photos[i].UpdatedAt < photos[j].UpdatedAt
})
return photos, count
return list, count, err
}
func (store *UsersPhotosStore) Remove(up *model.UserPhoto) (err error) {
userPhoto := store.Get(up)
if userPhoto == nil {
return ErrUserPhotoNotExist
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsersPhotos)
if err = bkt.ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("user_id") != up.UserID || v.GetUint64("photo_id") != up.PhotoID {
return nil
}
if err = bkt.Delete(key); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
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

@ -1,306 +1,169 @@
package store
import (
"sort"
"strconv"
"strings"
"time"
bolt "github.com/etcd-io/bbolt"
json "github.com/json-iterator/go"
"github.com/valyala/fastjson"
"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 *bolt.DB
marshler json.API
parser fastjson.Parser
stickers stickers.Manager
users users.Manager
conn *bolthold.Store
stickers stickers.Reader
users users.Reader
}
var (
ErrUserStickerExist = model.Error{
Message: "Sticker already imported",
}
ErrUserStickerNotExist = model.Error{
Message: "Sticker already removed",
}
)
func NewUsersStickersStore(db *bolt.DB, us users.Manager, ss stickers.Manager, m json.API) *UsersStickersStore {
func NewUsersStickersStore(conn *bolthold.Store, us users.Reader, ss stickers.Reader) *UsersStickersStore {
return &UsersStickersStore{
conn: db,
marshler: m,
parser: fastjson.Parser{},
conn: conn,
stickers: ss,
users: us,
}
}
func (store *UsersStickersStore) Add(us *model.UserSticker) (err error) {
if us == nil || us.UserID == 0 || us.StickerID == 0 {
return nil
func (store *UsersStickersStore) Add(us *model.UserSticker) error {
if store.users.Get(us.UserID) == nil || store.stickers.Get(us.StickerID) == nil {
return bolthold.ErrNotFound
}
userSticker := store.Get(us)
if userSticker != nil {
return ErrUserStickerExist
}
timeStamp := time.Now().UTC().Unix()
if us.CreatedAt == 0 {
us.CreatedAt = timeStamp
}
if us.UpdatedAt == 0 {
us.UpdatedAt = timeStamp
}
return store.conn.Update(func(tx *bolt.Tx) (err error) {
bkt := tx.Bucket(common.BucketUsersStickers)
us.ID, err = bkt.NextSequence()
if err != nil {
return err
}
src, err := store.marshler.Marshal(us)
if err != nil {
return err
}
return bkt.Put([]byte(strconv.FormatUint(us.ID, 10)), src)
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 uint64, setName string) (err error) {
set, _ := store.stickers.GetSet(setName)
for i := range set {
_ = store.Add(&model.UserSticker{
UserID: uid,
StickerID: set[i].ID,
})
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 err
return nil
}
func (store *UsersStickersStore) Update(us *model.UserSticker) (err error) {
if us == nil || us.UserID == 0 || us.StickerID == 0 {
return nil
}
userSticker := store.Get(us)
if userSticker == nil {
return store.Add(us)
}
if us.ID == 0 {
us.ID = userSticker.ID
}
if us.CreatedAt == 0 {
us.CreatedAt = userSticker.CreatedAt
}
if us.UpdatedAt <= userSticker.UpdatedAt {
us.UpdatedAt = time.Now().UTC().Unix()
}
src, err := store.marshler.Marshal(us)
if err != nil {
return err
}
return store.conn.Update(func(tx *bolt.Tx) error {
return tx.Bucket(common.BucketUsersStickers).Put([]byte(strconv.FormatUint(us.ID, 10)), src)
})
}
func (store *UsersStickersStore) Get(us *model.UserSticker) *model.UserSticker {
if us == nil || us.UserID == 0 || us.StickerID == 0 {
return nil
}
userSticker := new(model.UserSticker)
if err := store.conn.View(func(tx *bolt.Tx) (err error) {
if err = tx.Bucket(common.BucketUsersStickers).ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("user_id") != us.UserID || v.GetUint64("sticker_id") != us.StickerID {
return nil
}
if err = store.marshler.Unmarshal(val, userSticker); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
return err
}
return nil
}); err != nil || userSticker.UserID == 0 || userSticker.StickerID == 0 {
return nil
}
return userSticker
}
//nolint: gocognit
func (store *UsersStickersStore) GetList(uid uint64, offset, limit int, q string) (list model.Stickers, count int) {
if limit <= 0 {
limit = 0
}
list = make(model.Stickers, 0, limit)
_ = store.conn.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketStickers)
return tx.Bucket(common.BucketUsersStickers).ForEach(func(key, val []byte) error {
v, err := store.parser.ParseBytes(val)
if err != nil || v.GetUint64("user_id") != uid {
return err
}
src := bkt.Get([]byte(strconv.FormatUint(v.GetUint64("sticker_id"), 10)))
if q != "" {
vQuery := string(v.GetStringBytes("query"))
switch {
case vQuery != "" && !strings.ContainsAny(vQuery, q):
return nil
case vQuery == "":
s, err := store.parser.ParseBytes(src)
if err != nil || !strings.ContainsAny(string(s.GetStringBytes("emoji")), q) {
return err
}
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")
}
}
if (offset != 0 && count <= offset) || (limit > 0 && count > offset+limit) {
result.Query, result.UpdatedAt = us.Query, us.UpdatedAt
return nil
}
count++
s := new(model.Sticker)
if err = store.marshler.Unmarshal(src, s); err != nil {
return err
}
list = append(list, s)
return nil
})
})
})
sort.Slice(list, func(i, j int) bool {
return list[i].SetName < list[j].SetName || list[i].UpdatedAt < list[j].UpdatedAt
})
return list, count
}
func (store *UsersStickersStore) GetSet(uid uint64, offset, limit int, setName string) (model.Stickers, int) {
count := 0
stickers := make(model.Stickers, 0, limit)
func (store *UsersStickersStore) Get(us *model.UserSticker) *model.Sticker {
result := new(model.UserSticker)
_ = store.conn.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(common.BucketStickers)
return tx.Bucket(common.BucketUsersStickers).ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if v.GetUint64("user_id") != uid {
return nil
}
src := bkt.Get([]byte(strconv.FormatUint(v.GetUint64("sticker_id"), 10)))
if v, err = store.parser.ParseBytes(src); err != nil {
return err
}
if !strings.EqualFold(string(v.GetStringBytes("set_name")), setName) {
return nil
}
count++
if count <= offset || count > limit {
return nil
}
s := new(model.Sticker)
if err = store.marshler.Unmarshal(src, s); err != nil {
return err
}
stickers = append(stickers, s)
return nil
})
})
sort.Slice(stickers, func(i, j int) bool {
return stickers[i].UpdatedAt < stickers[j].UpdatedAt
})
return stickers, count
}
func (store *UsersStickersStore) Remove(us *model.UserSticker) (err error) {
userSticker := store.Get(us)
if userSticker == nil {
return ErrUserStickerNotExist
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.conn.Update(func(tx *bolt.Tx) (err error) {
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 err = bkt.ForEach(func(key, val []byte) (err error) {
v, err := store.parser.ParseBytes(val)
if err != nil {
return err
}
if userSticker.UserID != v.GetUint64("user_id") ||
(userSticker.StickerID != 0 && userSticker.StickerID != v.GetUint64("sticker_id")) {
return nil
}
if err = bkt.Delete(key); err != nil {
return err
}
return ErrForEachStop
}); err != nil && !xerrors.Is(err, ErrForEachStop) {
if count, err = store.conn.CountInBucket(bkt, &model.UserSticker{}, qCount); err != nil {
return err
}
return nil
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 uint64, setName string) (err error) {
set, _ := store.stickers.GetSet(setName)
for i := range set {
_ = store.Remove(&model.UserSticker{
UserID: uid,
StickerID: set[i].ID,
})
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 err
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,48 +1,6 @@
{
"language": "en",
"messages": [
{
"id": "🥳 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!",
"message": "🥳 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!",
"translation": "🥳 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "☺️ 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!",
"message": "☺️ 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!",
"translation": "☺️ 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "💸 Make a donation",
"message": "💸 Make a donation",
"translation": "💸 Make a donation",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🤝 Use referral links",
"message": "🤝 Use referral links",
"translation": "🤝 Use referral links",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🕺 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!",
"message": "🕺 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!",
"translation": "🕺 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🔧 Let's hack!",
"message": "🔧 Let's hack!",
"translation": "🔧 Let's hack!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "👋 Hi {FullName}, I'm {FullName_1}!\nThanks to me, you can collect almost any media content in Telegram without any limits, in any chat via inline mode.",
"message": "👋 Hi {FullName}, I'm {FullName_1}!\nThanks to me, you can collect almost any media content in Telegram without any limits, in any chat via inline mode.",
@ -202,6 +160,48 @@
}
],
"fuzzy": true
},
{
"id": "🥳 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!",
"message": "🥳 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!",
"translation": "🥳 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "☺️ 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!",
"message": "☺️ 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!",
"translation": "☺️ 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "💸 Make a donation",
"message": "💸 Make a donation",
"translation": "💸 Make a donation",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🤝 Use referral links",
"message": "🤝 Use referral links",
"translation": "🤝 Use referral links",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🕺 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!",
"message": "🕺 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!",
"translation": "🕺 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!",
"translatorComment": "Copied from source.",
"fuzzy": true
},
{
"id": "🔧 Let's hack!",
"message": "🔧 Let's hack!",
"translation": "🔧 Let's hack!",
"translatorComment": "Copied from source.",
"fuzzy": true
}
]
}

View File

@ -1,36 +1,6 @@
{
"language": "ru",
"messages": [
{
"id": "🥳 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!",
"message": "🥳 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!",
"translation": "🥳 4-е Ноября? Это день рождения @toby3d!\nЕсли тебе нравится этот бот, то почему бы не отправить ему поздравления вместе с небольшим подарком? Это несказанно его осчастливит!"
},
{
"id": "☺️ 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!",
"message": "☺️ 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!",
"translation": "☺️ Ой, ты пропустил день рождения @toby3d 4-го Ноября!\nЕсли тебе нравится этот бот, то почему бы не отправить ему поздравления вместе с небольшим подарком? Ещё не слишком поздно его порадовать!"
},
{
"id": "💸 Make a donation",
"message": "💸 Make a donation",
"translation": "💸 Пожертвование"
},
{
"id": "🤝 Use referral links",
"message": "🤝 Use referral links",
"translation": "🤝 Реферальные ссылки"
},
{
"id": "🕺 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!",
"message": "🕺 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!",
"translation": "🕺 Хактоберфест уже здесь!\n\nЕсли ты начинающий или уже опытный golang-разработчик, то сейчас хорошее время помочь улучшить качество кода этого бота. Выбери issue на свой вкус и предложи PR!"
},
{
"id": "🔧 Let's hack!",
"message": "🔧 Let's hack!",
"translation": "🔧 Let's hack!"
},
{
"id": "👋 Hi {FullName}, I'm {FullName_1}!\nThanks to me, you can collect almost any media content in Telegram without any limits, in any chat via inline mode.",
"message": "👋 Hi {FullName}, I'm {FullName_1}!\nThanks to me, you can collect almost any media content in Telegram without any limits, in any chat via inline mode.",
@ -158,6 +128,36 @@
"expr": "setName"
}
]
},
{
"id": "🥳 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!",
"message": "🥳 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!",
"translation": "🥳 4-е Ноября? Это день рождения @toby3d!\nЕсли тебе нравится этот бот, то почему бы не отправить ему поздравления вместе с небольшим подарком? Это несказанно его осчастливит!"
},
{
"id": "☺️ 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!",
"message": "☺️ 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!",
"translation": "☺️ Ой, ты пропустил день рождения @toby3d 4-го Ноября!\nЕсли тебе нравится этот бот, то почему бы не отправить ему поздравления вместе с небольшим подарком? Ещё не слишком поздно его порадовать!"
},
{
"id": "💸 Make a donation",
"message": "💸 Make a donation",
"translation": "💸 Пожертвование"
},
{
"id": "🤝 Use referral links",
"message": "🤝 Use referral links",
"translation": "🤝 Реферальные ссылки"
},
{
"id": "🕺 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!",
"message": "🕺 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!",
"translation": "🕺 Хактоберфест уже здесь!\n\nЕсли ты начинающий или уже опытный golang-разработчик, то сейчас хорошее время помочь улучшить качество кода этого бота. Выбери issue на свой вкус и предложи PR!"
},
{
"id": "🔧 Let's hack!",
"message": "🔧 Let's hack!",
"translation": "🔧 Let's hack!"
}
]
}