From a33680d52700c9b34cf4c86a01bfb45438043c5d Mon Sep 17 00:00:00 2001 From: Artyom Belousov Date: Sat, 27 May 2023 22:10:31 +0300 Subject: [PATCH] Redis -> YDB --- .drone.yml | 4 + .gitignore | 6 +- assets/additional_cards.json | 1 - cmd/telegram/main.go | 82 ++++++--- cmd/vk/main.go | 47 +++-- docker-compose.yaml | 9 +- go.mod | 20 ++- go.sum | 258 +++++++++++++++++++++++---- internal/caching/cache.go | 100 +++++++++-- internal/caching/cache_test.go | 64 ------- internal/cardsinfo/fetcher.go | 1 - internal/cardsinfo/names.go | 5 - internal/cardsinfo/names_test.go | 15 -- internal/dicttranslate/find.go | 10 -- internal/dicttranslate/find_test.go | 21 --- internal/dicttranslate/match.go | 24 --- internal/dicttranslate/match_test.go | 59 ------ internal/scenario/scenario.go | 16 +- internal/scenario/scenario_test.go | 13 +- internal/scenario/test_cache.go | 10 +- internal/vk/handler.go | 5 +- 21 files changed, 448 insertions(+), 322 deletions(-) delete mode 100644 assets/additional_cards.json delete mode 100644 internal/caching/cache_test.go delete mode 100644 internal/dicttranslate/find.go delete mode 100644 internal/dicttranslate/find_test.go delete mode 100644 internal/dicttranslate/match.go delete mode 100644 internal/dicttranslate/match_test.go diff --git a/.drone.yml b/.drone.yml index 80ed901..cfedc2c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -20,6 +20,10 @@ steps: from_secret: vk_confirmation_string TG_TOKEN: from_secret: tg_token + YDB_CONNECTION_STRING: + from_secret: ydb_connection_string + YDB_ACCESS_TOKEN_CREDENTIALS: + from_secret: ydb_access_token_credentials commands: - apk update diff --git a/.gitignore b/.gitignore index 37ea85c..f808674 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,8 @@ vendor hosts .idea .venv -coverage.out \ No newline at end of file +coverage.out +.envrc +/my_token +/vk +/telegram \ No newline at end of file diff --git a/assets/additional_cards.json b/assets/additional_cards.json deleted file mode 100644 index 285bacb..0000000 --- a/assets/additional_cards.json +++ /dev/null @@ -1 +0,0 @@ -{"\u041d\u0435\u043f\u0440\u0435\u043a\u043b\u043e\u043d\u043d\u0430\u044f \u0412\u043e\u043b\u044f": "Adamant Will", "\u0410\u043d\u0433\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0418\u043d\u0442\u0435\u043d\u0434\u0430\u043d\u0442": "Angelic Quartermaster", "\u0412\u043e\u043e\u0440\u0443\u0436\u0438\u0442\u044c \u041a\u0430\u0442\u0430\u0440\u043e\u0432": "Arm the Cathars", "\u041f\u043b\u0430\u0442\u044c\u0435 \u041d\u0435\u0432\u0435\u0441\u0442\u044b": "Bride's Gown", "\u0422\u043e\u043b\u044c\u043a\u043e \u043f\u043e \u041f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u044e": "By Invitation Only", "\u041a\u043b\u0430\u0434\u0431\u0438\u0449\u0435\u043d\u0441\u043a\u0430\u044f \u0417\u0430\u0449\u0438\u0442\u043d\u0438\u0446\u0430": "Cemetery Protector", "\u041a\u0440\u0443\u0433 \u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f": "Circle of Confinement", "\u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435 \u0414\u043e\u043d\u0445\u0430\u0440\u0442\u0430": "Dawnhart Geist", "\u042d\u0441\u0442\u0432\u0430\u043b\u044c\u0434\u0441\u043a\u0438\u0439 \u0429\u0438\u0442\u043e\u0431\u043e\u0435\u0446": "Estwald Shieldbasher", "\u042f\u0440\u043e\u0441\u0442\u043d\u043e\u0435 \u0412\u043e\u0437\u043c\u0435\u0437\u0434\u0438\u0435": "Fierce Retribution", "\u041c\u0438\u043c\u043e\u043b\u0435\u0442\u043d\u044b\u0439 \u0414\u0443\u0445": "Fleeting Spirit", "\u041d\u0430\u0435\u0437\u0434\u043d\u0438\u043a \u043d\u0430 \u0413\u0440\u0438\u0444\u0435": "Gryff Rider", "\u041a\u0430\u0432\u0430\u043b\u0435\u0440\u0438\u044f \u0413\u0440\u0438\u0444\u043e\u0432\u043e\u0433\u043e \u041a\u0440\u044b\u043b\u0430": "Gryffwing Cavalry", "\u041e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u0430\u044f \u041e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0441\u0442\u044c": "Hallowed Haunting", "\u0426\u0430\u043f\u043b\u044f \u041d\u0430\u0434\u0435\u0436\u0434\u044b": "Heron of Hope", "\u0411\u043b\u0430\u0433\u043e\u0441\u043b\u043e\u0432\u0435\u043d\u043d\u043e\u0435 \u0426\u0430\u043f\u043b\u0435\u0439 \u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435": "Heron-Blessed Geist", "\u0418\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0439 \u041d\u0430\u0434\u0435\u0436\u0434 \u041f\u043e\u0441\u043b\u0443\u0448\u043d\u0438\u043a": "Hopeful Initiate", "\u0412\u0441\u043f\u044b\u0448\u043a\u0430 \u0424\u043e\u043d\u0430\u0440\u044f": "Lantern Flare", "\u041f\u043e\u0434\u043d\u044f\u0432\u0448\u0430\u044f \u041e\u043f\u043e\u043b\u0447\u0435\u043d\u0438\u0435": "Militia Rallier", "\u041e\u0431\u043c\u0430\u043d\u0449\u0438\u0446\u0430 \u0438\u0437 \u041d\u0435\u0431\u0435\u043b\u044c\u0433\u0430\u0441\u0442\u0430": "Nebelgast Beguiler", "\u0417\u0430\u0431\u043e\u0442\u043b\u0438\u0432\u043e\u0435 \u041f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435": "Nurturing Presence", "\u041e\u043b\u043b\u0435\u043d\u0431\u043e\u043a\u0441\u043a\u0430\u044f \u0421\u043e\u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Ollenbock Escort", "\u0423\u0447\u0435\u043d\u0438\u0446\u0430 \u041f\u0440\u0438\u0445\u043e\u0434\u0441\u043a\u0438\u0445 \u041a\u043b\u0438\u043d\u043a\u043e\u0432": "Parish-Blade Trainee", "\u041f\u0440\u043e\u0431\u0438\u0432\u0430\u044e\u0449\u0438\u0439 \u0421\u0432\u0435\u0442": "Piercing Light", "\u041e\u0442\u0440\u044f\u0434 \u0421\u043e\u043f\u0440\u043e\u0442\u0438\u0432\u043b\u0435\u043d\u0438\u044f": "Resistance Squad", "\u041e\u0431\u0440\u044f\u0434 \u041e\u0441\u0432\u044f\u0449\u0435\u043d\u0438\u044f": "Sanctify", "\u0421\u043f\u0430\u0441\u0438\u0442\u0435\u043b\u044c \u041e\u043b\u043b\u0435\u043d\u0431\u043e\u043a\u0430": "Savior of Ollenbock", "\u041f\u043b\u0435\u043d\u0435\u043d\u0438\u0435 \u0421\u0438\u0433\u0430\u0440\u0434\u044b": "Sigarda's Imprisonment", "\u041f\u0440\u0438\u0437\u044b\u0432 \u0421\u0438\u0433\u0430\u0440\u0434\u044b": "Sigarda's Summons", "\u0421\u0432\u0435\u0440\u0445\u044a\u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0421\u043f\u0430\u0441\u0435\u043d\u0438\u0435": "Supernatural Rescue", "\u0422\u0430\u043b\u0438\u044f, \u0421\u0442\u0440\u0430\u0436 \u0422\u0440\u0435\u0439\u0431\u0435\u043d\u0430": "Thalia, Guardian of Thraben", "\u0421\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0443\u044e\u0449\u0430\u044f \u0421\u0432\u044f\u0449\u0435\u043d\u043d\u0438\u0446\u0430": "Traveling Minister", "\u041d\u0435\u0447\u0435\u0441\u0442\u0438\u0432\u044b\u0439 \u041f\u0440\u0435\u0441\u0432\u0438\u0442\u0435\u0440": "Unholy Officiant", "\u0414\u043e\u0431\u043b\u0435\u0441\u0442\u043d\u0430\u044f \u0421\u0442\u043e\u0439\u043a\u0430": "Valorous Stance", "\u0418\u0441\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430 \u0412\u0430\u043c\u043f\u0438\u0440\u043e\u0432": "Vampire Slayer", "\u0413\u043e\u043b\u043e\u0441 \u0411\u043b\u0430\u0433\u043e\u0441\u043b\u043e\u0432\u0435\u043d\u043d\u044b\u0445": "Voice of the Blessed", "\u0413\u043e\u0441\u0442\u0435\u043f\u0440\u0438\u0438\u043c\u043d\u0430\u044f \u0412\u0430\u043c\u043f\u0438\u0440\u0448\u0430": "Welcoming Vampire", "\u0414\u043e\u0431\u044b\u0447\u0430 \u0410\u043b\u0445\u0438\u043c\u0438\u043a\u0430": "Alchemist's Retrieval", "\u041a\u043b\u0430\u0434\u0431\u0438\u0449\u0435\u043d\u0441\u043a\u0438\u0439 \u041e\u0441\u0432\u0435\u0442\u0438\u0442\u0435\u043b\u044c": "Cemetery Illuminator", "\u041c\u043e\u0433\u0438\u043b\u044c\u043d\u044b\u0439 \u0425\u043e\u043b\u043e\u0434": "Chill of the Grave", "\u0417\u0430\u0448\u0442\u043e\u043f\u0430\u043d\u043d\u044b\u0439 \u041a\u043e\u043f\u0435\u0439\u0449\u0438\u043a": "Cobbled Lancer", "\u041f\u043e\u0433\u043b\u043e\u0449\u0430\u044e\u0449\u0438\u0439 \u041f\u0440\u0438\u043b\u0438\u0432": "Consuming Tide", "\u041a\u043e\u043b\u044b\u0431\u0435\u043b\u044c \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438": "Cradle of Safety", "\u0416\u0435\u0441\u0442\u043e\u043a\u0438\u0439 \u0421\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c": "Cruel Witness", "\u0421\u043a\u0430\u0430\u0431-\u0412\u043e\u0434\u043e\u043b\u0430\u0437": "Diver Skaab", "\u0427\u0443\u0434\u0438\u0449\u0435 \u041a\u043e\u0448\u043c\u0430\u0440\u043d\u043e\u0433\u043e \u0421\u0432\u0435\u0442\u0430": "Dreadlight Monstrosity", "\u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435 \u041e\u043a\u043e\u0432 \u0421\u043d\u0430": "Dreamshackle Geist", "\u0421\u0442\u0440\u0430\u0445 \u0421\u043c\u0435\u0440\u0442\u0438": "Fear of Death", "\u041b\u043e\u0432\u0443\u0448\u043a\u0430 \u041f\u0440\u0438\u0437\u0440\u0430\u0447\u043d\u043e\u0433\u043e \u0424\u043e\u043d\u0430\u0440\u044f": "Geistlight Snare", "\u0413\u0435\u0440\u0430\u043b\u044c\u0444, \u0418\u0437\u043e\u0449\u0440\u0435\u043d\u043d\u044b\u0439 \u0421\u0448\u0438\u0432\u0430\u0442\u0435\u043b\u044c": "Geralf, Visionary Stitcher", "\u0423\u0436\u0430\u0441\u043d\u044b\u0439 \u041a\u043e\u0440\u0430\u0431\u043b\u0435\u043a\u0440\u0443\u0448\u0438\u0442\u0435\u043b\u044c": "Hullbreaker Horror", "\u0412\u0434\u043e\u0445\u043d\u043e\u0432\u0435\u043d\u043d\u0430\u044f \u0418\u0434\u0435\u044f": "Inspired Idea", "\u041b\u0443\u043d\u043d\u044b\u0439 \u041e\u0442\u043a\u0430\u0437": "Lunar Rejection", "\u041d\u0435\u043a\u0440\u043e\u0434\u0432\u043e\u0439\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u044c": "Necroduality", "\u041f\u0435\u0440\u0435\u0437\u0430\u0440\u044f\u0436\u0435\u043d\u043d\u044b\u0439 \u041a\u043e\u043d\u0433\u043b\u043e\u043c\u0435\u0440\u0430\u0442": "Overcharged Amalgam", "\u0421\u0448\u0438\u0442\u044b\u0439 \u0438\u0437 \u041a\u0443\u0441\u043a\u043e\u0432 \u041f\u043e\u043b\u0437\u0443\u043d": "Patchwork Crawler", "\u0421\u043a\u0430\u0430\u0431-\u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435": "Repository Skaab", "\u041c\u044b\u0441\u043b\u0438 \u0412\u0440\u0430\u0437\u0431\u0440\u043e\u0441": "Scattered Thoughts", "\u0412\u043e\u043f\u044f\u0449\u0430\u044f \u0421\u0442\u0430\u044f": "Screaming Swarm", "\u0421\u0435\u043b\u0445\u043e\u0444\u0444\u0441\u043a\u0438\u0439 \u041c\u043e\u0433\u0438\u043b\u044c\u0449\u0438\u043a": "Selhoff Entomber", "\u0417\u043c\u0435\u0438\u043d\u0430\u044f \u0417\u0430\u0441\u0430\u0434\u0430": "Serpentine Ambush", "\u041e\u0441\u043a\u0432\u0435\u0440\u043d\u044f\u044e\u0449\u0438\u0439 \u041d\u0435\u0431\u043e \u0421\u043a\u0430\u0430\u0431": "Skywarp Skaab", "\u0414\u0443\u0445 \u0432 \u0421\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0414\u043e\u0441\u043f\u0435\u0445\u0430\u0445": "Steelclad Spirit", "\u0421\u0448\u0438\u0442\u044b\u0439 \u0410\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442": "Stitched Assistant", "\u041f\u0440\u0435\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0411\u0443\u0440\u0438 \u0414\u0440\u0435\u0439\u043a": "Stormchaser Drake", "\u0421\u0438\u043d\u043a\u043e\u043f\u0430": "Syncopate", "\u0412\u044b\u0442\u044f\u0433\u0438\u0432\u0430\u043d\u0438\u0435 \u0421\u0443\u0449\u043d\u043e\u0441\u0442\u0438": "Syphon Essence", "\u0416\u0430\u0436\u0434\u0430 \u041e\u0442\u043a\u0440\u044b\u0442\u0438\u0439": "Thirst for Discovery", "\u0414\u0443\u0445 \u0411\u0440\u043e\u0434\u044f\u0447\u0435\u0433\u043e \u0421\u0432\u0435\u0442\u0430": "Wanderlight Spirit", "\u0421\u043c\u044b\u0442\u044c": "Wash Away", "\u0428\u0435\u043f\u0447\u0443\u0449\u0438\u0439 \u0427\u0430\u0440\u043e\u0434\u0435\u0439": "Whispering Wizard", "\u041a\u0440\u044b\u043b\u0430\u0442\u043e\u0435 \u0417\u043d\u0430\u043c\u0435\u043d\u0438\u0435": "Winged Portent", "\u0423\u0432\u0438\u0434\u0435\u0442\u044c \u0411\u0443\u0434\u0443\u0449\u0435\u0435": "Witness the Future", "\u041f\u0440\u043e\u043a\u043b\u044f\u0442\u043e\u0435 \u0421\u043a\u043e\u043f\u0438\u0449\u0435": "Wretched Throng", "\u0426\u0435\u043b\u044c\u0441\u044f \u0432 \u0413\u043e\u043b\u043e\u0432\u0443": "Aim for the Head", "\u0422\u0440\u0435\u0439\u0431\u0435\u043d\u0441\u043a\u0438\u0439 \u0410\u0440\u0445\u0438\u0443\u043f\u044b\u0440\u044c": "Archghoul of Thraben", "\u041e\u0431\u0435\u0441\u043a\u0440\u043e\u0432\u0438\u0442\u044c \u0414\u043e\u0441\u0443\u0445\u0430": "Bleed Dry", "\u0424\u043e\u043d\u0442\u0430\u043d \u041a\u0440\u043e\u0432\u0438": "Blood Fountain", "\u041e\u0434\u0435\u0440\u0436\u0438\u043c\u0430\u044f \u041a\u0440\u043e\u0432\u044c\u044e \u0411\u043e\u043d\u0432\u0438\u0432\u0430\u043d\u043a\u0430": "Bloodcrazed Socialite", "\u041f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a \u041a\u0440\u043e\u0432\u0430\u0432\u044b\u0445 \u0421\u043e\u0441\u0443\u0434\u043e\u0432": "Bloodvial Purveyor", "\u041a\u043b\u0430\u0434\u0431\u0438\u0449\u0435\u043d\u0441\u043a\u0438\u0439 \u041e\u0441\u043a\u0432\u0435\u0440\u043d\u0438\u0442\u0435\u043b\u044c": "Cemetery Desecrator", "\u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u041d\u0435\u0442\u043e\u043f\u044b\u0440\u044c": "Courier Bat", "\u0414\u0435\u043c\u043e\u043d\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0421\u0434\u0435\u043b\u043a\u0430": "Demonic Bargain", "\u041f\u0430\u0434\u0430\u043b\u044c\u0449\u0438\u043a \u0441 \u041c\u0435\u0441\u0442\u0430 \u0420\u0435\u0437\u043d\u0438": "Diregraf Scavenger", "\u041e\u0431\u0440\u0435\u0447\u0435\u043d\u043d\u044b\u0439 \u0418\u043d\u0430\u043a\u043e\u043c\u044b\u0441\u043b\u044f\u0449\u0438\u0439": "Doomed Dissenter", "\u041a\u043e\u0448\u043c\u0430\u0440\u043d\u043e\u0435 \u0411\u0435\u0441\u043f\u0430\u043c\u044f\u0442\u0441\u0442\u0432\u043e": "Dread Fugue", "\u0414\u0435\u043c\u043e\u043d, \u041f\u043e\u0436\u0438\u0440\u0430\u0442\u0435\u043b\u044c \u0421\u0442\u0440\u0430\u0445\u0430": "Dreadfeast Demon", "\u0421\u043c\u0435\u0440\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0423\u0441\u043b\u0443\u0436\u043b\u0438\u0432\u043e\u0441\u0442\u044c": "Dying to Serve", "\u041f\u0440\u043e\u0431\u0443\u0436\u0434\u0435\u043d\u0438\u0435 \u042d\u0434\u0433\u0430\u0440\u0430": "Edgar's Awakening", "\u0424\u0430\u043b\u044c\u043a\u0435\u043d\u0440\u0430\u0442\u0441\u043a\u0430\u044f \u041f\u0440\u0430\u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Falkenrath Forebear", "\u0413\u0438\u0431\u0435\u043b\u044c\u043d\u044b\u0439 \u0423\u044f\u0437\u0432\u0438\u0442\u0435\u043b\u044c": "Fell Stinger", "\u0414\u0430\u0440 \u041a\u043b\u044b\u043a\u043e\u0432": "Gift of Fangs", "\u041d\u0435\u043d\u0430\u0441\u044b\u0442\u043d\u0430\u044f \u0413\u043e\u0441\u0442\u044c\u044f": "Gluttonous Guest", "\u0413\u0440\u0430\u0431\u0438\u0442\u0435\u043b\u044c \u0441 \u041c\u043e\u0433\u0438\u043b\u044c\u043d\u0438\u043a\u0430": "Graf Reaver", "\u0416\u0443\u0442\u043a\u0438\u0439 \u0420\u0438\u0442\u0443\u0430\u043b": "Grisly Ritual", "\u041d\u0430\u0440\u044f\u0434 \u0416\u0435\u043d\u0438\u0445\u0430": "Groom's Finery", "\u0412\u0441\u0430\u0434\u043d\u0438\u043a \u0431\u0435\u0437 \u0413\u043e\u043b\u043e\u0432\u044b": "Headless Rider", "\u041f\u0430\u0434\u0435\u043d\u0438\u0435 \u0413\u0435\u0440\u043e\u044f": "Hero's Downfall", "\u0421\u043e\u0441\u0443\u0449\u0438\u0439 \u0420\u0430\u0437\u0443\u043c \u0423\u043f\u044b\u0440\u044c": "Mindleech Ghoul", "\u041f\u0430\u0440\u0430\u0437\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0425\u0432\u0430\u0442\u043a\u0430": "Parasitic Grasp", "\u041f\u0443\u0442\u044c \u041e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0435\u0439": "Path of Peril", "\u0421\u0442\u043e\u0439\u043a\u0438\u0439 \u041e\u0431\u0440\u0430\u0437\u0435\u0446": "Persistent Specimen", "\u041a\u043e\u043b\u043a\u0430\u044f \u0414\u0438\u0441\u043a\u0443\u0441\u0441\u0438\u044f": "Pointed Discussion", "\u0418\u0441\u043f\u043e\u043b\u0438\u043d \u0413\u043d\u0438\u043b\u043e\u0441\u0442\u043d\u043e\u0433\u043e \u041f\u0440\u0438\u043b\u0438\u0432\u0430": "Rot-Tide Gargantua", "\u041f\u0440\u0438\u0442\u0430\u0438\u0432\u0448\u0430\u044f\u0441\u044f \u0423\u0431\u0438\u0439\u0446\u0430": "Skulking Killer", "\u0421\u043e\u0440\u0438\u043d \u0411\u0435\u0437\u0440\u0430\u0434\u043e\u0441\u0442\u043d\u044b\u0439": "Sorin the Mirthless", "\u0422\u043e\u043a\u0441\u0440\u0438\u043b\u043b, \u0415\u0434\u043a\u0438\u0439": "Toxrill, the Corrosive", "\u041d\u0435\u0436\u0438\u0432\u043e\u0439 \u0414\u0432\u043e\u0440\u0435\u0446\u043a\u0438\u0439": "Undead Butler", "\u041d\u0435\u0443\u043c\u0438\u0440\u0430\u044e\u0449\u0430\u044f \u0417\u043b\u043e\u0431\u0430": "Undying Malice", "\u041d\u0435\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u0430\u044f \u0424\u0430\u043b\u0430\u043d\u0433\u0430": "Unhallowed Phalanx", "\u041f\u043e\u0446\u0435\u043b\u0443\u0439 \u0412\u0430\u043c\u043f\u0438\u0440\u0430": "Vampire's Kiss", "\u0421\u0432\u0430\u0434\u0435\u0431\u043d\u044b\u0439 \u041e\u0445\u0440\u0430\u043d\u043d\u0438\u043a": "Wedding Security", "\u0418\u0441\u0442\u0438\u0440\u0430\u043d\u0438\u0435": "Abrade", "\u0413\u0430\u043c\u0431\u0438\u0442 \u0410\u043b\u0445\u0438\u043c\u0438\u043a\u0430": "Alchemist's Gambit", "\u0413\u043d\u0435\u0432 \u041f\u0440\u0435\u0434\u043a\u043e\u0432": "Ancestral Anger", "\u0412\u043e\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u0430\u044f \u0413\u043e\u0441\u0442\u044c\u044f": "Belligerent Guest", "\u0413\u0438\u043f\u043d\u043e\u0442\u0438\u0437\u0435\u0440\u0448\u0430 \u041a\u0440\u043e\u0432\u0438": "Blood Hypnotist", "\u0421\u0438\u0431\u0430\u0440\u0438\u0442\u043a\u0430 \u041a\u0440\u043e\u0432\u0430\u0432\u044b\u0445 \u041b\u0435\u043f\u0435\u0441\u0442\u043a\u043e\u0432": "Blood Petal Celebrant", "\u041a\u0440\u043e\u0432\u0430\u0432\u043e\u0435 \u041f\u0440\u0435\u0434\u0430\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e": "Bloody Betrayal", "\u041a\u043b\u0430\u0434\u0431\u0438\u0449\u0435\u043d\u0441\u043a\u0438\u0439 \u041f\u0440\u0438\u0432\u0440\u0430\u0442\u043d\u0438\u043a": "Cemetery Gatekeeper", "\u0427\u0430\u043d\u0434\u0440\u0430, \u0421\u0440\u0430\u0436\u0430\u044e\u0449\u0430\u044f \u041d\u0430\u043f\u043e\u0432\u0430\u043b": "Chandra, Dressed to Kill", "\u041f\u0435\u0440\u0435\u043c\u0435\u043d\u0447\u0438\u0432\u0430\u044f \u0423\u0434\u0430\u0447\u0430": "Change of Fortune", "\u0416\u0443\u0442\u043a\u0430\u044f \u041a\u0443\u043a\u043e\u043b\u044c\u043d\u0438\u0446\u0430": "Creepy Puppeteer", "\u041f\u0440\u043e\u043a\u043b\u044f\u0442\u0438\u0435 \u0413\u043e\u0441\u0442\u0435\u043f\u0440\u0438\u0438\u043c\u0441\u0442\u0432\u0430": "Curse of Hospitality", "\u0411\u043e\u0439\u0446\u044b \u0420\u0430\u0441\u0441\u0432\u0435\u0442\u0430": "Daybreak Combatants", "\u0412\u0430\u043c\u043f\u0438\u0440\u0448\u0430-\u041f\u043e\u0434\u0430\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Dominating Vampire", "\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u041f\u0440\u0430\u0437\u0434\u043d\u0438\u043a\u0430": "End the Festivities", "\u0424\u0430\u043b\u044c\u043a\u0435\u043d\u0440\u0430\u0442\u0441\u043a\u0438\u0435 \u0421\u0438\u0431\u0430\u0440\u0438\u0442\u043a\u0438": "Falkenrath Celebrants", "\u0417\u0430\u043a\u043b\u044f\u0442\u044b\u0439 \u041f\u043b\u0430\u043c\u0435\u043d\u0435\u043c \u0411\u043e\u043b\u0442": "Flame-Blessed Bolt", "\u0420\u0430\u0437\u044a\u044f\u0440\u0435\u043d\u043d\u044b\u0435 \u0414\u044c\u044f\u0432\u043e\u043b\u044b": "Frenzied Devils", "\u041a\u0430\u0442\u0430\u0444\u0430\u043b\u043a \u041c\u0435\u0434\u043e\u0432\u043e\u0433\u043e \u041c\u0435\u0441\u044f\u0446\u0430": "Honeymoon Hearse", "\u0413\u043e\u043b\u043e\u0434\u043d\u044b\u0439 \u0413\u0440\u0435\u0431\u043d\u0435\u0432\u043e\u043b\u043a": "Hungry Ridgewolf", "\u0412 \u041d\u043e\u0447\u044c": "Into the Night", "\u041a\u0435\u0441\u0441\u0438\u0433\u0441\u043a\u0438\u0439 \u041e\u0433\u043d\u0435\u0433\u043b\u043e\u0442\u0430\u0442\u0435\u043b\u044c": "Kessig Flamebreather", "\u041a\u0435\u0441\u0441\u0438\u0433\u0441\u043a\u0430\u044f \u041d\u0430\u0435\u0437\u0434\u043d\u0438\u0446\u0430 \u043d\u0430 \u0412\u043e\u043b\u043a\u0435": "Kessig Wolfrider", "\u0418\u0437\u0440\u0435\u0437\u0430\u043d\u043d\u0430\u044f \u041f\u043b\u043e\u0442\u044c": "Lacerate Flesh", "\u0412\u043e\u043b\u0447\u0438\u0446\u0430 \u041c\u043e\u043b\u043d\u0438\u0439": "Lightning Wolf", "\u041c\u0430\u0433\u043c\u043e\u0432\u044b\u0439 \u041c\u043e\u043b\u043e\u0442\u0438\u043b\u044c\u0449\u0438\u043a": "Magma Pummeler", "\u041c\u0430\u043d\u0430\u0444\u043e\u0440\u043c\u043e\u0432\u044b\u0439 \u0417\u043c\u0435\u0439": "Manaform Hellkite", "\u0412\u043e\u0437\u0434\u0430\u044f\u043d\u0438\u0435 \u041c\u0430\u0440\u043a\u043e\u0432\u0430": "Markov Retribution", "\u041f\u0440\u0438\u0441\u043b\u0443\u0436\u043d\u0438\u0446\u044b \u041e\u043b\u0438\u0432\u0438\u0438": "Olivia's Attendants", "\u041e\u0442\u0440\u043e\u0434\u044c\u0435 \u041f\u043e\u0436\u0430\u0440\u0430": "Pyre Spawn", "\u0411\u0435\u0437\u0440\u0430\u0441\u0441\u0443\u0434\u043d\u044b\u0439 \u041f\u043e\u0440\u044b\u0432": "Reckless Impulse", "\u0420\u0430\u0437\u0434\u0438\u0440\u0430\u044e\u0449\u0435\u0435 \u041f\u043b\u0430\u043c\u044f": "Rending Flame", "\u0420\u0443\u043d\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u0412\u043e\u043b\u043a": "Runebound Wolf", "\u0411\u0430\u0433\u0440\u044f\u043d\u0430\u044f \u0421\u0442\u0430\u0442\u0443\u044d\u0442\u043a\u0430": "Sanguine Statuette", "\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u0438\u0435 \u0432 \u0421\u0442\u0435\u043d\u0441\u0438\u0438": "Stensia Uprising", "\u0412\u0435\u0440\u043d\u044b\u0439 \u0423\u0434\u0430\u0440": "Sure Strike", "\u041c\u0435\u0441\u0442\u044c \u0412\u0430\u043c\u043f\u0438\u0440\u043e\u0432": "Vampires' Vengeance", "\u0412\u043e\u043b\u0434\u0430\u0440\u0435\u043d\u0441\u043a\u0438\u0439 \u042d\u043f\u0438\u043a\u0443\u0440\u0435\u0435\u0446": "Voldaren Epicure", "\u0423\u0447\u0435\u043d\u0438\u0446\u0430 \u0421\u043d\u0430\u0439\u043f\u0435\u0440\u0430": "Apprentice Sharpshooter", "\u0412\u043e\u0437\u0432\u044b\u0441\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0412\u043e\u0436\u0430\u043a": "Ascendant Packleader", "\u0422\u0435\u0440\u043d\u043e\u0432\u044b\u0439 \u0414\u043e\u0441\u043f\u0435\u0445": "Bramble Armor", "\u0422\u0435\u0440\u043d\u043e\u0432\u044b\u0439 \u0412\u0443\u0440\u043c": "Bramble Wurm", "\u0418\u0437\u044b\u0441\u043a\u0430\u043d\u0438\u044f \u041a\u0430\u0440\u0442\u043e\u0433\u0440\u0430\u0444\u0430": "Cartographer's Survey", "\u041a\u043b\u0430\u0434\u0431\u0438\u0449\u0435\u043d\u0441\u043a\u0438\u0439 \u041e\u0445\u043e\u0442\u043d\u0438\u043a": "Cemetery Prowler", "\u0417\u0430\u043c\u0430\u0441\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u041a\u0430\u0434\u0435\u0442": "Cloaked Cadet", "\u041f\u043e\u043b\u0437\u0443\u0447\u0435\u0435 \u0417\u0430\u0440\u0430\u0436\u0435\u043d\u0438\u0435": "Crawling Infestation", "\u0421\u043e\u043a\u0440\u0443\u0448\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u041a\u0440\u043e\u043d\u0430": "Crushing Canopy", "\u041a\u043e\u043b\u043e\u0441\u0441-\u0417\u0435\u043c\u043b\u0435\u043f\u0430\u0448\u0435\u0446": "Cultivator Colossus", "\u0423\u0447\u0435\u043d\u0438\u0446\u0430 \u0414\u043e\u043d\u0445\u0430\u0440\u0442\u0430": "Dawnhart Disciple", "\u0420\u0430\u0441\u043a\u0430\u043f\u044b\u0432\u0430\u043d\u0438\u0435": "Dig Up", "\u0426\u0432\u0435\u0442\u0443\u0449\u0430\u044f \u041e\u0445\u043e\u0442\u043d\u0438\u0446\u0430": "Flourishing Hunter", "\u0421\u043b\u0430\u0432\u043d\u044b\u0439 \u0412\u043e\u0441\u0445\u043e\u0434": "Glorious Sunrise", "\u0410\u0432\u0430\u043d\u0433\u0430\u0440\u0434 \u0414\u0435\u0440\u0435\u0432\u0443\u0448\u043a\u0438": "Hamlet Vanguard", "\u0428\u0430\u043c\u0430\u043d \u0421\u0435\u0440\u0434\u0446\u0430 \u0423\u043b\u044c\u044f": "Hiveheart Shaman", "\u0412\u043e\u044e\u0449\u0430\u044f \u041b\u0443\u043d\u0430": "Howling Moon", "\u0423\u043f\u043e\u043a\u043e\u0435\u043d\u043d\u044b\u0435": "Laid to Rest", "\u041c\u0430\u0441\u0441\u0438\u0432\u043d\u043e\u0435 \u041c\u043e\u0433\u0443\u0449\u0435\u0441\u0442\u0432\u043e": "Massive Might", "\u0422\u044b\u0441\u044f\u0447\u0435\u043d\u043e\u0436\u043a\u0430 \u041b\u0435\u0441\u043d\u043e\u0433\u043e \u041c\u043e\u0433\u0438\u043b\u044c\u043d\u0438\u043a\u0430": "Moldgraf Millipede", "\u041c\u0443\u043b\u044c\u0447\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435": "Mulch", "\u041e\u0431\u044a\u044f\u0442\u0438\u044f \u041f\u0440\u0438\u0440\u043e\u0434\u044b": "Nature's Embrace", "\u0412\u043e\u043b\u0447\u043e\u043d\u043e\u043a \u041f\u0435\u0441\u043d\u0438 \u0421\u0442\u0430\u0438": "Packsong Pup", "\u041d\u0435\u043b\u044e\u0434\u0438\u043c\u0430\u044f \u0427\u0443\u0447\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Reclusive Taxidermist", "\u041d\u0430\u0445\u043e\u0434\u043a\u0430": "Retrieve", "\u0421\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u041d\u043e\u0432\u043e\u0431\u0440\u0430\u043d\u0435\u0446": "Rural Recruit", "\u041c\u0435\u0442\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430 \u041b\u0435\u0437\u0432\u0438\u0439": "Sawblade Slinger", "\u0423\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u0412\u0435\u0442\u0432\u0438": "Sheltering Boughs", "\u041e\u0441\u043a\u0430\u043b\u0438\u0432\u0448\u0438\u0439\u0441\u044f \u0412\u043e\u043b\u043a": "Snarling Wolf", "\u0428\u0438\u043f\u0430\u0441\u0442\u0430\u044f \u041f\u0438\u043b\u0430": "Spiked Ripsaw", "\u0427\u0443\u0434\u0435\u0441\u043d\u043e\u0435 \u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435": "Splendid Reclamation", "\u0421\u043f\u043e\u0440\u043e\u0432\u044b\u0439 \u041f\u043e\u043b\u0437\u0443\u043d": "Spore Crawler", "\u0421\u043f\u043e\u0440\u043e\u0441\u043f\u0438\u043d\u043d\u044b\u0439 \u0412\u043e\u043b\u043a": "Sporeback Wolf", "\u042f\u0434\u043e\u0432\u0438\u0442\u044b\u0439 \u0421\u043a\u043e\u0440\u043f\u0438\u043e\u043d": "Toxic Scorpion", "\u0412\u0435\u0434\u044c\u043c\u0438\u043d\u0430 \u041f\u0430\u0443\u0442\u0438\u043d\u0430": "Witch's Web", "\u0412\u043e\u043b\u0447\u0438\u0439 \u0423\u0434\u0430\u0440": "Wolf Strike", "\u0414\u0440\u0435\u0432\u043d\u0438\u0439 \u0414\u0440\u0435\u0432\u043e\u043a\u0440\u0443\u0442": "Ancient Lumberknot", "\u0410\u043d\u0436\u0435, \u041d\u0435\u0447\u0435\u0441\u0442\u0438\u0432\u0430\u044f \u041f\u043e\u0434\u0440\u0443\u0436\u043a\u0430": "Anje, Maid of Dishonor", "\u0421\u043e\u0431\u0438\u0440\u0430\u0442\u0435\u043b\u044c \u041a\u0440\u043e\u0432\u0430\u0432\u043e\u0439 \u041f\u043e\u0434\u0430\u0442\u0438": "Bloodtithe Harvester", "\u042d\u0440\u0443\u0442, \u0418\u0437\u043c\u0443\u0447\u0435\u043d\u043d\u0430\u044f \u041f\u0440\u043e\u0440\u043e\u0447\u0438\u0446\u0430": "Eruth, Tormented Prophet", "\u0413\u0440\u043e\u043b\u043d\u043e\u043a, \u0412\u0441\u0435\u043f\u043e\u0435\u0434\u0430\u0442\u0435\u043b\u044c": "Grolnok, the Omnivore", "\u0425\u0430\u043b\u0430\u043d\u0430 \u0438 \u0410\u043b\u0435\u043d\u0430, \u041f\u0430\u0440\u0442\u043d\u0435\u0440\u044b": "Halana and Alena, Partners", "\u041a\u0430\u0439\u044f, \u041e\u0445\u043e\u0442\u043d\u0438\u0446\u0430 \u043d\u0430 \u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0439": "Kaya, Geist Hunter", "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u0435\u043b\u044c \u0438\u0437 \u0420\u043e\u0434\u0430 \u041c\u0430\u0440\u043a\u043e\u0432\u044b\u0445": "Markov Purifier", "\u0422\u0430\u043d\u0446\u043e\u0440 \u0438\u0437 \u0420\u043e\u0434\u0430 \u041c\u0430\u0440\u043a\u043e\u0432\u044b\u0445": "Markov Waltzer", "\u041e\u0434\u0440\u0438\u043a, \u041f\u0440\u043e\u043a\u043b\u044f\u0442\u044b\u0439 \u041a\u0440\u043e\u0432\u044c\u044e": "Odric, Blood-Cursed", "\u0421\u0442\u0430\u0440\u044b\u0439 \u0420\u0443\u0442\u0448\u0442\u0430\u0439\u043d": "Old Rutstein", "\u041e\u043b\u0438\u0432\u0438\u044f, \u0411\u0430\u0433\u0440\u043e\u0432\u0430\u044f \u041d\u0435\u0432\u0435\u0441\u0442\u0430": "Olivia, Crimson Bride", "\u041f\u0430\u043b\u0430\u0434\u0438\u043d-\u0421\u0438\u0433\u0430\u0440\u0434\u0438\u0430\u043d\u043a\u0430": "Sigardian Paladin", "\u0427\u0435\u0440\u0435\u043f\u043d\u043e\u0439 \u0421\u043a\u0430\u0430\u0431": "Skull Skaab", "\u0422\u043e\u0440\u0435\u043d\u0441, \u041a\u0443\u043b\u0430\u043a \u0410\u043d\u0433\u0435\u043b\u043e\u0432": "Torens, Fist of the Angels", "\u041f\u0430\u0443\u043a \u041c\u0435\u0440\u0437\u043a\u043e\u0433\u043e \u041e\u0442\u0440\u043e\u0434\u044c\u044f": "Vilespawn Spider", "\u0411\u043b\u0443\u0436\u0434\u0430\u044e\u0449\u0438\u0439 \u0423\u043c": "Wandering Mind", "\u041a\u0440\u043e\u0432\u0430\u0432\u044b\u0439 \u0421\u043b\u0443\u0433\u0430": "Blood Servitor", "\u0417\u0430\u043a\u043e\u043b\u043e\u0447\u0435\u043d\u043d\u043e\u0435 \u041e\u043a\u043d\u043e": "Boarded Window", "\u0420\u0438\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u041d\u043e\u0436": "Ceremonial Knife", "\u041a\u0443\u043a\u043e\u043b\u044c\u043d\u044b\u0439 \u0414\u043e\u043c\u0438\u043a \u0423\u0436\u0430\u0441\u043e\u0432": "Dollhouse of Horrors", "\u041f\u043e\u0447\u0442\u0435\u043d\u043d\u043e\u0435 \u041d\u0430\u0441\u043b\u0435\u0434\u0438\u0435": "Honored Heirloom", "\u0414\u043d\u0435\u0432\u043d\u0438\u043a \u0414\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u0435\u043b\u044f": "Investigator's Journal", "\u0424\u043e\u043d\u0430\u0440\u044c \u041f\u043e\u0442\u0435\u0440\u044f\u043d\u043d\u044b\u0445": "Lantern of the Lost", "\u041f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u043d\u0430 \u0421\u0432\u0430\u0434\u044c\u0431\u0443": "Wedding Invitation", "\u041f\u043e\u043b\u044f\u043d\u0430 \u041f\u043e\u0433\u0430\u043d\u043e\u043a": "Deathcap Glade", "\u041a\u0430\u0441\u043a\u0430\u0434 \u041a\u043e\u0440\u043d\u0435\u0439 \u0413\u0440\u0435\u0437": "Dreamroot Cascade", "\u041f\u0435\u0440\u0435\u0440\u043e\u0436\u0434\u0430\u044e\u0449\u0438\u0435\u0441\u044f \u0414\u0435\u0431\u0440\u0438": "Evolving Wilds", "\u0420\u0430\u0437\u0433\u0440\u043e\u043c\u043b\u0435\u043d\u043d\u043e\u0435 \u0421\u0432\u044f\u0442\u0438\u043b\u0438\u0449\u0435": "Shattered Sanctum", "\u0418\u0437\u0440\u0435\u0437\u0430\u043d\u043d\u044b\u0439 \u0428\u0442\u043e\u0440\u043c\u0430\u043c\u0438 \u0411\u0435\u0440\u0435\u0433": "Stormcarved Coast", "\u0417\u0430\u043a\u0430\u0442\u043d\u044b\u0439 \u041f\u0435\u0440\u0435\u0432\u0430\u043b": "Sundown Pass", "\u0423\u0441\u0430\u0434\u044c\u0431\u0430 \u0412\u043e\u043b\u0434\u0430\u0440\u0435\u043d\u043e\u0432": "Voldaren Estate", "\u0420\u0430\u0432\u043d\u0438\u043d\u0430": "Plains", "\u041e\u0441\u0442\u0440\u043e\u0432": "Island", "\u0411\u043e\u043b\u043e\u0442\u043e": "Swamp", "\u0413\u043e\u0440\u0430": "Mountain", "\u041b\u0435\u0441": "Forest", "\u041e\u0442\u0432\u043b\u0435\u043a\u0430\u044e\u0449\u0435\u0435 \u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435": "Distracting Geist", "\u0414\u0440\u043e\u0433\u0441\u043a\u043e\u043b\u0441\u043a\u0438\u0439 \u041f\u0435\u0445\u043e\u0442\u0438\u043d\u0435\u0446": "Drogskol Infantry", "\u0412\u0435\u0440\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u0421\u0443\u0434\u044c\u044f": "Faithbound Judge", "\u041a\u0430\u0442\u0438\u043b\u044c\u0434\u0430, \u041c\u0443\u0447\u0435\u043d\u0438\u0446\u0430 \u0414\u043e\u043d\u0445\u0430\u0440\u0442\u0430": "Katilda, Dawnhart Martyr", "\u0414\u043e\u0431\u0440\u043e\u0434\u0443\u0448\u043d\u0430\u044f \u041f\u0440\u0430\u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Kindly Ancestor", "\u041f\u0430\u043d\u0438\u043a\u0443\u044e\u0449\u0438\u0439 \u041f\u0440\u043e\u0445\u043e\u0436\u0438\u0439": "Panicked Bystander", "\u041b\u0443\u0447\u0435\u0437\u0430\u0440\u043d\u0430\u044f \u0413\u0440\u0430\u0446\u0438\u044f": "Radiant Grace", "\u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435 \u041f\u0430\u0440\u043d\u044b\u0445 \u041a\u043b\u0438\u043d\u043a\u043e\u0432": "Twinblade Geist", "\u041e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u043e \u0421\u0432\u0430\u0434\u044c\u0431\u0435": "Wedding Announcement", "\u0421\u0432\u044f\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435": "Binding Geist", "\u0411\u0438\u043e\u043b\u044e\u043c\u0438\u043d\u0435\u0441\u0446\u0435\u043d\u0442\u043d\u043e\u0435 \u042f\u0439\u0446\u043e": "Biolume Egg", "\u0421\u043e\u0433\u043b\u044f\u0434\u0430\u0442\u0430\u0439 \u0421\u0442\u043e\u043a\u043e\u0432": "Gutter Skulker", "\u0418\u043d\u0441\u043f\u0435\u043a\u0442\u043e\u0440 \u042f\u043a\u043e\u0431 \u0425\u0430\u0443\u043a\u0435\u043d": "Jacob Hauken, Inspector", "\u041d\u043e\u0441\u0438\u0442\u0435\u043b\u044c \u0424\u043e\u043d\u0430\u0440\u0435\u0439": "Lantern Bearer", "\u041c\u0438\u043c\u0438\u043a \u0417\u0435\u0440\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0417\u0430\u043b\u0430": "Mirrorhall Mimic", "\u041e\u0437\u043e\u0440\u043d\u043e\u0439 \u041a\u043e\u0442\u0435\u043d\u043e\u043a-\u041f\u0440\u0438\u0432\u0438\u0434\u0435\u043d\u0438\u0435": "Mischievous Catgeist", "\u0414\u043e\u0441\u043a\u0430 \u0428\u0438\u0444\u0440\u0430 \u0414\u0443\u0448\u0438": "Soulcipher Board", "\u041e\u0440\u0443\u0436\u0435\u043d\u043e\u0441\u0435\u0446 \u041a\u0440\u043e\u0432\u0430\u0432\u043e\u0439 \u041a\u043b\u044f\u0442\u0432\u044b": "Bloodsworn Squire", "\u041a\u0430\u0442\u0430\u043f\u0443\u043b\u044c\u0442\u043d\u043e\u0435 \u041c\u044f\u0441\u043e": "Catapult Fodder", "\u0421\u043a\u0440\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u0428\u0442\u043e\u0440\u044b": "Concealing Curtains", "\u041e\u0442\u0447\u0430\u044f\u0432\u0448\u0438\u0439\u0441\u044f \u0424\u0435\u0440\u043c\u0435\u0440": "Desperate Farmer", "\u0413\u0435\u043d\u0440\u0438\u043a\u0430 \u0414\u043e\u043c\u043d\u0430\u0442\u0438": "Henrika Domnathi", "\u041d\u0435\u0432\u0438\u043d\u043d\u0430\u044f \u041f\u0443\u0442\u043d\u0438\u0446\u0430": "Innocent Traveler", "\u0420\u0430\u0441\u0442\u0440\u0435\u043f\u0430\u043d\u043d\u0430\u044f \u0417\u0430\u0442\u0432\u043e\u0440\u043d\u0438\u0446\u0430": "Ragged Recluse", "\u0411\u0435\u0441\u043f\u043e\u043a\u043e\u0439\u043d\u044b\u0439 \u0418\u0441\u043a\u0430\u0442\u0435\u043b\u044c \u041a\u0440\u043e\u0432\u0438": "Restless Bloodseeker", "\u0412\u043e\u043b\u0434\u0430\u0440\u0435\u043d\u0441\u043a\u0430\u044f \u041a\u0440\u043e\u0432\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Voldaren Bloodcaster", "\u041e\u0447\u0430\u0440\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0423\u0445\u0430\u0436\u0435\u0440": "Alluring Suitor", "\u0421\u0442\u043e\u0440\u043e\u0436 \u0411\u0430\u043b\u043b\u0438\u0441\u0442\u044b": "Ballista Watcher", "\u0418\u0441\u043f\u0443\u0433\u0430\u043d\u043d\u044b\u0439 \u0421\u0435\u043b\u044f\u043d\u0438\u043d": "Fearful Villager", "\u0412\u0441\u043f\u044b\u043b\u044c\u0447\u0438\u0432\u044b\u0439 \u041e\u0434\u0438\u043d\u043e\u0447\u043a\u0430": "Ill-Tempered Loner", "\u041b\u0430\u043c\u0431\u0445\u043e\u043b\u044c\u0442\u0441\u043a\u0430\u044f \u0421\u043a\u0430\u0437\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Lambholt Raconteur", "\u041d\u0435\u0443\u043b\u043e\u0432\u0438\u043c\u0430\u044f \u041f\u043e\u0434\u0436\u0438\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Volatile Arsonist", "\u0413\u0430\u043b\u044c\u0432\u0430\u043d\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u041f\u0440\u043e\u0432\u0438\u0434\u0438\u0446\u0430": "Voltaic Visionary", "\u0418\u0437\u043c\u043e\u0436\u0434\u0435\u043d\u043d\u0430\u044f \u041f\u043b\u0435\u043d\u043d\u0438\u0446\u0430": "Weary Prisoner", "\u0421\u043c\u043e\u0442\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430 \u0410\u0432\u0430\u0431\u0440\u044e\u043a\u0430": "Avabruck Caretaker", "\u0414\u0440\u0435\u043c\u043b\u044e\u0449\u0430\u044f \u0420\u043e\u0449\u0430": "Dormant Grove", "\u041a\u0440\u044e\u043a\u043e\u0440\u0443\u043a\u0438\u0439 \u041c\u043e\u0440\u044f\u043a": "Hookhand Mariner", "\u0414\u0443\u0434\u043e\u0447\u043d\u0438\u0446\u0430 \u0412\u043e\u044e\u0449\u0435\u0439 \u0421\u0442\u0430\u0438": "Howlpack Piper", "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0441\u0442\u043a\u0430 \u043f\u043e \u0417\u0430\u0440\u0430\u0436\u0435\u043d\u0438\u044e": "Infestation Expert", "\u0421\u043b\u0435\u0434\u043e\u043f\u044b\u0442 \u0414\u0443\u0431\u043e\u0432\u043e\u0439 \u0422\u0435\u043d\u0438": "Oakshade Stalker", "\u0423\u043b\u0435\u043d\u0432\u0430\u043b\u044c\u0434\u0441\u043a\u0430\u044f \u0421\u0442\u0440\u0430\u043d\u043d\u043e\u0441\u0442\u044c": "Ulvenwald Oddity", "\u041f\u043b\u0435\u0442\u0435\u043b\u044c\u0449\u0438\u0446\u0430 \u0426\u0432\u0435\u0442\u043e\u0432": "Weaver of Blossoms", "\u0418\u0437\u0433\u043e\u0439 \u0412\u043e\u043b\u0447\u044c\u0435\u0433\u043e \u0420\u043e\u0434\u0430": "Wolfkin Outcast", "\u0421\u043e\u043b\u0435\u0432\u043e\u0434\u043d\u0430\u044f \u0418\u0441\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u0438\u0446\u0430": "Brine Comber", "\u0414\u0438\u0442\u044f \u0421\u0442\u0430\u0438": "Child of the Pack", "\u0414\u043e\u0440\u043e\u0442\u0435\u044f, \u041c\u0441\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0416\u0435\u0440\u0442\u0432\u0430": "Dorothea, Vengeful Victim", "\u042d\u0434\u0433\u0430\u0440, \u041e\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0416\u0435\u043d\u0438\u0445": "Edgar, Charmed Groom", "\u0420\u0443\u043d\u043e \u0421\u0442\u0440\u043e\u043c\u043a\u0438\u0440\u043a": "Runo Stromkirk", "\u0417\u043b\u043e\u0432\u0435\u0449\u0430\u044f \u0421\u0442\u0430\u0442\u0443\u044f": "Foreboding Statue"} \ No newline at end of file diff --git a/cmd/telegram/main.go b/cmd/telegram/main.go index 133ac17..8c11aa7 100644 --- a/cmd/telegram/main.go +++ b/cmd/telegram/main.go @@ -1,55 +1,81 @@ package main import ( - "encoding/json" - "io/ioutil" + "context" + "fmt" "log" "os" "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" + environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" + "github.com/ydb-platform/ydb-go-sdk/v3" + "gitlab.com/flygrounder/go-mtg-vk/internal/caching" "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" "gitlab.com/flygrounder/go-mtg-vk/internal/scenario" - - "gitlab.com/flygrounder/go-mtg-vk/internal/caching" "gitlab.com/flygrounder/go-mtg-vk/internal/telegram" ) const welcomeMessage = "Здравствуйте, вас приветствует бот для поиска цен на карты MTG, введите название карты, которая вас интересует." func main() { - dict, _ := os.Open("./assets/additional_cards.json") - dictBytes, _ := ioutil.ReadAll(dict) - var dictMap map[string]string - _ = json.Unmarshal(dictBytes, &dictMap) - bot, _ := tgbotapi.NewBotAPI(os.Getenv("TG_TOKEN")) + token, exists := os.LookupEnv("TG_TOKEN") + if !exists { + panic("TG_TOKEN environment variable not defined") + } + bot, _ := tgbotapi.NewBotAPI(token) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + dsn, exists := os.LookupEnv("YDB_CONNECTION_STRING") + if !exists { + panic("YDB_CONNECTION_STRING environment variable not defined") + } + + db, err := ydb.Open(ctx, + dsn, + environ.WithEnvironCredentials(ctx), + ) + if err != nil { + panic(fmt.Errorf("connect error: %w", err)) + } + defer func() { _ = db.Close(ctx) }() + sender := &telegram.Sender{ - API: bot, + API: bot, + } + cache := &caching.CacheClient{ + Storage: db.Table(), + Expiration: 12 * time.Hour, + Prefix: db.Name(), + } + err = cache.Init(context.Background()) + if err != nil { + panic(fmt.Errorf("init error: %w", err)) } sc := &scenario.Scenario{ - Sender: sender, - Logger: log.New(os.Stdout, "", 0), - Cache: caching.NewClient("redis:6379", "", time.Hour*24, 0), - InfoFetcher: &cardsinfo.Fetcher{ - Dict: dictMap, - }, + Sender: sender, + Logger: log.New(os.Stdout, "", 0), + Cache: cache, + InfoFetcher: &cardsinfo.Fetcher{}, } u := tgbotapi.NewUpdate(0) updates, _ := bot.GetUpdatesChan(u) for update := range updates { - if update.Message == nil { - continue - } + if update.Message == nil { + continue + } - if update.Message.Text == "/start" { - sender.Send(update.Message.Chat.ID, welcomeMessage) - continue - } + if update.Message.Text == "/start" { + sender.Send(update.Message.Chat.ID, welcomeMessage) + continue + } - go sc.HandleSearch(&scenario.UserMessage{ - Body: update.Message.Text, - UserId: update.Message.Chat.ID, - }) - } + go sc.HandleSearch(context.Background(), &scenario.UserMessage{ + Body: update.Message.Text, + UserId: update.Message.Chat.ID, + }) + } } diff --git a/cmd/vk/main.go b/cmd/vk/main.go index 2957a10..eedf0ef 100644 --- a/cmd/vk/main.go +++ b/cmd/vk/main.go @@ -1,19 +1,21 @@ package main import ( - "encoding/json" - "io/ioutil" + "context" + "fmt" "log" "math/rand" "os" "strconv" "time" + "gitlab.com/flygrounder/go-mtg-vk/internal/caching" "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" "gitlab.com/flygrounder/go-mtg-vk/internal/scenario" "github.com/gin-gonic/gin" - "gitlab.com/flygrounder/go-mtg-vk/internal/caching" + environ "github.com/ydb-platform/ydb-go-sdk-auth-environ" + "github.com/ydb-platform/ydb-go-sdk/v3" "gitlab.com/flygrounder/go-mtg-vk/internal/vk" ) @@ -22,11 +24,34 @@ func main() { r := gin.Default() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + dsn, exists := os.LookupEnv("YDB_CONNECTION_STRING") + if !exists { + panic("YDB_CONNECTION_STRING environment variable not defined") + } + + db, err := ydb.Open(ctx, + dsn, + environ.WithEnvironCredentials(ctx), + ) + if err != nil { + panic(fmt.Errorf("connect error: %w", err)) + } + defer func() { _ = db.Close(ctx) }() + + cache := &caching.CacheClient{ + Storage: db.Table(), + Expiration: 12 * time.Hour, + Prefix: db.Name(), + } + err = cache.Init(context.Background()) + if err != nil { + panic(fmt.Errorf("init error: %w", err)) + } + groupId, _ := strconv.ParseInt(os.Getenv("VK_GROUP_ID"), 10, 64) - dict, _ := os.Open("./assets/additional_cards.json") - dictBytes, _ := ioutil.ReadAll(dict) - var dictMap map[string]string - _ = json.Unmarshal(dictBytes, &dictMap) logger := log.New(os.Stdout, "", 0) handler := vk.Handler{ Scenario: &scenario.Scenario{ @@ -34,11 +59,9 @@ func main() { Token: os.Getenv("VK_TOKEN"), Logger: logger, }, - Logger: logger, - Cache: caching.NewClient("redis:6379", "", time.Hour*24, 0), - InfoFetcher: &cardsinfo.Fetcher{ - Dict: dictMap, - }, + Logger: logger, + InfoFetcher: &cardsinfo.Fetcher{}, + Cache: cache, }, SecretKey: os.Getenv("VK_SECRET_KEY"), GroupId: groupId, diff --git a/docker-compose.yaml b/docker-compose.yaml index 089a07e..5eea809 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -6,6 +6,8 @@ services: args: - VERSION=vk environment: + - YDB_CONNECTION_STRING + - YDB_ACCESS_TOKEN_CREDENTIALS - VK_TOKEN - VK_SECRET_KEY - VK_GROUP_ID @@ -20,8 +22,7 @@ services: args: - VERSION=telegram environment: + - YDB_CONNECTION_STRING + - YDB_ACCESS_TOKEN_CREDENTIALS - TG_TOKEN - restart: "always" - redis: - image: "redis:6.0.9" - restart: "always" + restart: "always" \ No newline at end of file diff --git a/go.mod b/go.mod index 901c88c..a05ecba 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,22 @@ module gitlab.com/flygrounder/go-mtg-vk go 1.12 require ( - github.com/alicebob/miniredis/v2 v2.14.1 github.com/antchfx/htmlquery v1.2.3 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/gin-gonic/gin v1.4.0 - github.com/go-redis/redis v6.15.2+incompatible github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible - github.com/onsi/ginkgo v1.10.3 // indirect - github.com/onsi/gomega v1.7.1 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.5.1 + github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/stretchr/testify v1.8.1 github.com/technoweenie/multipartstreamer v1.0.1 // indirect - github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c - github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect + github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.3 + github.com/ydb-platform/ydb-go-sdk/v3 v3.47.2 + golang.org/x/net v0.10.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/h2non/gock.v1 v1.0.16 + gopkg.in/yaml.v2 v2.2.4 // indirect ) diff --git a/go.sum b/go.sum index 5e3aa83..e8f26b2 100644 --- a/go.sum +++ b/go.sum @@ -1,87 +1,266 @@ -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= -github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.14.1 h1:GjlbSeoJ24bzdLRs13HoMEeaRZx9kg5nHoRW7QV/nCs= -github.com/alicebob/miniredis/v2 v2.14.1/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M= github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0= github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= -github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= -github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg= -github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e h1:9LPdmD1vqadsDQUva6t2O9MbnyvoOgo8nFNPaOIH5U8= +github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20220203104745-929cf9c248bc/go.mod h1:cc138nptTn9eKptCQl/grxP6pBKpo/bnXDiOxuVZtps= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20221215182650-986f9d10542f h1:BBczNIM1MJHT7XkIUA8pThXWxJvxoBjcWvne3xwe2RI= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20221215182650-986f9d10542f/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.3 h1:+DrFgi0hjjLarFcWHwI6WPk28hcWr6N7ga+OHAxtemI= +github.com/ydb-platform/ydb-go-sdk-auth-environ v0.1.3/go.mod h1:K5wHHoLBfmWc7zyiETOA1Spx7DN+4skQ7YeWy5snXKo= +github.com/ydb-platform/ydb-go-sdk/v3 v3.25.3/go.mod h1:PFizF/vJsdAgEwjK3DVSBD52kdmRkWfSIS2q2pA+e88= +github.com/ydb-platform/ydb-go-sdk/v3 v3.47.1/go.mod h1:oSLwnuilwIpaF5bJJMAofnGgzPJusoI3zWMNb8I+GnM= +github.com/ydb-platform/ydb-go-sdk/v3 v3.47.2 h1:nBPXs27hwve26x2DIec45IO0EnxZiB63SwTIPanre/4= +github.com/ydb-platform/ydb-go-sdk/v3 v3.47.2/go.mod h1:oSLwnuilwIpaF5bJJMAofnGgzPJusoI3zWMNb8I+GnM= +github.com/ydb-platform/ydb-go-yc v0.10.2 h1:RAHy6g7ncxk1y0N4oS2MwYXLATqRqKBI6DYXuxpV2wo= +github.com/ydb-platform/ydb-go-yc v0.10.2/go.mod h1:U1dX3LJy6zADId2DciCXlgrU/vphK1+CQzaefKq21dQ= +github.com/ydb-platform/ydb-go-yc-metadata v0.5.2 h1:nMtixUijP0Z7iHJNT9fOL+dbmEzZxqU6Xk87ll7hqXg= +github.com/ydb-platform/ydb-go-yc-metadata v0.5.2/go.mod h1:82SQ4L3PewiEmFW4oTMc1sfPjODasIYxD/SKGsbK74s= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3 h1:SeX3QUcBj3fciwnfPT9kt5gBhFy/FCZtYZ+I/RB8agc= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= @@ -89,8 +268,13 @@ gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/R gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg= gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8= gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/caching/cache.go b/internal/caching/cache.go index 415d9d6..d287e10 100644 --- a/internal/caching/cache.go +++ b/internal/caching/cache.go @@ -1,41 +1,107 @@ package caching import ( + "context" "encoding/json" + "fmt" + "path" "time" - "github.com/go-redis/redis" "github.com/pkg/errors" + "github.com/ydb-platform/ydb-go-sdk/v3/table" + "github.com/ydb-platform/ydb-go-sdk/v3/table/options" + "github.com/ydb-platform/ydb-go-sdk/v3/table/result/named" + "github.com/ydb-platform/ydb-go-sdk/v3/table/types" "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" ) type CacheClient struct { - Storage *redis.Client + Storage table.Client Expiration time.Duration + Prefix string } -func NewClient(addr string, passwd string, expiration time.Duration, db int) *CacheClient { - return &CacheClient{ - Storage: redis.NewClient(&redis.Options{ - Addr: addr, - Password: passwd, - DB: db, - }), - Expiration: expiration, - } +func (client *CacheClient) Init(ctx context.Context) error { + return client.Storage.Do(ctx, func(ctx context.Context, s table.Session) error { + return s.CreateTable( + ctx, + path.Join(client.Prefix, "cache"), + options.WithColumn("card", types.TypeString), + options.WithColumn("prices", types.Optional(types.TypeJSON)), + options.WithColumn("created_at", types.Optional(types.TypeTimestamp)), + options.WithTimeToLiveSettings( + options.NewTTLSettings().ColumnDateType("created_at").ExpireAfter(client.Expiration), + ), + options.WithPrimaryKeyColumn("card"), + ) + }) } -func (client *CacheClient) Set(key string, prices []cardsinfo.ScgCardPrice) { +func (client *CacheClient) Set(ctx context.Context, key string, prices []cardsinfo.ScgCardPrice) error { + const query = ` + DECLARE $cacheData AS List>; + + INSERT INTO cache SELECT cd.card AS card, cd.prices AS prices, cd.created_at AS created_at FROM AS_TABLE($cacheData) cd LEFT OUTER JOIN cache c ON cd.card = c.card WHERE c.card IS NULL` value, _ := json.Marshal(prices) - client.Storage.Set(key, value, client.Expiration) + return client.Storage.Do(ctx, func(ctx context.Context, s table.Session) error { + _, _, err := s.Execute(ctx, writeTx(), query, table.NewQueryParameters( + table.ValueParam("$cacheData", types.ListValue( + types.StructValue( + types.StructFieldValue("card", types.StringValueFromString(key)), + types.StructFieldValue("prices", types.JSONValueFromBytes(value)), + types.StructFieldValue("created_at", types.TimestampValueFromTime(time.Now())), + ))), + )) + return err + }) } -func (client *CacheClient) Get(key string) ([]cardsinfo.ScgCardPrice, error) { - c, err := client.Storage.Get(key).Result() +func (client *CacheClient) Get(ctx context.Context, key string) ([]cardsinfo.ScgCardPrice, error) { + const query = ` + DECLARE $card AS String; + + SELECT UNWRAP(prices) AS prices FROM cache WHERE card = $card` + var pricesStr string + err := client.Storage.Do(ctx, func(ctx context.Context, s table.Session) error { + _, res, err := s.Execute(ctx, readTx(), query, table.NewQueryParameters( + table.ValueParam("$card", types.StringValueFromString(key)), + )) + if err != nil { + return err + } + ok := res.NextResultSet(ctx) + if !ok { + return errors.New("no key") + } + ok = res.NextRow() + if !ok { + return errors.New("no key") + } + err = res.ScanNamed( + named.Required("prices", &pricesStr), + ) + return err + }) if err != nil { - return nil, errors.Wrap(err, "No such key in cache") + fmt.Println(err.Error()) + return nil, errors.Wrap(err, "Failed to get key") } var prices []cardsinfo.ScgCardPrice - json.Unmarshal([]byte(c), &prices) + json.Unmarshal([]byte(pricesStr), &prices) return prices, nil } + +func writeTx() *table.TransactionControl { + return table.TxControl(table.BeginTx( + table.WithSerializableReadWrite(), + ), table.CommitTx()) +} + +func readTx() *table.TransactionControl { + return table.TxControl(table.BeginTx( + table.WithOnlineReadOnly(), + ), table.CommitTx()) +} diff --git a/internal/caching/cache_test.go b/internal/caching/cache_test.go deleted file mode 100644 index 94074da..0000000 --- a/internal/caching/cache_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package caching - -import ( - "fmt" - "testing" - "time" - - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis" - "github.com/stretchr/testify/assert" - "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" -) - -func TestGetClient(t *testing.T) { - c := NewClient("addr", "123", time.Hour, 1) - assert.Equal(t, time.Hour, c.Expiration) - assert.Equal(t, 1, c.Storage.Options().DB) - assert.Equal(t, "addr", c.Storage.Options().Addr) - assert.Equal(t, "123", c.Storage.Options().Password) -} - -func TestGetSet(t *testing.T) { - client, s := getTestClient() - defer s.Close() - - keyName := "test_key" - value := []cardsinfo.ScgCardPrice{ - { - Price: "1", - Edition: "Alpha", - Link: "scg", - }, - } - client.Set(keyName, value) - val, err := client.Get(keyName) - assert.Nil(t, err) - assert.Equal(t, value, val) -} - -func TestExpiration(t *testing.T) { - client, s := getTestClient() - defer s.Close() - - client.Expiration = time.Millisecond - keyName := "test_key" - var value []cardsinfo.ScgCardPrice - client.Set(keyName, value) - s.FastForward(time.Millisecond * 2) - val, err := client.Get(keyName) - assert.Nil(t, val) - assert.NotNil(t, err) -} - -func getTestClient() (*CacheClient, *miniredis.Miniredis) { - s, _ := miniredis.Run() - fmt.Println(s.Addr()) - c := redis.NewClient(&redis.Options{ - Addr: s.Addr(), - }) - return &CacheClient{ - Storage: c, - Expiration: 0, - }, s -} diff --git a/internal/cardsinfo/fetcher.go b/internal/cardsinfo/fetcher.go index 252fcf4..c66b357 100644 --- a/internal/cardsinfo/fetcher.go +++ b/internal/cardsinfo/fetcher.go @@ -1,5 +1,4 @@ package cardsinfo type Fetcher struct { - Dict map[string]string } diff --git a/internal/cardsinfo/names.go b/internal/cardsinfo/names.go index a237aaf..4ff67cb 100644 --- a/internal/cardsinfo/names.go +++ b/internal/cardsinfo/names.go @@ -6,8 +6,6 @@ import ( "net/http" "net/url" "strings" - - "gitlab.com/flygrounder/go-mtg-vk/internal/dicttranslate" ) const scryfallUrl = "https://api.scryfall.com" @@ -23,9 +21,6 @@ func (f *Fetcher) GetNameByCardId(set string, number string) string { func (f *Fetcher) GetOriginalName(name string) string { path := scryfallUrl + "/cards/named?fuzzy=" + applyFilters(name) result := getCardByUrl(path) - if result == "" && f.Dict != nil { - result, _ = dicttranslate.Find(name, f.Dict, 5) - } return result } diff --git a/internal/cardsinfo/names_test.go b/internal/cardsinfo/names_test.go index 3b947e6..4855929 100644 --- a/internal/cardsinfo/names_test.go +++ b/internal/cardsinfo/names_test.go @@ -31,21 +31,6 @@ func TestGetOriginalName_Scryfall(t *testing.T) { assert.Equal(t, "Result Card", name) } -func TestGetOriginalName_DictTwice(t *testing.T) { - defer gock.Off() - - gock.New(scryfallUrl + "/cards/named?fuzzy=card").Persist().Reply(http.StatusOK).JSON(card{}) - f := &Fetcher{ - Dict: map[string]string{ - "card": "Card", - }, - } - name := f.GetOriginalName("card") - assert.Equal(t, "Card", name) - name = f.GetOriginalName("card") - assert.Equal(t, "Card", name) -} - func TestGetOriginalName_BadJson(t *testing.T) { defer gock.Off() diff --git a/internal/dicttranslate/find.go b/internal/dicttranslate/find.go deleted file mode 100644 index b4e5f71..0000000 --- a/internal/dicttranslate/find.go +++ /dev/null @@ -1,10 +0,0 @@ -package dicttranslate - -func Find(query string, dict map[string]string, maxDist int) (string, bool) { - var keys []string - for i := range dict { - keys = append(keys, i) - } - key, f := match(query, keys, maxDist) - return dict[key], f -} diff --git a/internal/dicttranslate/find_test.go b/internal/dicttranslate/find_test.go deleted file mode 100644 index e778646..0000000 --- a/internal/dicttranslate/find_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package dicttranslate - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestFindEmpty(t *testing.T) { - dict := map[string]string{} - _, f := Find("", dict, 0) - assert.False(t, f) -} - -func TestFindEntry(t *testing.T) { - dict := map[string]string{ - "entry": "value", - } - val, f := Find("entry", dict, 0) - assert.True(t, f) - assert.Equal(t, "value", val) -} diff --git a/internal/dicttranslate/match.go b/internal/dicttranslate/match.go deleted file mode 100644 index 968bc69..0000000 --- a/internal/dicttranslate/match.go +++ /dev/null @@ -1,24 +0,0 @@ -package dicttranslate - -import ( - "github.com/texttheater/golang-levenshtein/levenshtein" - "strings" -) - -func match(query string, opts []string, maxDist int) (string, bool) { - bestInd := -1 - bestDist := 0 - for i, s := range opts { - cfg := levenshtein.DefaultOptions - cfg.SubCost = 1 - dist := levenshtein.DistanceForStrings([]rune(strings.ToLower(s)), []rune(strings.ToLower(query)), cfg) - if dist <= maxDist && (bestInd == -1 || dist < bestDist) { - bestInd = i - bestDist = dist - } - } - if bestInd == -1 { - return "", false - } - return opts[bestInd], true -} diff --git a/internal/dicttranslate/match_test.go b/internal/dicttranslate/match_test.go deleted file mode 100644 index 5d7b852..0000000 --- a/internal/dicttranslate/match_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package dicttranslate - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestMatch(t *testing.T) { - type testCase struct { - name string - query string - opts []string - shouldFind bool - match string - } - tests := []testCase{ - { - name: "No options", - query: "opt", - opts: []string{}, - shouldFind: false, - }, - { - name: "Match one", - query: "option", - opts: []string{"opt1on"}, - shouldFind: true, - match: "opt1on", - }, - { - name: "Match exact", - query: "opt1on", - opts: []string{"option", "opt1on"}, - shouldFind: true, - match: "opt1on", - }, - { - name: "Do not match bad options", - query: "random", - opts: []string{"option", "opt1on"}, - shouldFind: false, - }, - { - name: "Match case insensitive", - query: "option", - opts: []string{"OPTION", "opt1on"}, - shouldFind: true, - match: "OPTION", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - val, f := match(test.query, test.opts, 1) - assert.Equal(t, test.shouldFind, f) - assert.Equal(t, test.match, val) - }) - } -} diff --git a/internal/scenario/scenario.go b/internal/scenario/scenario.go index e6bdecd..a5f6233 100644 --- a/internal/scenario/scenario.go +++ b/internal/scenario/scenario.go @@ -1,7 +1,9 @@ package scenario import ( + "context" "errors" + "fmt" "log" "strings" @@ -27,8 +29,9 @@ type UserMessage struct { } type CardCache interface { - Get(cardName string) ([]cardsinfo.ScgCardPrice, error) - Set(cardName string, prices []cardsinfo.ScgCardPrice) + Init(ctx context.Context) error + Get(ctx context.Context, cardName string) ([]cardsinfo.ScgCardPrice, error) + Set(ctx context.Context, cardName string, prices []cardsinfo.ScgCardPrice) error } type CardInfoFetcher interface { @@ -42,7 +45,7 @@ type Sender interface { SendPrices(userId int64, cardName string, prices []cardsinfo.ScgCardPrice) } -func (s *Scenario) HandleSearch(msg *UserMessage) { +func (s *Scenario) HandleSearch(ctx context.Context, msg *UserMessage) { cardName, err := s.getCardNameByCommand(msg.Body) if err != nil { s.Sender.Send(msg.UserId, incorrectMessage) @@ -51,7 +54,7 @@ func (s *Scenario) HandleSearch(msg *UserMessage) { s.Sender.Send(msg.UserId, cardNotFoundMessage) s.Logger.Printf("[info] Could not find card. User input: %s", msg.Body) } else { - prices, err := s.Cache.Get(cardName) + prices, err := s.Cache.Get(ctx, cardName) if err == nil { s.Sender.SendPrices(msg.UserId, cardName, prices) return @@ -62,7 +65,10 @@ func (s *Scenario) HandleSearch(msg *UserMessage) { s.Logger.Printf("[error] Could not find SCG prices. Message: %s card name: %s", err.Error(), cardName) return } - s.Cache.Set(cardName, prices) + err = s.Cache.Set(ctx, cardName, prices) + if err != nil { + s.Logger.Println(fmt.Errorf("failed add entry in cache: %w", err)) + } s.Sender.SendPrices(msg.UserId, cardName, prices) } } diff --git a/internal/scenario/scenario_test.go b/internal/scenario/scenario_test.go index 4b038b7..6800449 100644 --- a/internal/scenario/scenario_test.go +++ b/internal/scenario/scenario_test.go @@ -1,6 +1,7 @@ package scenario import ( + "context" "strings" "testing" @@ -9,7 +10,7 @@ import ( func TestScenario_HandleSearch_BadCommand(t *testing.T) { testCtx := GetTestScenarioCtx() - testCtx.Scenario.HandleSearch(&UserMessage{ + testCtx.Scenario.HandleSearch(context.Background(), &UserMessage{ Body: "!s", UserId: 1, }) @@ -24,7 +25,7 @@ func TestScenario_HandleSearch_BadCommand(t *testing.T) { func TestScenario_HandleSearch_GoodCommand(t *testing.T) { testCtx := GetTestScenarioCtx() - testCtx.Scenario.HandleSearch(&UserMessage{ + testCtx.Scenario.HandleSearch(context.Background(), &UserMessage{ Body: "!s grn 228", UserId: 1, }) @@ -38,7 +39,7 @@ func TestScenario_HandleSearch_GoodCommand(t *testing.T) { func TestScenario_HandleSearch_NotFoundCard(t *testing.T) { testCtx := GetTestScenarioCtx() - testCtx.Scenario.HandleSearch(&UserMessage{ + testCtx.Scenario.HandleSearch(context.Background(), &UserMessage{ Body: "absolutely_random_card", UserId: 1, }) @@ -53,7 +54,7 @@ func TestScenario_HandleSearch_NotFoundCard(t *testing.T) { func TestScenario_HandleSearch_BadCard(t *testing.T) { testCtx := GetTestScenarioCtx() - testCtx.Scenario.HandleSearch(&UserMessage{ + testCtx.Scenario.HandleSearch(context.Background(), &UserMessage{ Body: "bad", UserId: 1, }) @@ -67,7 +68,7 @@ func TestScenario_HandleSearch_BadCard(t *testing.T) { } func TestScenario_HandleSearch_Uncached(t *testing.T) { testCtx := GetTestScenarioCtx() - testCtx.Scenario.HandleSearch(&UserMessage{ + testCtx.Scenario.HandleSearch(context.Background(), &UserMessage{ Body: "uncached", UserId: 1, }) @@ -77,6 +78,6 @@ func TestScenario_HandleSearch_Uncached(t *testing.T) { message: "uncached", }, }, testCtx.Sender.sent) - _, err := testCtx.Scenario.Cache.Get("uncached") + _, err := testCtx.Scenario.Cache.Get(context.Background(), "uncached") assert.Nil(t, err) } diff --git a/internal/scenario/test_cache.go b/internal/scenario/test_cache.go index 5ddc215..7052fcf 100644 --- a/internal/scenario/test_cache.go +++ b/internal/scenario/test_cache.go @@ -1,6 +1,7 @@ package scenario import ( + "context" "errors" "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" @@ -10,7 +11,11 @@ type testCache struct { table map[string][]cardsinfo.ScgCardPrice } -func (t *testCache) Get(cardName string) ([]cardsinfo.ScgCardPrice, error) { +func (t *testCache) Init(ctx context.Context) error { + return nil +} + +func (t *testCache) Get(ctx context.Context, cardName string) ([]cardsinfo.ScgCardPrice, error) { msg, ok := t.table[cardName] if !ok { return nil, errors.New("test") @@ -18,6 +23,7 @@ func (t *testCache) Get(cardName string) ([]cardsinfo.ScgCardPrice, error) { return msg, nil } -func (t *testCache) Set(cardName string, prices []cardsinfo.ScgCardPrice) { +func (t *testCache) Set(ctx context.Context, cardName string, prices []cardsinfo.ScgCardPrice) error { t.table[cardName] = prices + return nil } diff --git a/internal/vk/handler.go b/internal/vk/handler.go index f8184df..77eae6b 100644 --- a/internal/vk/handler.go +++ b/internal/vk/handler.go @@ -1,6 +1,7 @@ package vk import ( + "context" "net/http" "github.com/gin-gonic/gin" @@ -8,7 +9,7 @@ import ( ) type Handler struct { - Scenario *scenario.Scenario + Scenario *scenario.Scenario SecretKey string GroupId int64 ConfirmationString string @@ -54,7 +55,7 @@ func (h *Handler) HandleMessage(c *gin.Context) { case "confirmation": h.handleConfirmation(c, &req) case "message_new": - go h.Scenario.HandleSearch(&scenario.UserMessage{ + go h.Scenario.HandleSearch(context.Background(), &scenario.UserMessage{ Body: req.Object.Body, UserId: req.Object.UserId, })