Added support for multiple languages

This commit is contained in:
StNicolay 2024-04-16 16:02:48 +03:00
parent aded63f9d5
commit c5855fced7
Signed by: StNicolay
GPG Key ID: 9693D04DCD962B0D
52 changed files with 973 additions and 409 deletions

View File

@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "INSERT INTO master_pass VALUES (?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "1fc824ca1ca447a990c3e68ee4f2a15b8e5bc260641057d914bb7ff871b51aa3"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "UPDATE master_pass SET locale = ? WHERE user_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "71661faf7bde5ba94a5090dde7fb3ee72e9b2860692e30333f2213188188fdb6"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "INSERT INTO master_pass VALUES (?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "ff36e44aa2d064634d3ad183ce503cd30555d1d745cf5ab486c0d17e8e9a026f"
}

304
Cargo.lock generated
View File

@ -42,24 +42,24 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.16" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.80" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]] [[package]]
name = "aquamarine" name = "aquamarine"
@ -89,27 +89,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "atomic-write-file"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8204db279bf648d64fe845bd8840f78b39c8132ed4d6a4194c3b10d4b4cfb0b"
dependencies = [
"nix",
"rand",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
@ -146,9 +136,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.4.2" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -164,9 +154,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.15.3" version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -176,15 +166,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.90" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -192,12 +182,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]] [[package]]
name = "chacha20" name = "chacha20"
version = "0.9.1" version = "0.9.1"
@ -224,9 +208,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.35" version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -281,9 +265,9 @@ dependencies = [
[[package]] [[package]]
name = "crc" name = "crc"
version = "3.0.1" version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
dependencies = [ dependencies = [
"crc-catalog", "crc-catalog",
] ]
@ -325,7 +309,7 @@ name = "cryptography"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bitflags 2.4.2", "bitflags 2.5.0",
"chacha20poly1305", "chacha20poly1305",
"entity", "entity",
"once_cell", "once_cell",
@ -375,9 +359,9 @@ dependencies = [
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.8" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"pem-rfc7468", "pem-rfc7468",
@ -426,18 +410,18 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.10.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.33" version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -509,9 +493,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.1" version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
[[package]] [[package]]
name = "finl_unicode" name = "finl_unicode"
@ -612,7 +596,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
@ -657,9 +641,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.12" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -674,9 +658,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.24" version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -854,9 +838,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.5" version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -908,9 +892,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.10" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
@ -987,9 +971,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]] [[package]]
name = "mime" name = "mime"
@ -1039,18 +1023,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.2",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -1180,6 +1152,7 @@ dependencies = [
"pretty_env_logger", "pretty_env_logger",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml",
"sha2", "sha2",
"teloxide", "teloxide",
"thiserror", "thiserror",
@ -1246,14 +1219,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1341,18 +1314,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.78" version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1407,9 +1380,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.3" version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1430,15 +1403,15 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.24" version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
@ -1530,11 +1503,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.31" version = "0.38.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.5.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -1638,14 +1611,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.114" version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1676,6 +1649,19 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@ -1728,9 +1714,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.1" version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "socket2" name = "socket2"
@ -1780,9 +1766,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx" name = "sqlx"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
dependencies = [ dependencies = [
"sqlx-core", "sqlx-core",
"sqlx-macros", "sqlx-macros",
@ -1793,9 +1779,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-core" name = "sqlx-core"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
dependencies = [ dependencies = [
"ahash", "ahash",
"atoi", "atoi",
@ -1803,7 +1789,6 @@ dependencies = [
"bytes", "bytes",
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"dotenvy",
"either", "either",
"event-listener", "event-listener",
"futures-channel", "futures-channel",
@ -1836,9 +1821,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-macros" name = "sqlx-macros"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1849,11 +1834,10 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-macros-core" name = "sqlx-macros-core"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
dependencies = [ dependencies = [
"atomic-write-file",
"dotenvy", "dotenvy",
"either", "either",
"heck", "heck",
@ -1874,13 +1858,13 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-mysql" name = "sqlx-mysql"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64 0.21.7", "base64 0.21.7",
"bitflags 2.4.2", "bitflags 2.5.0",
"byteorder", "byteorder",
"bytes", "bytes",
"crc", "crc",
@ -1916,13 +1900,13 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-postgres" name = "sqlx-postgres"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64 0.21.7", "base64 0.21.7",
"bitflags 2.4.2", "bitflags 2.5.0",
"byteorder", "byteorder",
"crc", "crc",
"dotenvy", "dotenvy",
@ -1943,7 +1927,6 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"serde_json", "serde_json",
"sha1",
"sha2", "sha2",
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
@ -1955,9 +1938,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlx-sqlite" name = "sqlx-sqlite"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
dependencies = [ dependencies = [
"atoi", "atoi",
"flume", "flume",
@ -2011,9 +1994,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.52" version = "2.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2152,22 +2135,22 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.57" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.57" version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
@ -2187,9 +2170,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.36.0" version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2211,7 +2194,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
@ -2226,9 +2209,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.14" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",
@ -2275,7 +2258,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]
@ -2357,6 +2340,12 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@ -2383,9 +2372,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [ dependencies = [
"getrandom", "getrandom",
] ]
@ -2450,7 +2439,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2484,7 +2473,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2526,9 +2515,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e" checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [ dependencies = [
"redox_syscall", "redox_syscall",
"wasite", "wasite",
@ -2580,7 +2569,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.4", "windows-targets 0.52.5",
] ]
[[package]] [[package]]
@ -2600,17 +2589,18 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.4", "windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.4", "windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.4", "windows_i686_gnu 0.52.5",
"windows_i686_msvc 0.52.4", "windows_i686_gnullvm",
"windows_x86_64_gnu 0.52.4", "windows_i686_msvc 0.52.5",
"windows_x86_64_gnullvm 0.52.4", "windows_x86_64_gnu 0.52.5",
"windows_x86_64_msvc 0.52.4", "windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
] ]
[[package]] [[package]]
@ -2621,9 +2611,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -2633,9 +2623,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -2645,9 +2635,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -2657,9 +2653,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -2669,9 +2665,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -2681,9 +2677,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -2693,9 +2689,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.4" version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]] [[package]]
name = "winreg" name = "winreg"
@ -2724,7 +2720,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.52", "syn 2.0.59",
] ]
[[package]] [[package]]

View File

@ -36,6 +36,7 @@ parking_lot = "0.12"
pretty_env_logger = "0.5" pretty_env_logger = "0.5"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
serde_yaml = "0.9"
sha2 = "0.10" sha2 = "0.10"
teloxide = { version = "0.12", features = [ teloxide = { version = "0.12", features = [
"macros", "macros",

View File

@ -0,0 +1,3 @@
-- Add down migration script here
ALTER TABLE master_pass
DROP COLUMN locale;

View File

@ -0,0 +1,3 @@
-- Add up migration script here
ALTER TABLE master_pass
ADD COLUMN locale SMALLINT UNSIGNED NOT NULL DEFAULT 1;

View File

@ -2,6 +2,7 @@
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
pub mod account; pub mod account;
pub mod locale;
pub mod master_pass; pub mod master_pass;
pub mod prelude; pub mod prelude;

50
entity/src/locale.rs Normal file
View File

@ -0,0 +1,50 @@
use crate::prelude::*;
use sqlx::{mysql::MySqlQueryResult as QueryResult, query, query_as};
#[derive(Clone, Copy, Default)]
#[allow(clippy::module_name_repetitions)]
pub enum LocaleType {
#[default]
Eng = 1,
Ru = 2,
}
impl TryFrom<u8> for LocaleType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Eng),
2 => Ok(Self::Ru),
_ => Err(()),
}
}
}
impl From<LocaleType> for u8 {
fn from(value: LocaleType) -> Self {
value as Self
}
}
impl LocaleType {
pub async fn get_from_db(user_id: u64, db: &Pool) -> crate::Result<Option<Self>> {
let result: Option<(u8,)> = query_as("SELECT locale FROM master_pass WHERE user_id = ?")
.bind(user_id)
.fetch_optional(db)
.await?;
Ok(result.and_then(|val| val.0.try_into().ok()))
}
pub async fn update(self, user_id: u64, db: &Pool) -> crate::Result<bool> {
let result: QueryResult = query!(
"UPDATE master_pass SET locale = ? WHERE user_id = ?",
u8::from(self),
user_id
)
.execute(db)
.await?;
Ok(result.rows_affected() == 1)
}
}

View File

@ -1,4 +1,4 @@
use super::Pool; use crate::{locale::LocaleType, Pool};
use sqlx::{prelude::FromRow, query, query_as, Executor, MySql}; use sqlx::{prelude::FromRow, query, query_as, Executor, MySql};
#[derive(Clone, Debug, PartialEq, FromRow, Eq)] #[derive(Clone, Debug, PartialEq, FromRow, Eq)]
@ -11,12 +11,14 @@ pub struct MasterPass {
impl MasterPass { impl MasterPass {
// Inserts the master password into DB // Inserts the master password into DB
#[inline] #[inline]
pub async fn insert(&self, pool: &Pool) -> crate::Result<()> { pub async fn insert(&self, pool: &Pool, locale: LocaleType) -> crate::Result<()> {
let locale: u8 = locale.into();
query!( query!(
"INSERT INTO master_pass VALUES (?, ?, ?)", "INSERT INTO master_pass VALUES (?, ?, ?, ?)",
self.user_id, self.user_id,
self.salt, self.salt,
self.password_hash self.password_hash,
locale
) )
.execute(pool) .execute(pool)
.await .await
@ -26,7 +28,7 @@ impl MasterPass {
/// Gets the master password from the database /// Gets the master password from the database
#[inline] #[inline]
pub async fn get(user_id: u64, pool: &Pool) -> crate::Result<Option<Self>> { pub async fn get(user_id: u64, pool: &Pool) -> crate::Result<Option<Self>> {
query_as("SELECT * FROM master_pass WHERE user_id = ?") query_as("SELECT user_id, salt, password_hash FROM master_pass WHERE user_id = ?")
.bind(user_id) .bind(user_id)
.fetch_optional(pool) .fetch_optional(pool)
.await .await

83
locales/eng.yaml Normal file
View File

@ -0,0 +1,83 @@
master_password_is_not_set: "Master password is not set. Use /cancel to cancel the current action and /set_master_pass to set it"
hide_button: "Hide"
change_name_button: "Change name"
change_login_button: "Change login"
change_password_button: "Change password"
delete_account_button: "Delete account"
couldnt_get_user_info_send_again: "Couldn't get user information. Send the message again"
unknown_command_use_help: "Unknown command. Use /help to get info on existing commands"
help_command: |
These commands are supported:
/start — displays the welcome message
/help — displays this text
/set_master_pass — sets the master password
/menu — gives you a menu to manage your accounts
/add_account — adds the account
/get_account — gets the account
/get_accounts — gets a list of accounts
/delete — deletes the account
/delete_all — deletes all the accounts and the master password
/export — exports all the accounts in a json file
/import — loads the accounts from a json file
/gen_password — generates 10 secure passwords
/cancel — cancels the current action
/change_language - allows you to change the language
no_file_send: "Expected you to send a file"
file_too_large: "File is larger that 200 MiB. Please split it into multiple files"
couldnt_get_file_name: "Couldn't get file name. Please try again"
following_accounts_have_problems: "Following accounts have problems:"
duplicate_names: "Duplicate names"
accounts_already_in_db: "Accounts with these names already exist in the database"
invalid_fields: "Accounts have invalid names"
fix_that_and_send_again: "Fix those errors and send the file again"
error_downloading_file: "Error downloading the file"
error_getting_account_names: "Error getting existing account names"
error_parsing_json_file: "Error parsing the json file"
successfully_canceled: "Successfully canceled"
invalid_password: "The password is invalid. Change it and send it again"
couldnt_get_message_text: "Couldn't get the text of the message. Send the message again"
invalid_file_name: "Invalid file name. The program expected you to send a file with .json extension"
account_already_exists: "Account with that names already exists"
master_password_too_weak: "Master password is too weak"
no_lowercase: "It doesn't have any lowercase characters"
no_uppercase: "It doesn't have any uppercase characters"
no_numbers: "It doesn't have any numbers"
master_pass_too_short: "It is shorter than 8 characters"
change_master_password_and_send_again: "Change the master password and send it again"
wrong_master_password: "Wrong master password. Try again"
invalid_login: "Invalid login. Change it and send it again"
start_command: "Hi! This bot can be used to store the passwords securely. Use /help command to get the list of commands"
master_password_dont_match: "Master password didn't match. Use the command again"
success: "Success"
send_master_password_again: "Send the master password again"
master_password_is_set: "Master password has already been set"
send_new_master_password: "Send new master password"
no_accounts_found: "No accounts found"
choose_account: "Choose the account"
couldnt_create_following_accounts: "Couldn't create the following accounts"
send_master_password: "Send the master password"
something_went_wrong: "Something went wrong. Try again later"
nothing_to_cancel: "Nothing to cancel"
send_account_name: "Send account name"
send_login: "Send login"
error_deleting_message: "Error deleting the message"
account_not_found: "Account wasn't found"
send_new_name: "Send new name"
send_new_login: "Send new login"
send_new_password: "Send new password"
success_choose_account_to_view: "Success. Chose an account to view"
send_json_file: "Send the json file to import"
send_master_pass_to_delete_everything: "Send master password to delete EVERY ACCOUNT. THIS ACTION CANNOT BE UNDONE"
everything_was_deleted: "Every account was deleted"
send_password: "Send password"
send_master_pass_to_delete_account: "Send the master password to delete the account"
no_special_characters: "No special characters in the master password"
invalid_name: "Invalid name"
word_name: "Name"
word_login: "Login"
word_password: "Password"
decrypt_button: "Decrypt"
delete_message_button: "Delete message"
menu_button: "Back to the menu"
choose_language: "Choose the language"

83
locales/ru.yaml Normal file
View File

@ -0,0 +1,83 @@
master_password_is_not_set: "Мастер-пароль не установлен. Используйте /cancel, чтобы отменить текущее действие, и /set_master_pass, чтобы установить его"
hide_button: "Скрыть"
change_name_button: "Изменить имя"
change_login_button: "Изменить логин"
change_password_button: "Изменить пароль"
delete_account_button: "Удалить аккаунт"
couldnt_get_user_info_send_again: "Не удалось получить информацию о пользователе. Отправьте сообщение еще раз"
unknown_command_use_help: "Неизвестная команда. Используйте /help, чтобы получить информацию о существующих командах"
help_command: |
Поддерживаются следующие команды:
/start — отображает приветственное сообщение
/help — отображает этот текст
/set_master_pass — устанавливает мастер-пароль
/menu — предоставляет меню для управления вашими аккаунтами
/add_account — добавляет аккаунт
/get_account — получает аккаунт
/get_accounts — получает список аккаунтов
/delete — удаляет аккаунт
/delete_all — удаляет все аккаунты и мастер-пароль
/export — экспортирует все аккаунты в файл json
/import — загружает аккаунты из файла json
/gen_password — генерирует 10 сложных паролей
/cancel — отменяет текущее действие
/change_language - позволяет изменить язык
no_file_send: "Ожидалось, что вы отправите файл"
file_too_large: "Файл больше 200 MiB. Пожалуйста, разделите его на несколько файлов"
couldnt_get_file_name: "Не удалось получить имя файла. Пожалуйста, попробуйте еще раз"
following_accounts_have_problems: "У следующих аккаунтов есть проблемы:"
duplicate_names: "Дублирующиеся имена"
accounts_already_in_db: "Аккаунты с такими именами уже существуют в базе данных"
invalid_fields: "Аккаунты имеют недопустимые имена"
fix_that_and_send_again: "Исправьте эти ошибки и отправьте файл снова"
error_downloading_file: "Ошибка загрузки файла"
error_getting_account_names: "Ошибка получения существующих имен аккаунтов"
error_parsing_json_file: "Ошибка разбора файла json"
successfully_canceled: "Успешная отменена"
invalid_password: "Неверный пароль. Измените его и отправьте снова"
couldnt_get_message_text: "Не удалось получить текст сообщения. Отправьте сообщение еще раз"
invalid_file_name: "Недопустимое имя файла. Программа ожидала, что вы отправите файл с расширением .json"
account_already_exists: "Аккаунт с таким именем уже существует"
master_password_too_weak: "Мастер-пароль слишком слабый"
no_lowercase: "Отсутствуют строчные буквы"
no_uppercase: "Отсутствуют заглавные буквы"
no_numbers: "Отсутствуют цифры"
master_pass_too_short: "Меньше 8 символов"
change_master_password_and_send_again: "Измените мастер-пароль и отправьте его снова"
wrong_master_password: "Неверный мастер-пароль. Попробуйте снова"
invalid_login: "Недопустимый логин. Измените его и отправьте снова"
start_command: "Привет! Этот бот может использоваться для безопасного хранения паролей. Используйте команду /help, чтобы получить список команд"
master_password_dont_match: "Мастер-пароли не совпадают. Используйте команду снова"
success: "Успешно"
send_master_password_again: "Отправьте мастер-пароль снова"
master_password_is_set: "Мастер-пароль уже установлен"
send_new_master_password: "Отправьте новый мастер-пароль"
no_accounts_found: "Аккаунты не найдены"
choose_account: "Выберите аккаунт"
couldnt_create_following_accounts: "Не удалось создать следующие аккаунты"
send_master_password: "Отправьте мастер-пароль"
something_went_wrong: "Что-то пошло не так. Попробуйте еще раз позже"
nothing_to_cancel: "Нечего отменять"
send_account_name: "Отправьте имя аккаунта"
send_login: "Отправьте логин"
error_deleting_message: "Ошибка удаления сообщения"
account_not_found: "Аккаунт не найден"
send_new_name: "Отправьте новое имя"
send_new_login: "Отправьте новый логин"
send_new_password: "Отправьте новый пароль"
success_choose_account_to_view: "Успешно. Выберите аккаунт для просмотра"
send_json_file: "Отправьте файл json для импорта"
send_master_pass_to_delete_everything: "Отправьте мастер-пароль, чтобы удалить ВСЕ АККАУНТЫ. ЭТО ДЕЙСТВИЕ НЕЛЬЗЯ ОТМЕНИТЬ"
everything_was_deleted: "Все аккаунты были удалены"
send_password: "Отправьте пароль"
send_master_pass_to_delete_account: "Отправьте мастер-пароль, чтобы удалить аккаунт"
no_special_characters: "Нет специальных символов в мастер-пароле"
invalid_name: "Недопустимое имя"
word_name: "Имя"
word_login: "Логин"
word_password: "Пароль"
decrypt_button: "Дешифровать"
delete_message_button: "Удалить сообщение"
menu_button: "Назад в меню"
choose_language: "Выберете язык"

View File

@ -1,22 +1,31 @@
//! This module consists of endpoints to handle callbacks //! This module consists of endpoints to handle callbacks
crate::export_handlers!(decrypt, delete, delete_message, get, get_menu, alter); crate::export_handlers!(
decrypt,
delete,
delete_message,
get,
get_menu,
alter,
change_locale
);
use crate::errors::InvalidCommand; use crate::{errors::InvalidCommand, locales::LocaleTypeExt};
use base64::{engine::general_purpose::STANDARD_NO_PAD as B64_ENGINE, Engine as _}; use base64::{engine::general_purpose::STANDARD_NO_PAD as B64_ENGINE, Engine as _};
use entity::locale::LocaleType;
use std::str::FromStr; use std::str::FromStr;
use teloxide::types::CallbackQuery; use teloxide::types::CallbackQuery;
type NameHash = Vec<u8>; type NameHash = Vec<u8>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub enum AlterableField { pub enum AlterableField {
Name, Name,
Login, Login,
Pass, Pass,
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub enum CallbackCommand { pub enum CallbackCommand {
DeleteMessage, DeleteMessage,
Get(NameHash), Get(NameHash),
@ -25,12 +34,13 @@ pub enum CallbackCommand {
Hide(NameHash), Hide(NameHash),
Alter(NameHash, AlterableField), Alter(NameHash, AlterableField),
DeleteAccount { name: NameHash, is_command: bool }, DeleteAccount { name: NameHash, is_command: bool },
ChangeLocale(LocaleType),
} }
impl CallbackCommand { impl CallbackCommand {
pub fn from_query(q: CallbackQuery) -> Option<Self> { pub fn from_query(q: CallbackQuery) -> Option<Self> {
q.message.as_ref()?; q.message?;
q.data.and_then(|data| data.parse().ok()) q.data?.parse().inspect_err(|err| log::error!("{err}")).ok()
} }
} }
@ -45,13 +55,19 @@ impl FromStr for CallbackCommand {
}; };
let mut substrings = s.split(' '); let mut substrings = s.split(' ');
let (Some(command), Some(name), None) = let (Some(command), Some(param), None) =
(substrings.next(), substrings.next(), substrings.next()) (substrings.next(), substrings.next(), substrings.next())
else { else {
return Err(InvalidCommand::InvalidParams); return Err(InvalidCommand::InvalidParams);
}; };
let name_hash = B64_ENGINE.decode(name)?; if command == "change_locale" {
let locale =
LocaleType::from_language_code(param).ok_or(InvalidCommand::UnknownLocale)?;
return Ok(Self::ChangeLocale(locale));
}
let name_hash = B64_ENGINE.decode(param)?;
if name_hash.len() != 32 { if name_hash.len() != 32 {
return Err(InvalidCommand::InvalidOutputLength); return Err(InvalidCommand::InvalidOutputLength);
} }

View File

@ -45,6 +45,7 @@ async fn get_master_pass(
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
name: String, name: String,
field: AlterableField, field: AlterableField,
field_value: String, field_value: String,
@ -57,7 +58,7 @@ async fn get_master_pass(
ids.alter_message( ids.alter_message(
&bot, &bot,
"Success. Choose the account to view", locale.success_choose_account_to_view.as_ref(),
menu_markup("get", user_id, &db).await?, menu_markup("get", user_id, &db).await?,
None, None,
) )
@ -66,7 +67,7 @@ async fn get_master_pass(
Ok(()) Ok(())
} }
handler!(get_field(name:String, field:AlterableField, field_value:String), "Send the master password", State::GetMasterPass, get_master_pass); handler!(get_field(name:String, field:AlterableField, field_value:String),send_master_password, State::GetMasterPass, get_master_pass);
#[inline] #[inline]
pub async fn alter( pub async fn alter(
@ -74,6 +75,7 @@ pub async fn alter(
q: CallbackQuery, q: CallbackQuery,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
(hash, field): (super::NameHash, AlterableField), (hash, field): (super::NameHash, AlterableField),
) -> crate::Result<()> { ) -> crate::Result<()> {
let user_id = q.from.id.0; let user_id = q.from.id.0;
@ -81,7 +83,7 @@ pub async fn alter(
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else { let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
bot.send_message(ids.0, "Account wasn't found") bot.send_message(ids.0, "Account wasn't found")
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;
return Ok(()); return Ok(());
@ -90,15 +92,15 @@ pub async fn alter(
let text = match field { let text = match field {
Name => { Name => {
change_state!(dialogue, ids, (name, field), State::GetNewName, get_field); change_state!(dialogue, ids, (name, field), State::GetNewName, get_field);
"Send new account name" locale.send_new_name.as_ref()
} }
Login => { Login => {
change_state!(dialogue, ids, (name, field), State::GetLogin, get_field); change_state!(dialogue, ids, (name, field), State::GetLogin, get_field);
"Send new account login" locale.send_new_login.as_ref()
} }
Pass => { Pass => {
change_state!(dialogue, ids, (name, field), State::GetPassword, get_field); change_state!(dialogue, ids, (name, field), State::GetPassword, get_field);
"Send new account password" locale.send_new_password.as_ref()
} }
}; };

View File

@ -0,0 +1,42 @@
use crate::{locales::LocaleTypeExt, prelude::*};
use entity::locale::LocaleType;
#[inline]
pub async fn change_locale(
bot: Throttle<Bot>,
q: CallbackQuery,
db: Pool,
mut locale: LocaleRef,
new_locale: LocaleType,
) -> crate::Result<()> {
let mut ids: MessageIds = q.message.as_ref().unwrap().into();
let user_id = q.from.id.0;
let is_successful = new_locale
.update(user_id, &db)
.await
.inspect_err(|err| log::error!("{err}"))
.unwrap_or(false);
if !is_successful {
ids.alter_message(
&bot,
locale.something_went_wrong.as_ref(),
deletion_markup(locale),
None,
)
.await?;
return Ok(());
}
locale = new_locale.get_locale();
ids.alter_message(
&bot,
locale.choose_language.as_ref(),
language_markup(),
None,
)
.await?;
Ok(())
}

View File

@ -3,12 +3,14 @@ use teloxide::types::ParseMode;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
#[inline] #[inline]
#[allow(clippy::too_many_arguments)]
async fn get_master_pass( async fn get_master_pass(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
name: String, name: String,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
@ -17,8 +19,8 @@ async fn get_master_pass(
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let Some(account) = Account::get(user_id, &name, &db).await? else { let Some(account) = Account::get(user_id, &name, &db).await? else {
bot.send_message(msg.chat.id, "Account not found") bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
return Ok(()); return Ok(());
}; };
@ -26,15 +28,12 @@ async fn get_master_pass(
let account = let account =
spawn_blocking(move || DecryptedAccount::from_account(account, &master_pass)).await??; spawn_blocking(move || DecryptedAccount::from_account(account, &master_pass)).await??;
let text = format!( let text = locale.show_account(&account.name, &account.login, &account.password);
"Name:\n`{name}`\nLogin:\n`{}`\nPassword:\n`{}`",
account.login, account.password
);
ids.alter_message( ids.alter_message(
&bot, &bot,
text, text,
account_markup(&name, false), account_markup(&name, false, locale),
ParseMode::MarkdownV2, ParseMode::MarkdownV2,
) )
.await?; .await?;
@ -47,20 +46,21 @@ pub async fn decrypt(
q: CallbackQuery, q: CallbackQuery,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
hash: super::NameHash, hash: super::NameHash,
) -> crate::Result<()> { ) -> crate::Result<()> {
let mut ids: MessageIds = q.message.as_ref().unwrap().into(); let mut ids: MessageIds = q.message.as_ref().unwrap().into();
let user_id = q.from.id.0; let user_id = q.from.id.0;
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else { let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
bot.send_message(ids.0, "Account wasn't found") bot.send_message(ids.0, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;
return Ok(()); return Ok(());
}; };
ids.alter_message(&bot, "Send master password", None, None) ids.alter_message(&bot, locale.send_master_password.as_ref(), None, None)
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;

View File

@ -1,12 +1,14 @@
use crate::{change_state, prelude::*}; use crate::{change_state, prelude::*};
#[inline] #[inline]
#[allow(clippy::too_many_arguments)]
async fn get_master_pass( async fn get_master_pass(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
name: String, name: String,
_: String, _: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
@ -15,12 +17,7 @@ async fn get_master_pass(
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
Account::delete(user_id, &name, &db).await?; Account::delete(user_id, &name, &db).await?;
ids.alter_message( ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
&bot,
"The account is successfully deleted",
deletion_markup(),
None,
)
.await?; .await?;
Ok(()) Ok(())
@ -32,26 +29,26 @@ pub async fn delete(
q: CallbackQuery, q: CallbackQuery,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
(hash, is_command): (super::NameHash, bool), (hash, is_command): (super::NameHash, bool),
) -> crate::Result<()> { ) -> crate::Result<()> {
const TEXT: &str = "Send master password. \ let text = locale.send_master_pass_to_delete_account.as_ref();
Once you send the master password the account is unrecoverable";
let mut ids: MessageIds = q.message.as_ref().unwrap().into(); let mut ids: MessageIds = q.message.as_ref().unwrap().into();
let user_id = q.from.id.0; let user_id = q.from.id.0;
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else { let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
bot.send_message(ids.0, "Account wasn't found") bot.send_message(ids.0, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;
return Ok(()); return Ok(());
}; };
if is_command { if is_command {
ids.alter_message(&bot, TEXT, None, None).await?; ids.alter_message(&bot, text, None, None).await?;
} else { } else {
let msg = bot.send_message(ids.0, TEXT).await?; let msg = bot.send_message(ids.0, text).await?;
ids = MessageIds::from(&msg); ids = MessageIds::from(&msg);
}; };

View File

@ -2,11 +2,15 @@ use crate::prelude::*;
/// Deletes the message from the callback /// Deletes the message from the callback
#[inline] #[inline]
pub async fn delete_message(bot: Throttle<Bot>, q: CallbackQuery) -> crate::Result<()> { pub async fn delete_message(
bot: Throttle<Bot>,
q: CallbackQuery,
locale: LocaleRef,
) -> crate::Result<()> {
if let Some(msg) = q.message { if let Some(msg) = q.message {
if bot.delete_message(msg.chat.id, msg.id).await.is_err() { if bot.delete_message(msg.chat.id, msg.id).await.is_err() {
bot.send_message(msg.chat.id, "Error deleting the message") bot.send_message(msg.chat.id, locale.error_deleting_message.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
} }
} }

View File

@ -6,25 +6,26 @@ pub async fn get(
bot: Throttle<Bot>, bot: Throttle<Bot>,
q: CallbackQuery, q: CallbackQuery,
db: Pool, db: Pool,
locale: LocaleRef,
hash: super::NameHash, hash: super::NameHash,
) -> crate::Result<()> { ) -> crate::Result<()> {
let user_id = q.from.id.0; let user_id = q.from.id.0;
let mut ids: MessageIds = q.message.as_ref().unwrap().into(); let mut ids: MessageIds = q.message.as_ref().unwrap().into();
let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else { let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
bot.send_message(ids.0, "Account wasn't found") bot.send_message(ids.0, locale.account_not_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;
return Ok(()); return Ok(());
}; };
let text = format!("Name:\n`{name}`\nLogin:\n\\*\\*\\*\nPassword:\n\\*\\*\\*"); let text = locale.show_hidden_account(&name);
ids.alter_message( ids.alter_message(
&bot, &bot,
text, text,
account_markup(&name, true), account_markup(&name, true, locale),
ParseMode::MarkdownV2, ParseMode::MarkdownV2,
) )
.await?; .await?;

View File

@ -1,18 +1,28 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
pub async fn get_menu(bot: Throttle<Bot>, q: CallbackQuery, db: Pool) -> crate::Result<()> { pub async fn get_menu(
bot: Throttle<Bot>,
q: CallbackQuery,
db: Pool,
locale: LocaleRef,
) -> crate::Result<()> {
let user_id = q.from.id.0; let user_id = q.from.id.0;
let mut ids: MessageIds = q.message.as_ref().unwrap().into(); let mut ids: MessageIds = q.message.as_ref().unwrap().into();
let markup = menu_markup("get", user_id, &db).await?; let markup = menu_markup("get", user_id, &db).await?;
if markup.inline_keyboard.is_empty() { if markup.inline_keyboard.is_empty() {
ids.alter_message(&bot, "You don't have any accounts", deletion_markup(), None) ids.alter_message(
&bot,
locale.no_accounts_found.as_ref(),
deletion_markup(locale),
None,
)
.await?; .await?;
return Ok(()); return Ok(());
} }
ids.alter_message(&bot, "Choose your account", markup, None) ids.alter_message(&bot, locale.choose_account.as_ref(), markup, None)
.await?; .await?;
bot.answer_callback_query(q.id).await?; bot.answer_callback_query(q.id).await?;
Ok(()) Ok(())

View File

@ -13,41 +13,27 @@ crate::export_handlers!(
import, import,
menu, menu,
set_master_pass, set_master_pass,
start start,
change_language
); );
use teloxide::macros::BotCommands; use teloxide::macros::BotCommands;
#[derive(BotCommands, Clone, Copy)] #[derive(BotCommands, Clone, Copy)]
#[command( #[command(rename_rule = "snake_case")]
rename_rule = "snake_case",
description = "These commands are supported:"
)]
pub enum Command { pub enum Command {
#[command(description = "displays the welcome message")]
Start, Start,
#[command(description = "displays this text")]
Help, Help,
#[command(description = "sets the master password")]
SetMasterPass, SetMasterPass,
#[command(description = "gives you a menu to manage your accounts")]
Menu, Menu,
#[command(description = "adds the account")]
AddAccount, AddAccount,
#[command(description = "gets the account")]
GetAccount, GetAccount,
#[command(description = "gets a list of accounts")]
GetAccounts, GetAccounts,
#[command(description = "deletes the account")]
Delete, Delete,
#[command(description = "deletes all the accounts and the master password")]
DeleteAll, DeleteAll,
#[command(description = "exports all the accounts in a json file")]
Export, Export,
#[command(description = "loads the accounts from a json file")]
Import, Import,
#[command(description = "generates 10 secure passwords")]
GenPassword, GenPassword,
#[command(description = "cancels the current action")]
Cancel, Cancel,
ChangeLanguage,
} }

View File

@ -10,6 +10,7 @@ async fn get_master_pass(
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
name: String, name: String,
login: String, login: String,
password: String, password: String,
@ -30,26 +31,26 @@ async fn get_master_pass(
.await?; .await?;
account.insert(&db).await?; account.insert(&db).await?;
ids.alter_message(&bot, "Success", deletion_markup(), None) ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
.await?; .await?;
Ok(()) Ok(())
} }
handler!( handler!(
get_password(name:String, login: String, password: String), get_password(name:String, login: String, password: String),
"Send master password", send_master_password,
State::GetMasterPass, State::GetMasterPass,
get_master_pass get_master_pass
); );
handler!(get_login(name: String, login: String), handler!(get_login(name: String, login: String),
"Send password", send_password,
State::GetPassword, State::GetPassword,
get_password get_password
); );
handler!(get_account_name(name: String), "Send login", State::GetLogin, get_login); handler!(get_account_name(name: String), send_login, State::GetLogin, get_login);
first_handler!( first_handler!(
add_account, add_account,
"Send account name", send_account_name,
State::GetNewName, State::GetNewName,
get_account_name get_account_name
); );

View File

@ -2,9 +2,9 @@ use crate::prelude::*;
/// Handles /cancel command when there's no active state /// Handles /cancel command when there's no active state
#[inline] #[inline]
pub async fn cancel(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> { pub async fn cancel(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
bot.send_message(msg.chat.id, "Nothing to cancel") bot.send_message(msg.chat.id, locale.nothing_to_cancel.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -0,0 +1,13 @@
use crate::prelude::*;
#[inline]
pub async fn change_language(
bot: Throttle<Bot>,
msg: Message,
locale: LocaleRef,
) -> crate::Result<()> {
bot.send_message(msg.chat.id, locale.choose_language.as_ref())
.reply_markup(language_markup())
.await?;
Ok(())
}

View File

@ -1,19 +1,24 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
pub async fn delete(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> { pub async fn delete(
bot: Throttle<Bot>,
msg: Message,
db: Pool,
locale: LocaleRef,
) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let markup = menu_markup("delete1", user_id, &db).await?; let markup = menu_markup("delete1", user_id, &db).await?;
if markup.inline_keyboard.is_empty() { if markup.inline_keyboard.is_empty() {
bot.send_message(msg.chat.id, "You don't have any accounts") bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
return Ok(()); return Ok(());
} }
bot.send_message(msg.chat.id, "Choose the account to delete") bot.send_message(msg.chat.id, locale.choose_account.as_ref())
.reply_markup(markup) .reply_markup(markup)
.await?; .await?;
Ok(()) Ok(())

View File

@ -10,6 +10,7 @@ async fn get_master_pass(
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
_: String, _: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
dialogue.exit().await?; dialogue.exit().await?;
@ -23,22 +24,22 @@ async fn get_master_pass(
let text = match result { let text = match result {
(Ok(()), Ok(())) => { (Ok(()), Ok(())) => {
txn.commit().await?; txn.commit().await?;
"Everything was deleted" locale.everything_was_deleted.as_ref()
} }
(Err(err), _) | (_, Err(err)) => { (Err(err), _) | (_, Err(err)) => {
error!("{}", crate::Error::from(err)); error!("{}", crate::Error::from(err));
txn.rollback().await?; txn.rollback().await?;
"Something went wrong. Try again later" locale.something_went_wrong.as_ref()
} }
}; };
ids.alter_message(&bot, text, deletion_markup(), None) ids.alter_message(&bot, text, deletion_markup(locale), None)
.await?; .await?;
Ok(()) Ok(())
} }
first_handler!( first_handler!(
delete_all, delete_all,
"Send master password to delete EVERYTHING.\nTHIS ACTION IS IRREVERSIBLE", send_master_pass_to_delete_everything,
State::GetMasterPass, State::GetMasterPass,
get_master_pass get_master_pass
); );

View File

@ -25,6 +25,7 @@ async fn get_master_pass(
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
ids: MessageIds, ids: MessageIds,
locale: LocaleRef,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
dialogue.exit().await?; dialogue.exit().await?;
@ -51,14 +52,14 @@ async fn get_master_pass(
let file = InputFile::memory(json).file_name("accounts.json"); let file = InputFile::memory(json).file_name("accounts.json");
bot.send_document(msg.chat.id, file) bot.send_document(msg.chat.id, file)
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }
first_handler!( first_handler!(
export, export,
"Send the master password to export your accounts", send_master_password,
State::GetMasterPass, State::GetMasterPass,
get_master_pass get_master_pass
); );

View File

@ -15,7 +15,11 @@ const BUFFER_LENGTH: usize =
/// Handles /`gen_password` command by generating 10 copyable passwords and sending them to the user /// Handles /`gen_password` command by generating 10 copyable passwords and sending them to the user
#[inline] #[inline]
pub async fn gen_password(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> { pub async fn gen_password(
bot: Throttle<Bot>,
msg: Message,
locale: LocaleRef,
) -> crate::Result<()> {
let mut message: ArrayString<BUFFER_LENGTH> = MESSAGE_HEADER.try_into().unwrap(); let mut message: ArrayString<BUFFER_LENGTH> = MESSAGE_HEADER.try_into().unwrap();
let passwords: PasswordArray = spawn_blocking(generate_passwords).await?; let passwords: PasswordArray = spawn_blocking(generate_passwords).await?;
for password in passwords { for password in passwords {
@ -23,7 +27,7 @@ pub async fn gen_password(bot: Throttle<Bot>, msg: Message) -> crate::Result<()>
} }
bot.send_message(msg.chat.id, message.as_str()) bot.send_message(msg.chat.id, message.as_str())
.parse_mode(ParseMode::MarkdownV2) .parse_mode(ParseMode::MarkdownV2)
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -1,19 +1,24 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
pub async fn get_account(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> { pub async fn get_account(
bot: Throttle<Bot>,
msg: Message,
db: Pool,
locale: LocaleRef,
) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let markup = menu_markup("decrypt", user_id, &db).await?; let markup = menu_markup("decrypt", user_id, &db).await?;
if markup.inline_keyboard.is_empty() { if markup.inline_keyboard.is_empty() {
bot.send_message(msg.chat.id, "You don't have any accounts") bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
return Ok(()); return Ok(());
} }
bot.send_message(msg.chat.id, "Choose the account to get") bot.send_message(msg.chat.id, locale.choose_account.as_ref())
.reply_markup(markup) .reply_markup(markup)
.await?; .await?;
Ok(()) Ok(())

View File

@ -4,26 +4,32 @@ use teloxide::types::ParseMode;
/// Handles /`get_accounts` command by sending the list of copyable account names to the user /// Handles /`get_accounts` command by sending the list of copyable account names to the user
#[inline] #[inline]
pub async fn get_accounts(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> { pub async fn get_accounts(
bot: Throttle<Bot>,
msg: Message,
db: Pool,
locale: LocaleRef,
) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let mut account_names = Account::get_names(user_id, &db); let mut account_names = Account::get_names(user_id, &db);
let mut text = if let Some(name) = account_names.try_next().await? { let Some(mut text) = account_names.try_next().await? else {
format!("Accounts:\n`{name}`") bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
} else { .reply_markup(deletion_markup(locale))
bot.send_message(msg.chat.id, "No accounts found")
.reply_markup(deletion_markup())
.await?; .await?;
return Ok(()); return Ok(());
}; };
text.insert(0, '`');
text.push('`');
while let Some(name) = account_names.try_next().await? { while let Some(name) = account_names.try_next().await? {
write!(text, "\n`{name}`")?; write!(text, "\n`{name}`")?;
} }
bot.send_message(msg.chat.id, text) bot.send_message(msg.chat.id, text)
.parse_mode(ParseMode::MarkdownV2) .parse_mode(ParseMode::MarkdownV2)
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -1,11 +1,10 @@
use crate::prelude::*; use crate::prelude::*;
use teloxide::utils::command::BotCommands;
/// Handles the help command by sending the passwords descryptions /// Handles the help command by sending the passwords descryptions
#[inline] #[inline]
pub async fn help(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> { pub async fn help(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
bot.send_message(msg.chat.id, Command::descriptions().to_string()) bot.send_message(msg.chat.id, locale.help_command.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -27,12 +27,14 @@ async fn encrypt_account(
/// Gets the master password, encryptes and adds the accounts to the DB /// Gets the master password, encryptes and adds the accounts to the DB
#[inline] #[inline]
#[allow(clippy::too_many_arguments)]
async fn get_master_pass( async fn get_master_pass(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
user: User, user: User,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
@ -53,22 +55,18 @@ async fn get_master_pass(
} }
let text = if failed.is_empty() { let text = if failed.is_empty() {
"Success".to_owned() locale.success.as_ref().to_owned()
} else { } else {
format!( format!(
"Failed to create the following accounts:\n{}", "{}:\n{}",
locale.couldnt_create_following_accounts,
failed.into_iter().format("\n") failed.into_iter().format("\n")
) )
}; };
ids.alter_message(&bot, text, deletion_markup(), None) ids.alter_message(&bot, text, deletion_markup(locale), None)
.await?; .await?;
Ok(()) Ok(())
} }
handler!(get_user(user: User), "Send master password", State::GetMasterPass, get_master_pass); handler!(get_user(user: User),send_master_password, State::GetMasterPass, get_master_pass);
first_handler!( first_handler!(import, send_json_file, State::GetUser, get_user);
import,
"Send a json document with the same format as created by /export",
State::GetUser,
get_user
);

View File

@ -1,19 +1,24 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
pub async fn menu(bot: Throttle<Bot>, msg: Message, db: Pool) -> crate::Result<()> { pub async fn menu(
bot: Throttle<Bot>,
msg: Message,
db: Pool,
locale: LocaleRef,
) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let markup = menu_markup("get", user_id, &db).await?; let markup = menu_markup("get", user_id, &db).await?;
if markup.inline_keyboard.is_empty() { if markup.inline_keyboard.is_empty() {
bot.send_message(msg.chat.id, "You don't have any accounts") bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
return Ok(()); return Ok(());
} }
bot.send_message(msg.chat.id, "Choose your account") bot.send_message(msg.chat.id, locale.choose_account.as_ref())
.reply_markup(markup) .reply_markup(markup)
.await?; .await?;
Ok(()) Ok(())

View File

@ -1,25 +1,29 @@
use crate::{change_state, prelude::*}; use crate::{change_state, locales::LocaleTypeExt, prelude::*};
use cryptography::hashing::HashedBytes; use cryptography::hashing::HashedBytes;
use entity::locale::LocaleType;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
#[inline] #[inline]
#[allow(clippy::too_many_arguments)]
async fn get_master_pass2( async fn get_master_pass2(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
hash: HashedBytes<[u8; 64], [u8; 64]>, hash: HashedBytes<[u8; 64], [u8; 64]>,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
dialogue.exit().await?; dialogue.exit().await?;
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let from = msg.from().ok_or(NoUserInfo)?;
let user_id = from.id.0;
if !hash.verify(master_pass.as_bytes()) { if !hash.verify(master_pass.as_bytes()) {
ids.alter_message( ids.alter_message(
&bot, &bot,
"The passwords didn't match. Use the command again", locale.master_password_dont_match.as_ref(),
deletion_markup(), deletion_markup(locale),
None, None,
) )
.await?; .await?;
@ -32,9 +36,14 @@ async fn get_master_pass2(
password_hash: hash.hash.to_vec(), password_hash: hash.hash.to_vec(),
salt: hash.salt.to_vec(), salt: hash.salt.to_vec(),
}; };
model.insert(&db).await?; let locale_type = from
.language_code
.as_deref()
.and_then(LocaleType::from_language_code)
.unwrap_or_default();
model.insert(&db, locale_type).await?;
ids.alter_message(&bot, "Success", deletion_markup(), None) ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
.await?; .await?;
Ok(()) Ok(())
@ -47,11 +56,13 @@ async fn get_master_pass(
_: Pool, _: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
let hash = spawn_blocking(move || HashedBytes::new(master_pass.as_bytes())).await?; let hash = spawn_blocking(move || HashedBytes::new(master_pass.as_bytes())).await?;
ids.alter_message(&bot, "Send it again", None, None).await?; ids.alter_message(&bot, locale.send_master_password_again.as_ref(), None, None)
.await?;
change_state!( change_state!(
dialogue, dialogue,
@ -71,16 +82,17 @@ pub async fn set_master_pass(
msg: Message, msg: Message,
dialogue: MainDialogue, dialogue: MainDialogue,
db: Pool, db: Pool,
locale: LocaleRef,
) -> crate::Result<()> { ) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
if MasterPass::exists(user_id, &db).await? { if MasterPass::exists(user_id, &db).await? {
bot.send_message(msg.chat.id, "Master password already exists") bot.send_message(msg.chat.id, locale.master_password_is_set.as_ref())
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
return Ok(()); return Ok(());
} }
let previous = bot let previous = bot
.send_message(msg.chat.id, "Send new master password") .send_message(msg.chat.id, locale.send_new_master_password.as_ref())
.await?; .await?;
change_state!( change_state!(

View File

@ -2,12 +2,8 @@ use crate::prelude::*;
/// Handles /start command by sending the greeting message /// Handles /start command by sending the greeting message
#[inline] #[inline]
pub async fn start(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> { pub async fn start(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
bot.send_message( bot.send_message(msg.chat.id, locale.start_command.as_ref())
msg.chat.id,
"Hi! This bot can be used to store the passwords securely. \
Use /help command to get the list of commands",
)
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -2,12 +2,9 @@ use crate::prelude::*;
/// Handles the messages which weren't matched by any commands or states /// Handles the messages which weren't matched by any commands or states
#[inline] #[inline]
pub async fn default(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> { pub async fn default(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
bot.send_message( bot.send_message(msg.chat.id, locale.unknown_command_use_help.as_ref())
msg.chat.id, .reply_markup(deletion_markup(locale))
"Unknown command. Use /help command to get the list of commands",
)
.reply_markup(deletion_markup())
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -14,4 +14,6 @@ pub enum InvalidCommand {
InvalidOutputLength, InvalidOutputLength,
#[error("Error decoding the values: {0}")] #[error("Error decoding the values: {0}")]
NameDecodingError(#[from] base64::DecodeError), NameDecodingError(#[from] base64::DecodeError),
#[error("Unknown locale passed into callback")]
UnknownLocale,
} }

View File

@ -13,13 +13,14 @@ async fn notify_about_no_user_info(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
state: State, state: State,
locale: LocaleRef,
) -> crate::Result<()> { ) -> crate::Result<()> {
const TEXT: &str = "Invalid message. Couldn't get the user information. Send the message again"; let text = locale.couldnt_get_user_info_send_again.as_ref();
match state { match state {
State::Start => { State::Start => {
bot.send_message(msg.chat.id, TEXT) bot.send_message(msg.chat.id, text)
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
} }
State::GetNewName(handler) State::GetNewName(handler)
@ -30,14 +31,14 @@ async fn notify_about_no_user_info(
let mut handler = handler.lock().await; let mut handler = handler.lock().await;
handler handler
.previous .previous
.alter_message(&bot, TEXT, None, None) .alter_message(&bot, text, None, None)
.await?; .await?;
} }
State::GetUser(handler) => { State::GetUser(handler) => {
let mut handler = handler.lock().await; let mut handler = handler.lock().await;
handler handler
.previous .previous
.alter_message(&bot, TEXT, None, None) .alter_message(&bot, text, None, None)
.await?; .await?;
} }
}; };

166
src/locales.rs Normal file
View File

@ -0,0 +1,166 @@
use crate::prelude::*;
use anyhow::Result;
use entity::locale::LocaleType;
use log::error;
use std::future::Future;
use std::sync::OnceLock;
static LOCALES: OnceLock<LocaleStore> = OnceLock::new();
pub struct LocaleStore {
eng: Locale,
ru: Locale,
}
impl LocaleStore {
pub fn init() -> Result<()> {
let ru = serde_yaml::from_slice(include_bytes!("../locales/ru.yaml"))?;
let eng = serde_yaml::from_slice(include_bytes!("../locales/eng.yaml"))?;
assert!(
LOCALES.set(Self { eng, ru }).is_ok(),
"Locales are already intialized"
);
Ok(())
}
}
#[derive(serde::Deserialize)]
pub struct Locale {
pub master_password_is_not_set: Box<str>,
pub hide_button: Box<str>,
pub change_name_button: Box<str>,
pub change_login_button: Box<str>,
pub change_password_button: Box<str>,
pub delete_account_button: Box<str>,
pub couldnt_get_user_info_send_again: Box<str>,
pub unknown_command_use_help: Box<str>,
pub help_command: Box<str>,
pub no_file_send: Box<str>,
pub file_too_large: Box<str>,
pub couldnt_get_file_name: Box<str>,
pub following_accounts_have_problems: Box<str>,
pub duplicate_names: Box<str>,
pub accounts_already_in_db: Box<str>,
pub invalid_fields: Box<str>,
pub fix_that_and_send_again: Box<str>,
pub error_downloading_file: Box<str>,
pub error_getting_account_names: Box<str>,
pub error_parsing_json_file: Box<str>,
pub successfully_canceled: Box<str>,
pub invalid_password: Box<str>,
pub couldnt_get_message_text: Box<str>,
pub invalid_file_name: Box<str>,
pub account_already_exists: Box<str>,
pub master_password_too_weak: Box<str>,
pub no_lowercase: Box<str>,
pub no_uppercase: Box<str>,
pub no_numbers: Box<str>,
pub master_pass_too_short: Box<str>,
pub change_master_password_and_send_again: Box<str>,
pub wrong_master_password: Box<str>,
pub invalid_login: Box<str>,
pub start_command: Box<str>,
pub master_password_dont_match: Box<str>,
pub success: Box<str>,
pub send_master_password_again: Box<str>,
pub master_password_is_set: Box<str>,
pub send_new_master_password: Box<str>,
pub no_accounts_found: Box<str>,
pub choose_account: Box<str>,
pub couldnt_create_following_accounts: Box<str>,
pub send_master_password: Box<str>,
pub something_went_wrong: Box<str>,
pub nothing_to_cancel: Box<str>,
pub send_account_name: Box<str>,
pub send_login: Box<str>,
pub error_deleting_message: Box<str>,
pub account_not_found: Box<str>,
pub send_new_name: Box<str>,
pub send_new_login: Box<str>,
pub send_new_password: Box<str>,
pub success_choose_account_to_view: Box<str>,
pub send_json_file: Box<str>,
pub send_master_pass_to_delete_everything: Box<str>,
pub everything_was_deleted: Box<str>,
pub send_password: Box<str>,
pub send_master_pass_to_delete_account: Box<str>,
pub no_special_characters: Box<str>,
pub invalid_name: Box<str>,
pub decrypt_button: Box<str>,
pub delete_message_button: Box<str>,
pub menu_button: Box<str>,
pub choose_language: Box<str>,
word_name: Box<str>,
word_login: Box<str>,
word_password: Box<str>,
}
impl Locale {
pub async fn from_update(update: Update, db: Pool) -> &'static Self {
let locale_type = LocaleType::locale_for_update(&update, &db).await;
locale_type.get_locale()
}
pub fn show_account(&self, name: &str, login: &str, password: &str) -> String {
format!(
"{}:\n`{name}`\n{}:\n`{login}`\n{}:\n`{password}`",
self.word_name, self.word_login, self.word_password
)
}
pub fn show_hidden_account(&self, name: &str) -> String {
format!(
"{}:\n`{name}`\n{}:\n\\*\\*\\*\n{}:\n\\*\\*\\*",
self.word_name, self.word_login, self.word_password
)
}
}
pub type LocaleRef = &'static Locale;
pub trait LocaleTypeExt: Sized {
fn locale_for_update(update: &Update, db: &Pool) -> impl Future<Output = Self> + Send;
fn from_language_code(code: &str) -> Option<Self>;
fn get_locale(self) -> &'static Locale;
}
impl LocaleTypeExt for LocaleType {
async fn locale_for_update(update: &Update, db: &Pool) -> Self {
let Some(from) = update.user() else {
return Self::default();
};
match Self::get_from_db(from.id.0, db).await {
Ok(Some(locale)) => return locale,
Ok(None) => (),
Err(err) => error!("{err}"),
}
from.language_code
.as_deref()
.and_then(Self::from_language_code)
.unwrap_or_default()
}
fn from_language_code(code: &str) -> Option<Self> {
match code {
"ru" => Some(Self::Ru),
"en" => Some(Self::Eng),
_ => None,
}
}
fn get_locale(self) -> &'static Locale {
let Some(store) = LOCALES.get() else {
panic!("Locales are not initialized")
};
match self {
Self::Eng => &store.eng,
Self::Ru => &store.ru,
}
}
}

View File

@ -3,7 +3,7 @@ macro_rules! change_state {
($dialogue: expr, $previous: expr, ($($param: ident),*), $next_state: expr, $next_func: ident) => {{ ($dialogue: expr, $previous: expr, ($($param: ident),*), $next_state: expr, $next_func: ident) => {{
$dialogue $dialogue
.update($next_state(Handler::new( .update($next_state(Handler::new(
move |bot, msg, db, dialogue, ids, param| Box::pin($next_func(bot, msg, db, dialogue, ids, $($param,)* param)), move |bot, msg, db, dialogue, locale, ids, param| Box::pin($next_func(bot, msg, db, dialogue, locale, ids, $($param,)* param)),
$previous, $previous,
))) )))
.await?; .await?;
@ -12,14 +12,17 @@ macro_rules! change_state {
#[macro_export] #[macro_export]
macro_rules! first_handler { macro_rules! first_handler {
($function_name: ident, $message: expr, $next_state: expr, $next_func: ident) => { ($function_name: ident, $message: ident, $next_state: expr, $next_func: ident) => {
#[inline] #[inline]
pub async fn $function_name( pub async fn $function_name(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
) -> $crate::Result<()> { ) -> $crate::Result<()> {
let previous = bot.send_message(msg.chat.id, $message).await?; let previous = bot
.send_message(msg.chat.id, locale.$message.as_ref())
.await?;
$crate::change_state!(dialogue, &previous, (), $next_state, $next_func); $crate::change_state!(dialogue, &previous, (), $next_state, $next_func);
@ -30,7 +33,7 @@ macro_rules! first_handler {
#[macro_export] #[macro_export]
macro_rules! handler { macro_rules! handler {
($function_name: ident ($($param: ident: $type: ty),*), $message: literal, $next_state: expr, $next_func: ident) => { ($function_name: ident ($($param: ident: $type: ty),*), $message: ident, $next_state: expr, $next_func: ident) => {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[inline] #[inline]
async fn $function_name( async fn $function_name(
@ -39,9 +42,10 @@ macro_rules! handler {
_: Pool, _: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
locale: LocaleRef,
$($param: $type),* $($param: $type),*
) -> $crate::Result<()> { ) -> $crate::Result<()> {
ids.alter_message(&bot, $message, None, None).await?; ids.alter_message(&bot, locale.$message.as_ref(), None, None).await?;
$crate::change_state!(dialogue, ids, ($($param),*), $next_state, $next_func); $crate::change_state!(dialogue, ids, ($($param),*), $next_state, $next_func);
@ -52,13 +56,14 @@ macro_rules! handler {
#[macro_export] #[macro_export]
macro_rules! simple_state_handler { macro_rules! simple_state_handler {
($function_name: ident, $check: ident, $no_text_message: literal) => { ($function_name: ident, $check: ident) => {
#[inline] #[inline]
pub async fn $function_name( pub async fn $function_name(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
next: PackagedHandler<String>, next: PackagedHandler<String>,
) -> $crate::Result<()> { ) -> $crate::Result<()> {
super::generic::generic( super::generic::generic(
@ -66,8 +71,8 @@ macro_rules! simple_state_handler {
msg, msg,
db, db,
dialogue, dialogue,
|msg, db, param| Box::pin($check(msg, db, param)), |msg, db, locale, param| Box::pin($check(msg, db, locale, param)),
$no_text_message, locale,
next, next,
) )
.await .await

View File

@ -4,6 +4,7 @@ mod default;
mod delete_mesage_handler; mod delete_mesage_handler;
mod errors; mod errors;
mod filter_user_info; mod filter_user_info;
mod locales;
mod macros; mod macros;
mod markups; mod markups;
mod master_password_check; mod master_password_check;
@ -42,7 +43,8 @@ fn get_dispatcher(
.branch(case![Command::Delete].endpoint(commands::delete)) .branch(case![Command::Delete].endpoint(commands::delete))
.branch(case![Command::DeleteAll].endpoint(commands::delete_all)) .branch(case![Command::DeleteAll].endpoint(commands::delete_all))
.branch(case![Command::Export].endpoint(commands::export)) .branch(case![Command::Export].endpoint(commands::export))
.branch(case![Command::Import].endpoint(commands::import)); .branch(case![Command::Import].endpoint(commands::import))
.branch(case![Command::ChangeLanguage].endpoint(commands::change_language));
let message_handler = Update::filter_message() let message_handler = Update::filter_message()
.map_async(delete_mesage_handler::delete_message) .map_async(delete_mesage_handler::delete_message)
@ -67,9 +69,11 @@ fn get_dispatcher(
.branch( .branch(
case![CallbackCommand::DeleteAccount { name, is_command }].endpoint(callbacks::delete), case![CallbackCommand::DeleteAccount { name, is_command }].endpoint(callbacks::delete),
) )
.branch(case![CallbackCommand::Alter(hash, field)].endpoint(callbacks::alter)); .branch(case![CallbackCommand::Alter(hash, field)].endpoint(callbacks::alter))
.branch(case![CallbackCommand::ChangeLocale(locale)].endpoint(callbacks::change_locale));
let handler = dptree::entry() let handler = dptree::entry()
.map_async(Locale::from_update)
.enter_dialogue::<Update, InMemStorage<State>, State>() .enter_dialogue::<Update, InMemStorage<State>, State>()
.branch(message_handler) .branch(message_handler)
.branch(callback_handler); .branch(callback_handler);
@ -85,6 +89,8 @@ async fn main() -> Result<()> {
let _ = dotenv(); let _ = dotenv();
pretty_env_logger::init(); pretty_env_logger::init();
locales::LocaleStore::init()?;
let token = env::var("TOKEN").expect("expected TOKEN in the enviroment"); let token = env::var("TOKEN").expect("expected TOKEN in the enviroment");
let database_url = env::var("DATABASE_URL").expect("expected DATABASE_URL in the enviroment"); let database_url = env::var("DATABASE_URL").expect("expected DATABASE_URL in the enviroment");
let pool = Pool::connect(&database_url).await?; let pool = Pool::connect(&database_url).await?;

View File

@ -40,16 +40,16 @@ pub async fn menu_markup(
} }
#[inline] #[inline]
fn make_button(text: &str, command: &str, hash: &str) -> InlineKeyboardButton { fn make_button(text: &str, command: &str, param: &str) -> InlineKeyboardButton {
let mut data = command.to_owned(); let mut data = command.to_owned();
data.reserve(44); data.reserve(param.len() + 1);
data.push(' '); data.push(' ');
data.push_str(hash); data.push_str(param);
InlineKeyboardButton::callback(text, data) InlineKeyboardButton::callback(text, data)
} }
#[inline] #[inline]
pub fn account_markup(name: &str, is_encrypted: bool) -> InlineKeyboardMarkup { pub fn account_markup(name: &str, is_encrypted: bool, locale: LocaleRef) -> InlineKeyboardMarkup {
let mut hash = [0; 43]; let mut hash = [0; 43];
B64_ENGINE B64_ENGINE
.encode_slice(<Sha256 as Digest>::digest(name), &mut hash) .encode_slice(<Sha256 as Digest>::digest(name), &mut hash)
@ -57,31 +57,41 @@ pub fn account_markup(name: &str, is_encrypted: bool) -> InlineKeyboardMarkup {
let hash = std::str::from_utf8(&hash).unwrap(); let hash = std::str::from_utf8(&hash).unwrap();
let encryption_button = if is_encrypted { let encryption_button = if is_encrypted {
("Decrypt", "decrypt") (locale.decrypt_button.as_ref(), "decrypt")
} else { } else {
("Hide", "get") (locale.hide_button.as_ref(), "get")
}; };
let main_buttons = [ let main_buttons = [
("Alter name", "an"), (locale.change_name_button.as_ref(), "an"),
("Alter login", "al"), (locale.change_login_button.as_ref(), "al"),
("Alter password", "ap"), (locale.change_password_button.as_ref(), "ap"),
encryption_button, encryption_button,
("Delete account", "delete0"), (locale.delete_account_button.as_ref(), "delete0"),
] ]
.into_iter() .into_iter()
.map(|(text, command)| make_button(text, command, hash)) .map(|(text, command)| make_button(text, command, hash))
.chunks(3); .chunks(3);
let menu_button = InlineKeyboardButton::callback("Back to the menu", "get_menu"); let menu_button = InlineKeyboardButton::callback(locale.menu_button.as_ref(), "get_menu");
InlineKeyboardMarkup::new(&main_buttons).append_row([menu_button]) InlineKeyboardMarkup::new(&main_buttons).append_row([menu_button])
} }
#[inline]
pub fn language_markup() -> InlineKeyboardMarkup {
let languages = [("🇷🇺 Русский", "ru"), ("🇬🇧 English", "en")]
.into_iter()
.map(|(text, param)| make_button(text, "change_locale", param))
.chunks(3);
InlineKeyboardMarkup::new(&languages)
}
/// Creates a markup with a "Delete message" button. /// Creates a markup with a "Delete message" button.
/// This markup should be added for all messages that won't be deleted afterwards /// This markup should be added for all messages that won't be deleted afterwards
#[inline] #[inline]
pub fn deletion_markup() -> InlineKeyboardMarkup { pub fn deletion_markup(locale: LocaleRef) -> InlineKeyboardMarkup {
let button = InlineKeyboardButton::callback("Delete message", "delete_message"); let button =
InlineKeyboardButton::callback(locale.delete_message_button.as_ref(), "delete_message");
InlineKeyboardMarkup::new([[button]]) InlineKeyboardMarkup::new([[button]])
} }

View File

@ -29,6 +29,7 @@ async fn master_pass_exists(update: Update, db: Pool) -> Option<Option<DynError>
#[inline] #[inline]
async fn notify_about_no_master_pass( async fn notify_about_no_master_pass(
bot: Throttle<Bot>, bot: Throttle<Bot>,
locale: LocaleRef,
result: Option<DynError>, result: Option<DynError>,
update: Update, update: Update,
) -> crate::Result<()> { ) -> crate::Result<()> {
@ -37,9 +38,9 @@ async fn notify_about_no_master_pass(
} }
bot.send_message( bot.send_message(
update.chat_id().unwrap(), update.chat_id().unwrap(),
"No master password set. Use /set_master_pass to set it", locale.master_password_is_not_set.as_ref(),
) )
.reply_markup(deletion_markup()) .reply_markup(deletion_markup(locale))
.await?; .await?;
Ok(()) Ok(())
} }

View File

@ -2,6 +2,7 @@ pub use crate::{
commands::Command, commands::Command,
errors::*, errors::*,
first_handler, handler, first_handler, handler,
locales::{Locale, LocaleRef},
markups::*, markups::*,
models::*, models::*,
state::{Handler, MainDialogue, MessageIds, PackagedHandler, State}, state::{Handler, MainDialogue, MessageIds, PackagedHandler, State},

View File

@ -9,11 +9,16 @@ pub async fn generic<F>(
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
check: F, check: F,
no_text_message: impl Into<String> + Send, locale: LocaleRef,
handler: PackagedHandler<String>, handler: PackagedHandler<String>,
) -> crate::Result<()> ) -> crate::Result<()>
where where
for<'a> F: FnOnce(&'a Message, &'a Pool, &'a str) -> BoxFuture<'a, crate::Result<Option<String>>> for<'a> F: FnOnce(
&'a Message,
&'a Pool,
LocaleRef,
&'a str,
) -> BoxFuture<'a, crate::Result<Option<String>>>
+ Send, + Send,
{ {
let mut handler = handler.lock().await; let mut handler = handler.lock().await;
@ -25,7 +30,7 @@ where
let Some(text) = msg.text().map(str::trim) else { let Some(text) = msg.text().map(str::trim) else {
handler handler
.previous .previous
.alter_message(&bot, no_text_message, None, None) .alter_message(&bot, locale.couldnt_get_message_text.as_ref(), None, None)
.await?; .await?;
return Ok(()); return Ok(());
}; };
@ -34,12 +39,17 @@ where
dialogue.exit().await?; dialogue.exit().await?;
handler handler
.previous .previous
.alter_message(&bot, "Successfully cancelled", deletion_markup(), None) .alter_message(
&bot,
locale.successfully_canceled.as_ref(),
deletion_markup(locale),
None,
)
.await?; .await?;
return Ok(()); return Ok(());
} }
if let Some(text) = check(&msg, &db, text).await? { if let Some(text) = check(&msg, &db, locale, text).await? {
handler handler
.previous .previous
.alter_message(&bot, text, None, None) .alter_message(&bot, text, None, None)
@ -52,7 +62,7 @@ where
drop(handler); drop(handler);
let text = text.to_owned(); let text = text.to_owned();
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, text).await { if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, locale, text).await {
let _ = dialogue.exit().await; let _ = dialogue.exit().await;
return Err(err); return Err(err);
} }

View File

@ -1,16 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
async fn check_login(_: &Message, _: &Pool, login: &str) -> crate::Result<Option<String>> { async fn check_login(
_: &Message,
_: &Pool,
locale: LocaleRef,
login: &str,
) -> crate::Result<Option<String>> {
let is_valid = validate_field(login); let is_valid = validate_field(login);
if !is_valid { if !is_valid {
return Ok(Some("Invalid login. Try again".to_owned())); return Ok(Some(locale.invalid_login.as_ref().into()));
} }
Ok(None) Ok(None)
} }
crate::simple_state_handler!( crate::simple_state_handler!(get_login, check_login);
get_login,
check_login,
"Couldn't get the text of the message. Send the login again"
);

View File

@ -8,14 +8,13 @@ use tokio::task::spawn_blocking;
async fn check_master_pass( async fn check_master_pass(
msg: &Message, msg: &Message,
db: &Pool, db: &Pool,
locale: LocaleRef,
master_pass: &str, master_pass: &str,
) -> crate::Result<Option<String>> { ) -> crate::Result<Option<String>> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
let Some(model) = MasterPass::get(user_id, db).await? else { let Some(model) = MasterPass::get(user_id, db).await? else {
error!("User was put into the GetMasterPass state with no master password set"); error!("User was put into the GetMasterPass state with no master password set");
return Ok(Some( return Ok(Some(locale.master_password_is_not_set.as_ref().into()));
"No master password set. Use /cancel and set it by using /set_master_pass".to_owned(),
));
}; };
let is_valid = { let is_valid = {
@ -25,13 +24,9 @@ async fn check_master_pass(
}; };
if !is_valid { if !is_valid {
return Ok(Some("Wrong master password. Try again".to_owned())); return Ok(Some(locale.wrong_master_password.as_ref().into()));
} }
Ok(None) Ok(None)
} }
crate::simple_state_handler!( crate::simple_state_handler!(get_master_pass, check_master_pass);
get_master_pass,
check_master_pass,
"Couldn't get the text of the message. Send the master password again"
);

View File

@ -1,31 +1,32 @@
use crate::prelude::*; use crate::prelude::*;
use cryptography::passwords::{check_master_pass, PasswordValidity}; use cryptography::passwords::{check_master_pass, PasswordValidity};
use std::fmt::Write as _;
#[inline] #[inline]
fn process_validity(validity: PasswordValidity) -> Result<(), String> { fn process_validity(validity: PasswordValidity, locale: LocaleRef) -> Result<(), String> {
if validity.is_empty() { if validity.is_empty() {
return Ok(()); return Ok(());
} }
let mut error_text = "Your master password is invalid:\n".to_owned(); let mut error_text = locale.master_password_too_weak.as_ref().to_owned();
if validity.contains(PasswordValidity::NO_LOWERCASE) { if validity.contains(PasswordValidity::NO_LOWERCASE) {
error_text.push_str("\n* It doesn't have any lowercase characters"); write!(error_text, "\n* {}", locale.no_lowercase).unwrap();
} }
if validity.contains(PasswordValidity::NO_UPPERCASE) { if validity.contains(PasswordValidity::NO_UPPERCASE) {
error_text.push_str("\n* It doesn't have any uppercase characters"); write!(error_text, "\n* {}", locale.no_uppercase).unwrap();
} }
if validity.contains(PasswordValidity::NO_NUMBER) { if validity.contains(PasswordValidity::NO_NUMBER) {
error_text.push_str("\n* It doesn't have any numbers"); write!(error_text, "\n* {}", locale.no_numbers).unwrap();
} }
if validity.contains(PasswordValidity::NO_SPECIAL_CHARACTER) { if validity.contains(PasswordValidity::NO_SPECIAL_CHARACTER) {
error_text.push_str("\n* It doesn't have any special characters"); write!(error_text, "\n* {}", locale.no_special_characters).unwrap();
} }
if validity.contains(PasswordValidity::TOO_SHORT) { if validity.contains(PasswordValidity::TOO_SHORT) {
error_text.push_str("\n* It is shorter than 8 characters"); write!(error_text, "\n* {}", locale.master_pass_too_short).unwrap();
} }
error_text.push_str("\n\nModify your password and send it again"); error_text.push_str(&locale.change_master_password_and_send_again);
Err(error_text) Err(error_text)
} }
@ -35,15 +36,12 @@ fn process_validity(validity: PasswordValidity) -> Result<(), String> {
async fn check_new_master_pass( async fn check_new_master_pass(
_: &Message, _: &Message,
_: &Pool, _: &Pool,
locale: LocaleRef,
password: &str, password: &str,
) -> crate::Result<Option<String>> { ) -> crate::Result<Option<String>> {
let validity = check_master_pass(password); let validity = check_master_pass(password);
Ok(process_validity(validity).err()) Ok(process_validity(validity, locale).err())
} }
crate::simple_state_handler!( crate::simple_state_handler!(get_new_master_pass, check_new_master_pass);
get_new_master_pass,
check_new_master_pass,
"Couldn't get the text of the message. Send the master password again"
);

View File

@ -5,21 +5,18 @@ use crate::prelude::*;
async fn check_new_account_name( async fn check_new_account_name(
msg: &Message, msg: &Message,
db: &Pool, db: &Pool,
locale: LocaleRef,
name: &str, name: &str,
) -> crate::Result<Option<String>> { ) -> crate::Result<Option<String>> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
if Account::exists(user_id, name, db).await? { if Account::exists(user_id, name, db).await? {
Ok(Some("Account already exists".to_owned())) Ok(Some(locale.account_already_exists.as_ref().into()))
} else if !validate_field(name) { } else if !validate_field(name) {
Ok(Some("Invalid account name. Try again".to_owned())) Ok(Some(locale.invalid_name.as_ref().into()))
} else { } else {
Ok(None) Ok(None)
} }
} }
crate::simple_state_handler!( crate::simple_state_handler!(get_new_name, check_new_account_name);
get_new_name,
check_new_account_name,
"Couldn't get the text of the message. Send the name of the new account again"
);

View File

@ -1,16 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
#[inline] #[inline]
async fn check_password(_: &Message, _: &Pool, password: &str) -> crate::Result<Option<String>> { async fn check_password(
_: &Message,
_: &Pool,
locale: LocaleRef,
password: &str,
) -> crate::Result<Option<String>> {
let is_valid = validate_field(password); let is_valid = validate_field(password);
if !is_valid { if !is_valid {
return Ok(Some("Invalid password. Try again".to_owned())); return Ok(Some(locale.invalid_password.as_ref().into()));
} }
Ok(None) Ok(None)
} }
crate::simple_state_handler!( crate::simple_state_handler!(get_password, check_password);
get_password,
check_password,
"Couldn't get the text of the message. Send the password again"
);

View File

@ -9,24 +9,43 @@ use teloxide::{
use tokio::{task::spawn_blocking, try_join}; use tokio::{task::spawn_blocking, try_join};
use trim_in_place::TrimInPlace; use trim_in_place::TrimInPlace;
#[derive(Clone, Copy)]
enum InvalidDocument {
NoFileSend,
FileTooLarge,
CouldntGetFileName,
InvalidFileName,
}
impl InvalidDocument {
const fn into_str(self, locale: LocaleRef) -> &'static str {
match self {
Self::NoFileSend => &locale.no_file_send,
Self::FileTooLarge => &locale.file_too_large,
Self::CouldntGetFileName => &locale.couldnt_get_file_name,
Self::InvalidFileName => &locale.invalid_file_name,
}
}
}
#[inline] #[inline]
fn validate_document(document: Option<&Document>) -> Result<&Document, &'static str> { fn validate_document(document: Option<&Document>) -> Result<&Document, InvalidDocument> {
let Some(document) = document else { let Some(document) = document else {
return Err("You didn't send a file. Try again"); return Err(InvalidDocument::NoFileSend);
}; };
if document.file.size > 1024 * 1024 * 200 { if document.file.size > 1024 * 1024 * 200 {
return Err("The file is larger than 200 MiB. Try splitting it into multiple files"); return Err(InvalidDocument::FileTooLarge);
} }
let name = match document.file_name.as_deref() { let name = match document.file_name.as_deref() {
Some(name) => Path::new(name.trim_end()), Some(name) => Path::new(name.trim_end()),
None => return Err("Couldn't get the name of the file. Try sending it again"), None => return Err(InvalidDocument::CouldntGetFileName),
}; };
match name.extension() { match name.extension() {
Some(ext) if ext.eq_ignore_ascii_case("json") => Ok(document), Some(ext) if ext.eq_ignore_ascii_case("json") => Ok(document),
_ => Err("Invalid file name. You need to send a json file. Try again"), _ => Err(InvalidDocument::InvalidFileName),
} }
} }
@ -45,6 +64,7 @@ async fn download_file(bot: &Throttle<Bot>, file: &FileMeta) -> crate::Result<Bo
fn process_accounts( fn process_accounts(
accounts: &mut [DecryptedAccount], accounts: &mut [DecryptedAccount],
existing_names: ahash::HashSet<String>, existing_names: ahash::HashSet<String>,
locale: LocaleRef,
) -> crate::Result<Result<(), String>> { ) -> crate::Result<Result<(), String>> {
for account in accounts.iter_mut() { for account in accounts.iter_mut() {
account.name.trim_in_place(); account.name.trim_in_place();
@ -82,12 +102,13 @@ fn process_accounts(
return Ok(Ok(())); return Ok(Ok(()));
} }
let mut error_text = "Your accounts have the following problems:".to_owned(); let mut error_text = locale.following_accounts_have_problems.as_ref().to_owned();
if !duplicates.is_empty() { if !duplicates.is_empty() {
write!( write!(
error_text, error_text,
"\n\nDuplicate names:\n{:?}", "\n\n{}:\n{:?}",
locale.duplicate_names,
duplicates.into_iter().format("\n") duplicates.into_iter().format("\n")
)?; )?;
} }
@ -95,7 +116,8 @@ fn process_accounts(
if !existing.is_empty() { if !existing.is_empty() {
write!( write!(
error_text, error_text,
"\n\nAccounts with these names already exist in the database:\n{:?}", "\n\n{}:\n{:?}",
locale.accounts_already_in_db,
existing.into_iter().format("\n") existing.into_iter().format("\n")
)?; )?;
} }
@ -103,12 +125,13 @@ fn process_accounts(
if !invalid.is_empty() { if !invalid.is_empty() {
write!( write!(
error_text, error_text,
"\n\nInvalid account fields:\n{:?}", "\n\n{}:\n{:?}",
locale.invalid_fields,
invalid.into_iter().format("\n") invalid.into_iter().format("\n")
)?; )?;
} }
error_text.push_str("\n\nFix these problems and send the file again"); error_text.push_str(&locale.fix_that_and_send_again);
Ok(Err(error_text)) Ok(Err(error_text))
} }
@ -117,10 +140,11 @@ fn process_accounts(
fn user_from_bytes( fn user_from_bytes(
bytes: impl AsRef<[u8]>, bytes: impl AsRef<[u8]>,
existing_names: ahash::HashSet<String>, existing_names: ahash::HashSet<String>,
locale: LocaleRef,
) -> crate::Result<Result<User, String>> { ) -> crate::Result<Result<User, String>> {
let mut user: User = serde_json::from_slice(bytes.as_ref())?; let mut user: User = serde_json::from_slice(bytes.as_ref())?;
drop(bytes); drop(bytes);
match process_accounts(&mut user.accounts, existing_names)? { match process_accounts(&mut user.accounts, existing_names, locale)? {
Ok(()) => Ok(Ok(user)), Ok(()) => Ok(Ok(user)),
Err(error_text) => Ok(Err(error_text)), Err(error_text) => Ok(Err(error_text)),
} }
@ -132,21 +156,24 @@ async fn user_from_document(
db: &Pool, db: &Pool,
document: Option<&Document>, document: Option<&Document>,
user_id: u64, user_id: u64,
locale: LocaleRef,
) -> Result<User, Cow<'static, str>> { ) -> Result<User, Cow<'static, str>> {
let (data, existing_names) = { let (data, existing_names) = {
let file = &validate_document(document)?.file; let file = &validate_document(document)
let data = download_file(bot, file).map_err(|_| "Error downloading the file. Try again"); .map_err(|err| err.into_str(locale))?
.file;
let data = download_file(bot, file).map_err(|_| locale.error_downloading_file.as_ref());
let existing_names = Account::get_names(user_id, db) let existing_names = Account::get_names(user_id, db)
.try_collect() .try_collect()
.map_err(|_| "Error getting existing account names. Try again"); .map_err(|_| locale.error_getting_account_names.as_ref());
try_join!(data, existing_names)? try_join!(data, existing_names)?
}; };
match spawn_blocking(|| user_from_bytes(data, existing_names)).await { match spawn_blocking(|| user_from_bytes(data, existing_names, locale)).await {
Ok(Ok(user)) => user.map_err(Cow::Owned), Ok(Ok(user)) => user.map_err(Cow::Owned),
_ => Err(Cow::Borrowed("Error parsing the json file. Try again")), _ => Err(Cow::Borrowed(&locale.error_parsing_json_file)),
} }
} }
@ -157,6 +184,7 @@ pub async fn get_user(
msg: Message, msg: Message,
db: Pool, db: Pool,
dialogue: MainDialogue, dialogue: MainDialogue,
locale: LocaleRef,
handler: PackagedHandler<User>, handler: PackagedHandler<User>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
@ -171,12 +199,17 @@ pub async fn get_user(
dialogue.exit().await?; dialogue.exit().await?;
handler handler
.previous .previous
.alter_message(&bot, "Successfully cancelled", deletion_markup(), None) .alter_message(
&bot,
locale.successfully_canceled.as_ref(),
deletion_markup(locale),
None,
)
.await?; .await?;
return Ok(()); return Ok(());
} }
let user = match user_from_document(&bot, &db, msg.document(), user_id).await { let user = match user_from_document(&bot, &db, msg.document(), user_id, locale).await {
Ok(user) => user, Ok(user) => user,
Err(error_text) => { Err(error_text) => {
handler handler
@ -191,7 +224,7 @@ pub async fn get_user(
let func = handler.func.take().unwrap(); let func = handler.func.take().unwrap();
drop(handler); drop(handler);
if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, user).await { if let Err(err) = func(bot, msg, db, dialogue.clone(), previous, locale, user).await {
let _ = dialogue.exit().await; let _ = dialogue.exit().await;
return Err(err); return Err(err);
} }

View File

@ -68,6 +68,7 @@ type DynHanlder<T> = Box<
Pool, Pool,
MainDialogue, MainDialogue,
MessageIds, MessageIds,
LocaleRef,
T, T,
) -> BoxFuture<'static, crate::Result<()>> ) -> BoxFuture<'static, crate::Result<()>>
+ Send, + Send,
@ -92,6 +93,7 @@ impl<T> Handler<T> {
Pool, Pool,
MainDialogue, MainDialogue,
MessageIds, MessageIds,
LocaleRef,
T, T,
) -> BoxFuture<'static, crate::Result<()>> ) -> BoxFuture<'static, crate::Result<()>>
+ Send + Send