Added support for multiple languages
This commit is contained in:
		@@ -1,12 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "db_name": "MySQL",
 | 
			
		||||
  "query": "INSERT INTO master_pass VALUES (?, ?, ?)",
 | 
			
		||||
  "describe": {
 | 
			
		||||
    "columns": [],
 | 
			
		||||
    "parameters": {
 | 
			
		||||
      "Right": 3
 | 
			
		||||
    },
 | 
			
		||||
    "nullable": []
 | 
			
		||||
  },
 | 
			
		||||
  "hash": "1fc824ca1ca447a990c3e68ee4f2a15b8e5bc260641057d914bb7ff871b51aa3"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								.sqlx/query-71661faf7bde5ba94a5090dde7fb3ee72e9b2860692e30333f2213188188fdb6.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.sqlx/query-71661faf7bde5ba94a5090dde7fb3ee72e9b2860692e30333f2213188188fdb6.json
									
									
									
										generated
									
									
									
										Normal 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"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								.sqlx/query-ff36e44aa2d064634d3ad183ce503cd30555d1d745cf5ab486c0d17e8e9a026f.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.sqlx/query-ff36e44aa2d064634d3ad183ce503cd30555d1d745cf5ab486c0d17e8e9a026f.json
									
									
									
										generated
									
									
									
										Normal 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
									
									
									
								
							
							
						
						
									
										304
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -42,24 +42,24 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "aho-corasick"
 | 
			
		||||
version = "1.1.2"
 | 
			
		||||
version = "1.1.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
 | 
			
		||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "memchr",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "allocator-api2"
 | 
			
		||||
version = "0.2.16"
 | 
			
		||||
version = "0.2.18"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
 | 
			
		||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "anyhow"
 | 
			
		||||
version = "1.0.80"
 | 
			
		||||
version = "1.0.82"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
 | 
			
		||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "aquamarine"
 | 
			
		||||
@@ -89,27 +89,17 @@ dependencies = [
 | 
			
		||||
 "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]]
 | 
			
		||||
name = "autocfg"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
version = "1.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
			
		||||
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "backtrace"
 | 
			
		||||
version = "0.3.69"
 | 
			
		||||
version = "0.3.71"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 | 
			
		||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "addr2line",
 | 
			
		||||
 "cc",
 | 
			
		||||
@@ -146,9 +136,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bitflags"
 | 
			
		||||
version = "2.4.2"
 | 
			
		||||
version = "2.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 | 
			
		||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
@@ -164,9 +154,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bumpalo"
 | 
			
		||||
version = "3.15.3"
 | 
			
		||||
version = "3.16.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
 | 
			
		||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "byteorder"
 | 
			
		||||
@@ -176,15 +166,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bytes"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
version = "1.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 | 
			
		||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cc"
 | 
			
		||||
version = "1.0.90"
 | 
			
		||||
version = "1.0.94"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
 | 
			
		||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
@@ -192,12 +182,6 @@ version = "1.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg_aliases"
 | 
			
		||||
version = "0.1.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "chacha20"
 | 
			
		||||
version = "0.9.1"
 | 
			
		||||
@@ -224,9 +208,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "chrono"
 | 
			
		||||
version = "0.4.35"
 | 
			
		||||
version = "0.4.38"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
 | 
			
		||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "num-traits",
 | 
			
		||||
]
 | 
			
		||||
@@ -281,9 +265,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crc"
 | 
			
		||||
version = "3.0.1"
 | 
			
		||||
version = "3.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
 | 
			
		||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "crc-catalog",
 | 
			
		||||
]
 | 
			
		||||
@@ -325,7 +309,7 @@ name = "cryptography"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "arrayvec",
 | 
			
		||||
 "bitflags 2.4.2",
 | 
			
		||||
 "bitflags 2.5.0",
 | 
			
		||||
 "chacha20poly1305",
 | 
			
		||||
 "entity",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
@@ -375,9 +359,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "der"
 | 
			
		||||
version = "0.7.8"
 | 
			
		||||
version = "0.7.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
 | 
			
		||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "const-oid",
 | 
			
		||||
 "pem-rfc7468",
 | 
			
		||||
@@ -426,18 +410,18 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "either"
 | 
			
		||||
version = "1.10.0"
 | 
			
		||||
version = "1.11.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 | 
			
		||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "serde",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "encoding_rs"
 | 
			
		||||
version = "0.8.33"
 | 
			
		||||
version = "0.8.34"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 | 
			
		||||
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
@@ -509,9 +493,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "fastrand"
 | 
			
		||||
version = "2.0.1"
 | 
			
		||||
version = "2.0.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
 | 
			
		||||
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "finl_unicode"
 | 
			
		||||
@@ -612,7 +596,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -657,9 +641,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "getrandom"
 | 
			
		||||
version = "0.2.12"
 | 
			
		||||
version = "0.2.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 | 
			
		||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "libc",
 | 
			
		||||
@@ -674,9 +658,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "h2"
 | 
			
		||||
version = "0.3.24"
 | 
			
		||||
version = "0.3.26"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
 | 
			
		||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "fnv",
 | 
			
		||||
@@ -854,9 +838,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "indexmap"
 | 
			
		||||
version = "2.2.5"
 | 
			
		||||
version = "2.2.6"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
 | 
			
		||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "equivalent",
 | 
			
		||||
 "hashbrown",
 | 
			
		||||
@@ -908,9 +892,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "itoa"
 | 
			
		||||
version = "1.0.10"
 | 
			
		||||
version = "1.0.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 | 
			
		||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "js-sys"
 | 
			
		||||
@@ -987,9 +971,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memchr"
 | 
			
		||||
version = "2.7.1"
 | 
			
		||||
version = "2.7.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 | 
			
		||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "mime"
 | 
			
		||||
@@ -1039,18 +1023,6 @@ version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
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]]
 | 
			
		||||
name = "nom"
 | 
			
		||||
version = "7.1.3"
 | 
			
		||||
@@ -1180,6 +1152,7 @@ dependencies = [
 | 
			
		||||
 "pretty_env_logger",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "serde_yaml",
 | 
			
		||||
 "sha2",
 | 
			
		||||
 "teloxide",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
@@ -1246,14 +1219,14 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-project-lite"
 | 
			
		||||
version = "0.2.13"
 | 
			
		||||
version = "0.2.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
 | 
			
		||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-utils"
 | 
			
		||||
@@ -1341,18 +1314,18 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "proc-macro2"
 | 
			
		||||
version = "1.0.78"
 | 
			
		||||
version = "1.0.80"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
 | 
			
		||||
checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.35"
 | 
			
		||||
version = "1.0.36"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 | 
			
		||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
]
 | 
			
		||||
@@ -1407,9 +1380,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex"
 | 
			
		||||
version = "1.10.3"
 | 
			
		||||
version = "1.10.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
 | 
			
		||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "aho-corasick",
 | 
			
		||||
 "memchr",
 | 
			
		||||
@@ -1430,15 +1403,15 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "regex-syntax"
 | 
			
		||||
version = "0.8.2"
 | 
			
		||||
version = "0.8.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 | 
			
		||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "reqwest"
 | 
			
		||||
version = "0.11.24"
 | 
			
		||||
version = "0.11.27"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
 | 
			
		||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "base64 0.21.7",
 | 
			
		||||
 "bytes",
 | 
			
		||||
@@ -1530,11 +1503,11 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustix"
 | 
			
		||||
version = "0.38.31"
 | 
			
		||||
version = "0.38.32"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
 | 
			
		||||
checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags 2.4.2",
 | 
			
		||||
 "bitflags 2.5.0",
 | 
			
		||||
 "errno",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "linux-raw-sys",
 | 
			
		||||
@@ -1638,14 +1611,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_json"
 | 
			
		||||
version = "1.0.114"
 | 
			
		||||
version = "1.0.116"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
 | 
			
		||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "ryu",
 | 
			
		||||
@@ -1676,6 +1649,19 @@ dependencies = [
 | 
			
		||||
 "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]]
 | 
			
		||||
name = "sha1"
 | 
			
		||||
version = "0.10.6"
 | 
			
		||||
@@ -1728,9 +1714,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "smallvec"
 | 
			
		||||
version = "1.13.1"
 | 
			
		||||
version = "1.13.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
 | 
			
		||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "socket2"
 | 
			
		||||
@@ -1780,9 +1766,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
 | 
			
		||||
checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "sqlx-core",
 | 
			
		||||
 "sqlx-macros",
 | 
			
		||||
@@ -1793,9 +1779,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-core"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
 | 
			
		||||
checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "ahash",
 | 
			
		||||
 "atoi",
 | 
			
		||||
@@ -1803,7 +1789,6 @@ dependencies = [
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "crc",
 | 
			
		||||
 "crossbeam-queue",
 | 
			
		||||
 "dotenvy",
 | 
			
		||||
 "either",
 | 
			
		||||
 "event-listener",
 | 
			
		||||
 "futures-channel",
 | 
			
		||||
@@ -1836,9 +1821,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-macros"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
 | 
			
		||||
checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -1849,11 +1834,10 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-macros-core"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
 | 
			
		||||
checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "atomic-write-file",
 | 
			
		||||
 "dotenvy",
 | 
			
		||||
 "either",
 | 
			
		||||
 "heck",
 | 
			
		||||
@@ -1874,13 +1858,13 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-mysql"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
 | 
			
		||||
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "atoi",
 | 
			
		||||
 "base64 0.21.7",
 | 
			
		||||
 "bitflags 2.4.2",
 | 
			
		||||
 "bitflags 2.5.0",
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "bytes",
 | 
			
		||||
 "crc",
 | 
			
		||||
@@ -1916,13 +1900,13 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-postgres"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
 | 
			
		||||
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "atoi",
 | 
			
		||||
 "base64 0.21.7",
 | 
			
		||||
 "bitflags 2.4.2",
 | 
			
		||||
 "bitflags 2.5.0",
 | 
			
		||||
 "byteorder",
 | 
			
		||||
 "crc",
 | 
			
		||||
 "dotenvy",
 | 
			
		||||
@@ -1943,7 +1927,6 @@ dependencies = [
 | 
			
		||||
 "rand",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "sha1",
 | 
			
		||||
 "sha2",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
 "sqlx-core",
 | 
			
		||||
@@ -1955,9 +1938,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "sqlx-sqlite"
 | 
			
		||||
version = "0.7.3"
 | 
			
		||||
version = "0.7.4"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
 | 
			
		||||
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "atoi",
 | 
			
		||||
 "flume",
 | 
			
		||||
@@ -2011,9 +1994,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "syn"
 | 
			
		||||
version = "2.0.52"
 | 
			
		||||
version = "2.0.59"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
 | 
			
		||||
checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@@ -2152,22 +2135,22 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror"
 | 
			
		||||
version = "1.0.57"
 | 
			
		||||
version = "1.0.58"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
 | 
			
		||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "thiserror-impl",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "thiserror-impl"
 | 
			
		||||
version = "1.0.57"
 | 
			
		||||
version = "1.0.58"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
 | 
			
		||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -2187,9 +2170,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio"
 | 
			
		||||
version = "1.36.0"
 | 
			
		||||
version = "1.37.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
 | 
			
		||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace",
 | 
			
		||||
 "bytes",
 | 
			
		||||
@@ -2211,7 +2194,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -2226,9 +2209,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tokio-stream"
 | 
			
		||||
version = "0.1.14"
 | 
			
		||||
version = "0.1.15"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
 | 
			
		||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
@@ -2275,7 +2258,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -2357,6 +2340,12 @@ dependencies = [
 | 
			
		||||
 "subtle",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unsafe-libyaml"
 | 
			
		||||
version = "0.2.11"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "untrusted"
 | 
			
		||||
version = "0.9.0"
 | 
			
		||||
@@ -2383,9 +2372,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "uuid"
 | 
			
		||||
version = "1.7.0"
 | 
			
		||||
version = "1.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
 | 
			
		||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "getrandom",
 | 
			
		||||
]
 | 
			
		||||
@@ -2450,7 +2439,7 @@ dependencies = [
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
 "wasm-bindgen-shared",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -2484,7 +2473,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
 "wasm-bindgen-backend",
 | 
			
		||||
 "wasm-bindgen-shared",
 | 
			
		||||
]
 | 
			
		||||
@@ -2526,9 +2515,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "whoami"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
version = "1.5.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0fec781d48b41f8163426ed18e8fc2864c12937df9ce54c88ede7bd47270893e"
 | 
			
		||||
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "redox_syscall",
 | 
			
		||||
 "wasite",
 | 
			
		||||
@@ -2580,7 +2569,7 @@ version = "0.52.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows-targets 0.52.4",
 | 
			
		||||
 "windows-targets 0.52.5",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -2600,17 +2589,18 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows-targets"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
 | 
			
		||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "windows_aarch64_gnullvm 0.52.4",
 | 
			
		||||
 "windows_aarch64_msvc 0.52.4",
 | 
			
		||||
 "windows_i686_gnu 0.52.4",
 | 
			
		||||
 "windows_i686_msvc 0.52.4",
 | 
			
		||||
 "windows_x86_64_gnu 0.52.4",
 | 
			
		||||
 "windows_x86_64_gnullvm 0.52.4",
 | 
			
		||||
 "windows_x86_64_msvc 0.52.4",
 | 
			
		||||
 "windows_aarch64_gnullvm 0.52.5",
 | 
			
		||||
 "windows_aarch64_msvc 0.52.5",
 | 
			
		||||
 "windows_i686_gnu 0.52.5",
 | 
			
		||||
 "windows_i686_gnullvm",
 | 
			
		||||
 "windows_i686_msvc 0.52.5",
 | 
			
		||||
 "windows_x86_64_gnu 0.52.5",
 | 
			
		||||
 "windows_x86_64_gnullvm 0.52.5",
 | 
			
		||||
 "windows_x86_64_msvc 0.52.5",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@@ -2621,9 +2611,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_aarch64_gnullvm"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
 | 
			
		||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_aarch64_msvc"
 | 
			
		||||
@@ -2633,9 +2623,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_aarch64_msvc"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
 | 
			
		||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_i686_gnu"
 | 
			
		||||
@@ -2645,9 +2635,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_i686_gnu"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
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]]
 | 
			
		||||
name = "windows_i686_msvc"
 | 
			
		||||
@@ -2657,9 +2653,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_i686_msvc"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
 | 
			
		||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnu"
 | 
			
		||||
@@ -2669,9 +2665,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnu"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
 | 
			
		||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnullvm"
 | 
			
		||||
@@ -2681,9 +2677,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_gnullvm"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
 | 
			
		||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_msvc"
 | 
			
		||||
@@ -2693,9 +2689,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "windows_x86_64_msvc"
 | 
			
		||||
version = "0.52.4"
 | 
			
		||||
version = "0.52.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
 | 
			
		||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "winreg"
 | 
			
		||||
@@ -2724,7 +2720,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.52",
 | 
			
		||||
 "syn 2.0.59",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ parking_lot = "0.12"
 | 
			
		||||
pretty_env_logger = "0.5"
 | 
			
		||||
serde = { version = "1", features = ["derive"] }
 | 
			
		||||
serde_json = "1"
 | 
			
		||||
serde_yaml = "0.9"
 | 
			
		||||
sha2 = "0.10"
 | 
			
		||||
teloxide = { version = "0.12", features = [
 | 
			
		||||
    "macros",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								entity/migrations/0002_locales.down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								entity/migrations/0002_locales.down.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
-- Add down migration script here
 | 
			
		||||
ALTER TABLE master_pass
 | 
			
		||||
DROP COLUMN locale;
 | 
			
		||||
							
								
								
									
										3
									
								
								entity/migrations/0002_locales.up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								entity/migrations/0002_locales.up.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
-- Add up migration script here
 | 
			
		||||
ALTER TABLE master_pass
 | 
			
		||||
ADD COLUMN locale SMALLINT UNSIGNED NOT NULL DEFAULT 1;
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#![allow(clippy::missing_errors_doc)]
 | 
			
		||||
 | 
			
		||||
pub mod account;
 | 
			
		||||
pub mod locale;
 | 
			
		||||
pub mod master_pass;
 | 
			
		||||
pub mod prelude;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								entity/src/locale.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								entity/src/locale.rs
									
									
									
									
									
										Normal 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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
use super::Pool;
 | 
			
		||||
use crate::{locale::LocaleType, Pool};
 | 
			
		||||
use sqlx::{prelude::FromRow, query, query_as, Executor, MySql};
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, PartialEq, FromRow, Eq)]
 | 
			
		||||
@@ -11,12 +11,14 @@ pub struct MasterPass {
 | 
			
		||||
impl MasterPass {
 | 
			
		||||
    // Inserts the master password into DB
 | 
			
		||||
    #[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!(
 | 
			
		||||
            "INSERT INTO master_pass VALUES (?, ?, ?)",
 | 
			
		||||
            "INSERT INTO master_pass VALUES (?, ?, ?, ?)",
 | 
			
		||||
            self.user_id,
 | 
			
		||||
            self.salt,
 | 
			
		||||
            self.password_hash
 | 
			
		||||
            self.password_hash,
 | 
			
		||||
            locale
 | 
			
		||||
        )
 | 
			
		||||
        .execute(pool)
 | 
			
		||||
        .await
 | 
			
		||||
@@ -26,7 +28,7 @@ impl MasterPass {
 | 
			
		||||
    /// Gets the master password from the database
 | 
			
		||||
    #[inline]
 | 
			
		||||
    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)
 | 
			
		||||
            .fetch_optional(pool)
 | 
			
		||||
            .await
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								locales/eng.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								locales/eng.yaml
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										83
									
								
								locales/ru.yaml
									
									
									
									
									
										Normal 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: "Выберете язык"
 | 
			
		||||
@@ -1,22 +1,31 @@
 | 
			
		||||
//! 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 entity::locale::LocaleType;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
use teloxide::types::CallbackQuery;
 | 
			
		||||
 | 
			
		||||
type NameHash = Vec<u8>;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 | 
			
		||||
#[derive(Clone, Copy, PartialEq, Eq)]
 | 
			
		||||
pub enum AlterableField {
 | 
			
		||||
    Name,
 | 
			
		||||
    Login,
 | 
			
		||||
    Pass,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug)]
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
pub enum CallbackCommand {
 | 
			
		||||
    DeleteMessage,
 | 
			
		||||
    Get(NameHash),
 | 
			
		||||
@@ -25,12 +34,13 @@ pub enum CallbackCommand {
 | 
			
		||||
    Hide(NameHash),
 | 
			
		||||
    Alter(NameHash, AlterableField),
 | 
			
		||||
    DeleteAccount { name: NameHash, is_command: bool },
 | 
			
		||||
    ChangeLocale(LocaleType),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CallbackCommand {
 | 
			
		||||
    pub fn from_query(q: CallbackQuery) -> Option<Self> {
 | 
			
		||||
        q.message.as_ref()?;
 | 
			
		||||
        q.data.and_then(|data| data.parse().ok())
 | 
			
		||||
        q.message?;
 | 
			
		||||
        q.data?.parse().inspect_err(|err| log::error!("{err}")).ok()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -45,13 +55,19 @@ impl FromStr for CallbackCommand {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let mut substrings = s.split(' ');
 | 
			
		||||
        let (Some(command), Some(name), None) =
 | 
			
		||||
        let (Some(command), Some(param), None) =
 | 
			
		||||
            (substrings.next(), substrings.next(), substrings.next())
 | 
			
		||||
        else {
 | 
			
		||||
            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 {
 | 
			
		||||
            return Err(InvalidCommand::InvalidOutputLength);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ async fn get_master_pass(
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    name: String,
 | 
			
		||||
    field: AlterableField,
 | 
			
		||||
    field_value: String,
 | 
			
		||||
@@ -57,7 +58,7 @@ async fn get_master_pass(
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(
 | 
			
		||||
        &bot,
 | 
			
		||||
        "Success. Choose the account to view",
 | 
			
		||||
        locale.success_choose_account_to_view.as_ref(),
 | 
			
		||||
        menu_markup("get", user_id, &db).await?,
 | 
			
		||||
        None,
 | 
			
		||||
    )
 | 
			
		||||
@@ -66,7 +67,7 @@ async fn get_master_pass(
 | 
			
		||||
    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]
 | 
			
		||||
pub async fn alter(
 | 
			
		||||
@@ -74,6 +75,7 @@ pub async fn alter(
 | 
			
		||||
    q: CallbackQuery,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    (hash, field): (super::NameHash, AlterableField),
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    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 {
 | 
			
		||||
        bot.send_message(ids.0, "Account wasn't found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        bot.answer_callback_query(q.id).await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
@@ -90,15 +92,15 @@ pub async fn alter(
 | 
			
		||||
    let text = match field {
 | 
			
		||||
        Name => {
 | 
			
		||||
            change_state!(dialogue, ids, (name, field), State::GetNewName, get_field);
 | 
			
		||||
            "Send new account name"
 | 
			
		||||
            locale.send_new_name.as_ref()
 | 
			
		||||
        }
 | 
			
		||||
        Login => {
 | 
			
		||||
            change_state!(dialogue, ids, (name, field), State::GetLogin, get_field);
 | 
			
		||||
            "Send new account login"
 | 
			
		||||
            locale.send_new_login.as_ref()
 | 
			
		||||
        }
 | 
			
		||||
        Pass => {
 | 
			
		||||
            change_state!(dialogue, ids, (name, field), State::GetPassword, get_field);
 | 
			
		||||
            "Send new account password"
 | 
			
		||||
            locale.send_new_password.as_ref()
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/callbacks/change_locale.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/callbacks/change_locale.rs
									
									
									
									
									
										Normal 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(())
 | 
			
		||||
}
 | 
			
		||||
@@ -3,12 +3,14 @@ use teloxide::types::ParseMode;
 | 
			
		||||
use tokio::task::spawn_blocking;
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
async fn get_master_pass(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    name: String,
 | 
			
		||||
    master_pass: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
@@ -17,8 +19,8 @@ async fn get_master_pass(
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
 | 
			
		||||
    let Some(account) = Account::get(user_id, &name, &db).await? else {
 | 
			
		||||
        bot.send_message(msg.chat.id, "Account not found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
@@ -26,15 +28,12 @@ async fn get_master_pass(
 | 
			
		||||
    let account =
 | 
			
		||||
        spawn_blocking(move || DecryptedAccount::from_account(account, &master_pass)).await??;
 | 
			
		||||
 | 
			
		||||
    let text = format!(
 | 
			
		||||
        "Name:\n`{name}`\nLogin:\n`{}`\nPassword:\n`{}`",
 | 
			
		||||
        account.login, account.password
 | 
			
		||||
    );
 | 
			
		||||
    let text = locale.show_account(&account.name, &account.login, &account.password);
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(
 | 
			
		||||
        &bot,
 | 
			
		||||
        text,
 | 
			
		||||
        account_markup(&name, false),
 | 
			
		||||
        account_markup(&name, false, locale),
 | 
			
		||||
        ParseMode::MarkdownV2,
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
@@ -47,20 +46,21 @@ pub async fn decrypt(
 | 
			
		||||
    q: CallbackQuery,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    hash: super::NameHash,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    let mut ids: MessageIds = q.message.as_ref().unwrap().into();
 | 
			
		||||
    let user_id = q.from.id.0;
 | 
			
		||||
 | 
			
		||||
    let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
 | 
			
		||||
        bot.send_message(ids.0, "Account wasn't found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(ids.0, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        bot.answer_callback_query(q.id).await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(&bot, "Send master password", None, None)
 | 
			
		||||
    ids.alter_message(&bot, locale.send_master_password.as_ref(), None, None)
 | 
			
		||||
        .await?;
 | 
			
		||||
    bot.answer_callback_query(q.id).await?;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
use crate::{change_state, prelude::*};
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
async fn get_master_pass(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    name: String,
 | 
			
		||||
    _: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
@@ -15,13 +17,8 @@ async fn get_master_pass(
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
    Account::delete(user_id, &name, &db).await?;
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(
 | 
			
		||||
        &bot,
 | 
			
		||||
        "The account is successfully deleted",
 | 
			
		||||
        deletion_markup(),
 | 
			
		||||
        None,
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
    ids.alter_message(&bot, locale.success.as_ref(), deletion_markup(locale), None)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -32,26 +29,26 @@ pub async fn delete(
 | 
			
		||||
    q: CallbackQuery,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    (hash, is_command): (super::NameHash, bool),
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    const TEXT: &str = "Send master password. \
 | 
			
		||||
        Once you send the master password the account is unrecoverable";
 | 
			
		||||
    let text = locale.send_master_pass_to_delete_account.as_ref();
 | 
			
		||||
 | 
			
		||||
    let mut ids: MessageIds = q.message.as_ref().unwrap().into();
 | 
			
		||||
    let user_id = q.from.id.0;
 | 
			
		||||
 | 
			
		||||
    let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
 | 
			
		||||
        bot.send_message(ids.0, "Account wasn't found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(ids.0, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        bot.answer_callback_query(q.id).await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if is_command {
 | 
			
		||||
        ids.alter_message(&bot, TEXT, None, None).await?;
 | 
			
		||||
        ids.alter_message(&bot, text, None, None).await?;
 | 
			
		||||
    } else {
 | 
			
		||||
        let msg = bot.send_message(ids.0, TEXT).await?;
 | 
			
		||||
        let msg = bot.send_message(ids.0, text).await?;
 | 
			
		||||
        ids = MessageIds::from(&msg);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,15 @@ use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
/// Deletes the message from the callback
 | 
			
		||||
#[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 bot.delete_message(msg.chat.id, msg.id).await.is_err() {
 | 
			
		||||
            bot.send_message(msg.chat.id, "Error deleting the message")
 | 
			
		||||
                .reply_markup(deletion_markup())
 | 
			
		||||
            bot.send_message(msg.chat.id, locale.error_deleting_message.as_ref())
 | 
			
		||||
                .reply_markup(deletion_markup(locale))
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,25 +6,26 @@ pub async fn get(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    q: CallbackQuery,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    hash: super::NameHash,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    let user_id = q.from.id.0;
 | 
			
		||||
    let mut ids: MessageIds = q.message.as_ref().unwrap().into();
 | 
			
		||||
 | 
			
		||||
    let Some(name) = Account::get_name_by_hash(user_id, &hash, &db).await? else {
 | 
			
		||||
        bot.send_message(ids.0, "Account wasn't found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(ids.0, locale.account_not_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        bot.answer_callback_query(q.id).await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let text = format!("Name:\n`{name}`\nLogin:\n\\*\\*\\*\nPassword:\n\\*\\*\\*");
 | 
			
		||||
    let text = locale.show_hidden_account(&name);
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(
 | 
			
		||||
        &bot,
 | 
			
		||||
        text,
 | 
			
		||||
        account_markup(&name, true),
 | 
			
		||||
        account_markup(&name, true, locale),
 | 
			
		||||
        ParseMode::MarkdownV2,
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,28 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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 mut ids: MessageIds = q.message.as_ref().unwrap().into();
 | 
			
		||||
 | 
			
		||||
    let markup = menu_markup("get", user_id, &db).await?;
 | 
			
		||||
    if markup.inline_keyboard.is_empty() {
 | 
			
		||||
        ids.alter_message(&bot, "You don't have any accounts", deletion_markup(), None)
 | 
			
		||||
            .await?;
 | 
			
		||||
        ids.alter_message(
 | 
			
		||||
            &bot,
 | 
			
		||||
            locale.no_accounts_found.as_ref(),
 | 
			
		||||
            deletion_markup(locale),
 | 
			
		||||
            None,
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ids.alter_message(&bot, "Choose your account", markup, None)
 | 
			
		||||
    ids.alter_message(&bot, locale.choose_account.as_ref(), markup, None)
 | 
			
		||||
        .await?;
 | 
			
		||||
    bot.answer_callback_query(q.id).await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
 
 | 
			
		||||
@@ -13,41 +13,27 @@ crate::export_handlers!(
 | 
			
		||||
    import,
 | 
			
		||||
    menu,
 | 
			
		||||
    set_master_pass,
 | 
			
		||||
    start
 | 
			
		||||
    start,
 | 
			
		||||
    change_language
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
use teloxide::macros::BotCommands;
 | 
			
		||||
 | 
			
		||||
#[derive(BotCommands, Clone, Copy)]
 | 
			
		||||
#[command(
 | 
			
		||||
    rename_rule = "snake_case",
 | 
			
		||||
    description = "These commands are supported:"
 | 
			
		||||
)]
 | 
			
		||||
#[command(rename_rule = "snake_case")]
 | 
			
		||||
pub enum Command {
 | 
			
		||||
    #[command(description = "displays the welcome message")]
 | 
			
		||||
    Start,
 | 
			
		||||
    #[command(description = "displays this text")]
 | 
			
		||||
    Help,
 | 
			
		||||
    #[command(description = "sets the master password")]
 | 
			
		||||
    SetMasterPass,
 | 
			
		||||
    #[command(description = "gives you a menu to manage your accounts")]
 | 
			
		||||
    Menu,
 | 
			
		||||
    #[command(description = "adds the account")]
 | 
			
		||||
    AddAccount,
 | 
			
		||||
    #[command(description = "gets the account")]
 | 
			
		||||
    GetAccount,
 | 
			
		||||
    #[command(description = "gets a list of accounts")]
 | 
			
		||||
    GetAccounts,
 | 
			
		||||
    #[command(description = "deletes the account")]
 | 
			
		||||
    Delete,
 | 
			
		||||
    #[command(description = "deletes all the accounts and the master password")]
 | 
			
		||||
    DeleteAll,
 | 
			
		||||
    #[command(description = "exports all the accounts in a json file")]
 | 
			
		||||
    Export,
 | 
			
		||||
    #[command(description = "loads the accounts from a json file")]
 | 
			
		||||
    Import,
 | 
			
		||||
    #[command(description = "generates 10 secure passwords")]
 | 
			
		||||
    GenPassword,
 | 
			
		||||
    #[command(description = "cancels the current action")]
 | 
			
		||||
    Cancel,
 | 
			
		||||
    ChangeLanguage,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ async fn get_master_pass(
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    name: String,
 | 
			
		||||
    login: String,
 | 
			
		||||
    password: String,
 | 
			
		||||
@@ -30,26 +31,26 @@ async fn get_master_pass(
 | 
			
		||||
    .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?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
handler!(
 | 
			
		||||
    get_password(name:String, login: String, password: String),
 | 
			
		||||
    "Send master password",
 | 
			
		||||
    send_master_password,
 | 
			
		||||
    State::GetMasterPass,
 | 
			
		||||
    get_master_pass
 | 
			
		||||
);
 | 
			
		||||
handler!(get_login(name: String, login: String),
 | 
			
		||||
    "Send password",
 | 
			
		||||
send_password,
 | 
			
		||||
    State::GetPassword,
 | 
			
		||||
    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!(
 | 
			
		||||
    add_account,
 | 
			
		||||
    "Send account name",
 | 
			
		||||
    send_account_name,
 | 
			
		||||
    State::GetNewName,
 | 
			
		||||
    get_account_name
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@ use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
/// Handles /cancel command when there's no active state
 | 
			
		||||
#[inline]
 | 
			
		||||
pub async fn cancel(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, "Nothing to cancel")
 | 
			
		||||
        .reply_markup(deletion_markup())
 | 
			
		||||
pub async fn cancel(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, locale.nothing_to_cancel.as_ref())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/commands/change_language.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/commands/change_language.rs
									
									
									
									
									
										Normal 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(())
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,24 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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 markup = menu_markup("delete1", user_id, &db).await?;
 | 
			
		||||
 | 
			
		||||
    if markup.inline_keyboard.is_empty() {
 | 
			
		||||
        bot.send_message(msg.chat.id, "You don't have any accounts")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        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)
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ async fn get_master_pass(
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    _: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    dialogue.exit().await?;
 | 
			
		||||
@@ -23,22 +24,22 @@ async fn get_master_pass(
 | 
			
		||||
    let text = match result {
 | 
			
		||||
        (Ok(()), Ok(())) => {
 | 
			
		||||
            txn.commit().await?;
 | 
			
		||||
            "Everything was deleted"
 | 
			
		||||
            locale.everything_was_deleted.as_ref()
 | 
			
		||||
        }
 | 
			
		||||
        (Err(err), _) | (_, Err(err)) => {
 | 
			
		||||
            error!("{}", crate::Error::from(err));
 | 
			
		||||
            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?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
first_handler!(
 | 
			
		||||
    delete_all,
 | 
			
		||||
    "Send master password to delete EVERYTHING.\nTHIS ACTION IS IRREVERSIBLE",
 | 
			
		||||
    send_master_pass_to_delete_everything,
 | 
			
		||||
    State::GetMasterPass,
 | 
			
		||||
    get_master_pass
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ async fn get_master_pass(
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    master_pass: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    dialogue.exit().await?;
 | 
			
		||||
@@ -51,14 +52,14 @@ async fn get_master_pass(
 | 
			
		||||
    let file = InputFile::memory(json).file_name("accounts.json");
 | 
			
		||||
 | 
			
		||||
    bot.send_document(msg.chat.id, file)
 | 
			
		||||
        .reply_markup(deletion_markup())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
first_handler!(
 | 
			
		||||
    export,
 | 
			
		||||
    "Send the master password to export your accounts",
 | 
			
		||||
    send_master_password,
 | 
			
		||||
    State::GetMasterPass,
 | 
			
		||||
    get_master_pass
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,11 @@ const BUFFER_LENGTH: usize =
 | 
			
		||||
 | 
			
		||||
/// Handles /`gen_password` command by generating 10 copyable passwords and sending them to the user
 | 
			
		||||
#[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 passwords: PasswordArray = spawn_blocking(generate_passwords).await?;
 | 
			
		||||
    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())
 | 
			
		||||
        .parse_mode(ParseMode::MarkdownV2)
 | 
			
		||||
        .reply_markup(deletion_markup())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,24 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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 markup = menu_markup("decrypt", user_id, &db).await?;
 | 
			
		||||
 | 
			
		||||
    if markup.inline_keyboard.is_empty() {
 | 
			
		||||
        bot.send_message(msg.chat.id, "You don't have any accounts")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        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)
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
 
 | 
			
		||||
@@ -4,26 +4,32 @@ use teloxide::types::ParseMode;
 | 
			
		||||
 | 
			
		||||
/// Handles /`get_accounts` command by sending the list of copyable account names to the user
 | 
			
		||||
#[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 mut account_names = Account::get_names(user_id, &db);
 | 
			
		||||
 | 
			
		||||
    let mut text = if let Some(name) = account_names.try_next().await? {
 | 
			
		||||
        format!("Accounts:\n`{name}`")
 | 
			
		||||
    } else {
 | 
			
		||||
        bot.send_message(msg.chat.id, "No accounts found")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
    let Some(mut text) = account_names.try_next().await? else {
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    text.insert(0, '`');
 | 
			
		||||
    text.push('`');
 | 
			
		||||
 | 
			
		||||
    while let Some(name) = account_names.try_next().await? {
 | 
			
		||||
        write!(text, "\n`{name}`")?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bot.send_message(msg.chat.id, text)
 | 
			
		||||
        .parse_mode(ParseMode::MarkdownV2)
 | 
			
		||||
        .reply_markup(deletion_markup())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
use teloxide::utils::command::BotCommands;
 | 
			
		||||
 | 
			
		||||
/// Handles the help command by sending the passwords descryptions
 | 
			
		||||
#[inline]
 | 
			
		||||
pub async fn help(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, Command::descriptions().to_string())
 | 
			
		||||
        .reply_markup(deletion_markup())
 | 
			
		||||
pub async fn help(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, locale.help_command.as_ref())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,12 +27,14 @@ async fn encrypt_account(
 | 
			
		||||
 | 
			
		||||
/// Gets the master password, encryptes and adds the accounts to the DB
 | 
			
		||||
#[inline]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
async fn get_master_pass(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    user: User,
 | 
			
		||||
    master_pass: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
@@ -53,22 +55,18 @@ async fn get_master_pass(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let text = if failed.is_empty() {
 | 
			
		||||
        "Success".to_owned()
 | 
			
		||||
        locale.success.as_ref().to_owned()
 | 
			
		||||
    } else {
 | 
			
		||||
        format!(
 | 
			
		||||
            "Failed to create the following accounts:\n{}",
 | 
			
		||||
            "{}:\n{}",
 | 
			
		||||
            locale.couldnt_create_following_accounts,
 | 
			
		||||
            failed.into_iter().format("\n")
 | 
			
		||||
        )
 | 
			
		||||
    };
 | 
			
		||||
    ids.alter_message(&bot, text, deletion_markup(), None)
 | 
			
		||||
    ids.alter_message(&bot, text, deletion_markup(locale), None)
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
handler!(get_user(user: User), "Send master password", State::GetMasterPass, get_master_pass);
 | 
			
		||||
first_handler!(
 | 
			
		||||
    import,
 | 
			
		||||
    "Send a json document with the same format as created by /export",
 | 
			
		||||
    State::GetUser,
 | 
			
		||||
    get_user
 | 
			
		||||
);
 | 
			
		||||
handler!(get_user(user: User),send_master_password, State::GetMasterPass, get_master_pass);
 | 
			
		||||
first_handler!(import, send_json_file, State::GetUser, get_user);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,24 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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 markup = menu_markup("get", user_id, &db).await?;
 | 
			
		||||
 | 
			
		||||
    if markup.inline_keyboard.is_empty() {
 | 
			
		||||
        bot.send_message(msg.chat.id, "You don't have any accounts")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.no_accounts_found.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        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)
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,29 @@
 | 
			
		||||
use crate::{change_state, prelude::*};
 | 
			
		||||
use crate::{change_state, locales::LocaleTypeExt, prelude::*};
 | 
			
		||||
use cryptography::hashing::HashedBytes;
 | 
			
		||||
use entity::locale::LocaleType;
 | 
			
		||||
use tokio::task::spawn_blocking;
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
async fn get_master_pass2(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    hash: HashedBytes<[u8; 64], [u8; 64]>,
 | 
			
		||||
    master_pass: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    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()) {
 | 
			
		||||
        ids.alter_message(
 | 
			
		||||
            &bot,
 | 
			
		||||
            "The passwords didn't match. Use the command again",
 | 
			
		||||
            deletion_markup(),
 | 
			
		||||
            locale.master_password_dont_match.as_ref(),
 | 
			
		||||
            deletion_markup(locale),
 | 
			
		||||
            None,
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@@ -32,9 +36,14 @@ async fn get_master_pass2(
 | 
			
		||||
        password_hash: hash.hash.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?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
@@ -47,11 +56,13 @@ async fn get_master_pass(
 | 
			
		||||
    _: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    mut ids: MessageIds,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    master_pass: String,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    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!(
 | 
			
		||||
        dialogue,
 | 
			
		||||
@@ -71,16 +82,17 @@ pub async fn set_master_pass(
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
    if MasterPass::exists(user_id, &db).await? {
 | 
			
		||||
        bot.send_message(msg.chat.id, "Master password already exists")
 | 
			
		||||
            .reply_markup(deletion_markup())
 | 
			
		||||
        bot.send_message(msg.chat.id, locale.master_password_is_set.as_ref())
 | 
			
		||||
            .reply_markup(deletion_markup(locale))
 | 
			
		||||
            .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
    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?;
 | 
			
		||||
 | 
			
		||||
    change_state!(
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,8 @@ use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
/// Handles /start command by sending the greeting message
 | 
			
		||||
#[inline]
 | 
			
		||||
pub async fn start(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(
 | 
			
		||||
        msg.chat.id,
 | 
			
		||||
        "Hi! This bot can be used to store the passwords securely. \
 | 
			
		||||
        Use /help command to get the list of commands",
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
pub async fn start(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, locale.start_command.as_ref())
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,9 @@ use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
/// Handles the messages which weren't matched by any commands or states
 | 
			
		||||
#[inline]
 | 
			
		||||
pub async fn default(bot: Throttle<Bot>, msg: Message) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(
 | 
			
		||||
        msg.chat.id,
 | 
			
		||||
        "Unknown command. Use /help command to get the list of commands",
 | 
			
		||||
    )
 | 
			
		||||
    .reply_markup(deletion_markup())
 | 
			
		||||
    .await?;
 | 
			
		||||
pub async fn default(bot: Throttle<Bot>, msg: Message, locale: LocaleRef) -> crate::Result<()> {
 | 
			
		||||
    bot.send_message(msg.chat.id, locale.unknown_command_use_help.as_ref())
 | 
			
		||||
        .reply_markup(deletion_markup(locale))
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,4 +14,6 @@ pub enum InvalidCommand {
 | 
			
		||||
    InvalidOutputLength,
 | 
			
		||||
    #[error("Error decoding the values: {0}")]
 | 
			
		||||
    NameDecodingError(#[from] base64::DecodeError),
 | 
			
		||||
    #[error("Unknown locale passed into callback")]
 | 
			
		||||
    UnknownLocale,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,14 @@ async fn notify_about_no_user_info(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    msg: Message,
 | 
			
		||||
    state: State,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
) -> 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 {
 | 
			
		||||
        State::Start => {
 | 
			
		||||
            bot.send_message(msg.chat.id, TEXT)
 | 
			
		||||
                .reply_markup(deletion_markup())
 | 
			
		||||
            bot.send_message(msg.chat.id, text)
 | 
			
		||||
                .reply_markup(deletion_markup(locale))
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
        State::GetNewName(handler)
 | 
			
		||||
@@ -30,14 +31,14 @@ async fn notify_about_no_user_info(
 | 
			
		||||
            let mut handler = handler.lock().await;
 | 
			
		||||
            handler
 | 
			
		||||
                .previous
 | 
			
		||||
                .alter_message(&bot, TEXT, None, None)
 | 
			
		||||
                .alter_message(&bot, text, None, None)
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
        State::GetUser(handler) => {
 | 
			
		||||
            let mut handler = handler.lock().await;
 | 
			
		||||
            handler
 | 
			
		||||
                .previous
 | 
			
		||||
                .alter_message(&bot, TEXT, None, None)
 | 
			
		||||
                .alter_message(&bot, text, None, None)
 | 
			
		||||
                .await?;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										166
									
								
								src/locales.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								src/locales.rs
									
									
									
									
									
										Normal 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,7 @@ macro_rules! change_state {
 | 
			
		||||
    ($dialogue: expr, $previous: expr, ($($param: ident),*), $next_state: expr, $next_func: ident) => {{
 | 
			
		||||
        $dialogue
 | 
			
		||||
            .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,
 | 
			
		||||
            )))
 | 
			
		||||
            .await?;
 | 
			
		||||
@@ -12,14 +12,17 @@ macro_rules! change_state {
 | 
			
		||||
 | 
			
		||||
#[macro_export]
 | 
			
		||||
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]
 | 
			
		||||
        pub async fn $function_name(
 | 
			
		||||
            bot: Throttle<Bot>,
 | 
			
		||||
            msg: Message,
 | 
			
		||||
            dialogue: MainDialogue,
 | 
			
		||||
            locale: LocaleRef,
 | 
			
		||||
        ) -> $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);
 | 
			
		||||
 | 
			
		||||
@@ -30,7 +33,7 @@ macro_rules! first_handler {
 | 
			
		||||
 | 
			
		||||
#[macro_export]
 | 
			
		||||
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)]
 | 
			
		||||
        #[inline]
 | 
			
		||||
        async fn $function_name(
 | 
			
		||||
@@ -39,9 +42,10 @@ macro_rules! handler {
 | 
			
		||||
            _: Pool,
 | 
			
		||||
            dialogue: MainDialogue,
 | 
			
		||||
            mut ids: MessageIds,
 | 
			
		||||
            locale: LocaleRef,
 | 
			
		||||
            $($param: $type),*
 | 
			
		||||
        ) -> $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);
 | 
			
		||||
 | 
			
		||||
@@ -52,13 +56,14 @@ macro_rules! handler {
 | 
			
		||||
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! simple_state_handler {
 | 
			
		||||
    ($function_name: ident, $check: ident, $no_text_message: literal) => {
 | 
			
		||||
    ($function_name: ident, $check: ident) => {
 | 
			
		||||
        #[inline]
 | 
			
		||||
        pub async fn $function_name(
 | 
			
		||||
            bot: Throttle<Bot>,
 | 
			
		||||
            msg: Message,
 | 
			
		||||
            db: Pool,
 | 
			
		||||
            dialogue: MainDialogue,
 | 
			
		||||
            locale: LocaleRef,
 | 
			
		||||
            next: PackagedHandler<String>,
 | 
			
		||||
        ) -> $crate::Result<()> {
 | 
			
		||||
            super::generic::generic(
 | 
			
		||||
@@ -66,8 +71,8 @@ macro_rules! simple_state_handler {
 | 
			
		||||
                msg,
 | 
			
		||||
                db,
 | 
			
		||||
                dialogue,
 | 
			
		||||
                |msg, db, param| Box::pin($check(msg, db, param)),
 | 
			
		||||
                $no_text_message,
 | 
			
		||||
                |msg, db, locale, param| Box::pin($check(msg, db, locale, param)),
 | 
			
		||||
                locale,
 | 
			
		||||
                next,
 | 
			
		||||
            )
 | 
			
		||||
            .await
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -4,6 +4,7 @@ mod default;
 | 
			
		||||
mod delete_mesage_handler;
 | 
			
		||||
mod errors;
 | 
			
		||||
mod filter_user_info;
 | 
			
		||||
mod locales;
 | 
			
		||||
mod macros;
 | 
			
		||||
mod markups;
 | 
			
		||||
mod master_password_check;
 | 
			
		||||
@@ -42,7 +43,8 @@ fn get_dispatcher(
 | 
			
		||||
        .branch(case![Command::Delete].endpoint(commands::delete))
 | 
			
		||||
        .branch(case![Command::DeleteAll].endpoint(commands::delete_all))
 | 
			
		||||
        .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()
 | 
			
		||||
        .map_async(delete_mesage_handler::delete_message)
 | 
			
		||||
@@ -67,9 +69,11 @@ fn get_dispatcher(
 | 
			
		||||
        .branch(
 | 
			
		||||
            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()
 | 
			
		||||
        .map_async(Locale::from_update)
 | 
			
		||||
        .enter_dialogue::<Update, InMemStorage<State>, State>()
 | 
			
		||||
        .branch(message_handler)
 | 
			
		||||
        .branch(callback_handler);
 | 
			
		||||
@@ -85,6 +89,8 @@ async fn main() -> Result<()> {
 | 
			
		||||
    let _ = dotenv();
 | 
			
		||||
    pretty_env_logger::init();
 | 
			
		||||
 | 
			
		||||
    locales::LocaleStore::init()?;
 | 
			
		||||
 | 
			
		||||
    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 pool = Pool::connect(&database_url).await?;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,16 +40,16 @@ pub async fn menu_markup(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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();
 | 
			
		||||
    data.reserve(44);
 | 
			
		||||
    data.reserve(param.len() + 1);
 | 
			
		||||
    data.push(' ');
 | 
			
		||||
    data.push_str(hash);
 | 
			
		||||
    data.push_str(param);
 | 
			
		||||
    InlineKeyboardButton::callback(text, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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];
 | 
			
		||||
    B64_ENGINE
 | 
			
		||||
        .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 encryption_button = if is_encrypted {
 | 
			
		||||
        ("Decrypt", "decrypt")
 | 
			
		||||
        (locale.decrypt_button.as_ref(), "decrypt")
 | 
			
		||||
    } else {
 | 
			
		||||
        ("Hide", "get")
 | 
			
		||||
        (locale.hide_button.as_ref(), "get")
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let main_buttons = [
 | 
			
		||||
        ("Alter name", "an"),
 | 
			
		||||
        ("Alter login", "al"),
 | 
			
		||||
        ("Alter password", "ap"),
 | 
			
		||||
        (locale.change_name_button.as_ref(), "an"),
 | 
			
		||||
        (locale.change_login_button.as_ref(), "al"),
 | 
			
		||||
        (locale.change_password_button.as_ref(), "ap"),
 | 
			
		||||
        encryption_button,
 | 
			
		||||
        ("Delete account", "delete0"),
 | 
			
		||||
        (locale.delete_account_button.as_ref(), "delete0"),
 | 
			
		||||
    ]
 | 
			
		||||
    .into_iter()
 | 
			
		||||
    .map(|(text, command)| make_button(text, command, hash))
 | 
			
		||||
    .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])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[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.
 | 
			
		||||
/// This markup should be added for all messages that won't be deleted afterwards
 | 
			
		||||
#[inline]
 | 
			
		||||
pub fn deletion_markup() -> InlineKeyboardMarkup {
 | 
			
		||||
    let button = InlineKeyboardButton::callback("Delete message", "delete_message");
 | 
			
		||||
pub fn deletion_markup(locale: LocaleRef) -> InlineKeyboardMarkup {
 | 
			
		||||
    let button =
 | 
			
		||||
        InlineKeyboardButton::callback(locale.delete_message_button.as_ref(), "delete_message");
 | 
			
		||||
    InlineKeyboardMarkup::new([[button]])
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ async fn master_pass_exists(update: Update, db: Pool) -> Option<Option<DynError>
 | 
			
		||||
#[inline]
 | 
			
		||||
async fn notify_about_no_master_pass(
 | 
			
		||||
    bot: Throttle<Bot>,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    result: Option<DynError>,
 | 
			
		||||
    update: Update,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
@@ -37,9 +38,9 @@ async fn notify_about_no_master_pass(
 | 
			
		||||
    }
 | 
			
		||||
    bot.send_message(
 | 
			
		||||
        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?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ pub use crate::{
 | 
			
		||||
    commands::Command,
 | 
			
		||||
    errors::*,
 | 
			
		||||
    first_handler, handler,
 | 
			
		||||
    locales::{Locale, LocaleRef},
 | 
			
		||||
    markups::*,
 | 
			
		||||
    models::*,
 | 
			
		||||
    state::{Handler, MainDialogue, MessageIds, PackagedHandler, State},
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,16 @@ pub async fn generic<F>(
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    check: F,
 | 
			
		||||
    no_text_message: impl Into<String> + Send,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    handler: PackagedHandler<String>,
 | 
			
		||||
) -> crate::Result<()>
 | 
			
		||||
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,
 | 
			
		||||
{
 | 
			
		||||
    let mut handler = handler.lock().await;
 | 
			
		||||
@@ -25,7 +30,7 @@ where
 | 
			
		||||
    let Some(text) = msg.text().map(str::trim) else {
 | 
			
		||||
        handler
 | 
			
		||||
            .previous
 | 
			
		||||
            .alter_message(&bot, no_text_message, None, None)
 | 
			
		||||
            .alter_message(&bot, locale.couldnt_get_message_text.as_ref(), None, None)
 | 
			
		||||
            .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    };
 | 
			
		||||
@@ -34,12 +39,17 @@ where
 | 
			
		||||
        dialogue.exit().await?;
 | 
			
		||||
        handler
 | 
			
		||||
            .previous
 | 
			
		||||
            .alter_message(&bot, "Successfully cancelled", deletion_markup(), None)
 | 
			
		||||
            .alter_message(
 | 
			
		||||
                &bot,
 | 
			
		||||
                locale.successfully_canceled.as_ref(),
 | 
			
		||||
                deletion_markup(locale),
 | 
			
		||||
                None,
 | 
			
		||||
            )
 | 
			
		||||
            .await?;
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(text) = check(&msg, &db, text).await? {
 | 
			
		||||
    if let Some(text) = check(&msg, &db, locale, text).await? {
 | 
			
		||||
        handler
 | 
			
		||||
            .previous
 | 
			
		||||
            .alter_message(&bot, text, None, None)
 | 
			
		||||
@@ -52,7 +62,7 @@ where
 | 
			
		||||
    drop(handler);
 | 
			
		||||
    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;
 | 
			
		||||
        return Err(err);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,17 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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);
 | 
			
		||||
    if !is_valid {
 | 
			
		||||
        return Ok(Some("Invalid login. Try again".to_owned()));
 | 
			
		||||
        return Ok(Some(locale.invalid_login.as_ref().into()));
 | 
			
		||||
    }
 | 
			
		||||
    Ok(None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
crate::simple_state_handler!(
 | 
			
		||||
    get_login,
 | 
			
		||||
    check_login,
 | 
			
		||||
    "Couldn't get the text of the message. Send the login again"
 | 
			
		||||
);
 | 
			
		||||
crate::simple_state_handler!(get_login, check_login);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,13 @@ use tokio::task::spawn_blocking;
 | 
			
		||||
async fn check_master_pass(
 | 
			
		||||
    msg: &Message,
 | 
			
		||||
    db: &Pool,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    master_pass: &str,
 | 
			
		||||
) -> crate::Result<Option<String>> {
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
    let Some(model) = MasterPass::get(user_id, db).await? else {
 | 
			
		||||
        error!("User was put into the GetMasterPass state with no master password set");
 | 
			
		||||
        return Ok(Some(
 | 
			
		||||
            "No master password set. Use /cancel and set it by using /set_master_pass".to_owned(),
 | 
			
		||||
        ));
 | 
			
		||||
        return Ok(Some(locale.master_password_is_not_set.as_ref().into()));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let is_valid = {
 | 
			
		||||
@@ -25,13 +24,9 @@ async fn check_master_pass(
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
crate::simple_state_handler!(
 | 
			
		||||
    get_master_pass,
 | 
			
		||||
    check_master_pass,
 | 
			
		||||
    "Couldn't get the text of the message. Send the master password again"
 | 
			
		||||
);
 | 
			
		||||
crate::simple_state_handler!(get_master_pass, check_master_pass);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +1,32 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
use cryptography::passwords::{check_master_pass, PasswordValidity};
 | 
			
		||||
use std::fmt::Write as _;
 | 
			
		||||
 | 
			
		||||
#[inline]
 | 
			
		||||
fn process_validity(validity: PasswordValidity) -> Result<(), String> {
 | 
			
		||||
fn process_validity(validity: PasswordValidity, locale: LocaleRef) -> Result<(), String> {
 | 
			
		||||
    if validity.is_empty() {
 | 
			
		||||
        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) {
 | 
			
		||||
        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) {
 | 
			
		||||
        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) {
 | 
			
		||||
        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) {
 | 
			
		||||
        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) {
 | 
			
		||||
        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)
 | 
			
		||||
}
 | 
			
		||||
@@ -35,15 +36,12 @@ fn process_validity(validity: PasswordValidity) -> Result<(), String> {
 | 
			
		||||
async fn check_new_master_pass(
 | 
			
		||||
    _: &Message,
 | 
			
		||||
    _: &Pool,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    password: &str,
 | 
			
		||||
) -> crate::Result<Option<String>> {
 | 
			
		||||
    let validity = check_master_pass(password);
 | 
			
		||||
 | 
			
		||||
    Ok(process_validity(validity).err())
 | 
			
		||||
    Ok(process_validity(validity, locale).err())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
crate::simple_state_handler!(
 | 
			
		||||
    get_new_master_pass,
 | 
			
		||||
    check_new_master_pass,
 | 
			
		||||
    "Couldn't get the text of the message. Send the master password again"
 | 
			
		||||
);
 | 
			
		||||
crate::simple_state_handler!(get_new_master_pass, check_new_master_pass);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,21 +5,18 @@ use crate::prelude::*;
 | 
			
		||||
async fn check_new_account_name(
 | 
			
		||||
    msg: &Message,
 | 
			
		||||
    db: &Pool,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    name: &str,
 | 
			
		||||
) -> crate::Result<Option<String>> {
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        Ok(Some("Invalid account name. Try again".to_owned()))
 | 
			
		||||
        Ok(Some(locale.invalid_name.as_ref().into()))
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
crate::simple_state_handler!(
 | 
			
		||||
    get_new_name,
 | 
			
		||||
    check_new_account_name,
 | 
			
		||||
    "Couldn't get the text of the message. Send the name of the new account again"
 | 
			
		||||
);
 | 
			
		||||
crate::simple_state_handler!(get_new_name, check_new_account_name);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,17 @@
 | 
			
		||||
use crate::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[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);
 | 
			
		||||
    if !is_valid {
 | 
			
		||||
        return Ok(Some("Invalid password. Try again".to_owned()));
 | 
			
		||||
        return Ok(Some(locale.invalid_password.as_ref().into()));
 | 
			
		||||
    }
 | 
			
		||||
    Ok(None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
crate::simple_state_handler!(
 | 
			
		||||
    get_password,
 | 
			
		||||
    check_password,
 | 
			
		||||
    "Couldn't get the text of the message. Send the password again"
 | 
			
		||||
);
 | 
			
		||||
crate::simple_state_handler!(get_password, check_password);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,24 +9,43 @@ use teloxide::{
 | 
			
		||||
use tokio::{task::spawn_blocking, try_join};
 | 
			
		||||
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]
 | 
			
		||||
fn validate_document(document: Option<&Document>) -> Result<&Document, &'static str> {
 | 
			
		||||
fn validate_document(document: Option<&Document>) -> Result<&Document, InvalidDocument> {
 | 
			
		||||
    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 {
 | 
			
		||||
        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() {
 | 
			
		||||
        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() {
 | 
			
		||||
        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(
 | 
			
		||||
    accounts: &mut [DecryptedAccount],
 | 
			
		||||
    existing_names: ahash::HashSet<String>,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
) -> crate::Result<Result<(), String>> {
 | 
			
		||||
    for account in accounts.iter_mut() {
 | 
			
		||||
        account.name.trim_in_place();
 | 
			
		||||
@@ -82,12 +102,13 @@ fn process_accounts(
 | 
			
		||||
        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() {
 | 
			
		||||
        write!(
 | 
			
		||||
            error_text,
 | 
			
		||||
            "\n\nDuplicate names:\n{:?}",
 | 
			
		||||
            "\n\n{}:\n{:?}",
 | 
			
		||||
            locale.duplicate_names,
 | 
			
		||||
            duplicates.into_iter().format("\n")
 | 
			
		||||
        )?;
 | 
			
		||||
    }
 | 
			
		||||
@@ -95,7 +116,8 @@ fn process_accounts(
 | 
			
		||||
    if !existing.is_empty() {
 | 
			
		||||
        write!(
 | 
			
		||||
            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")
 | 
			
		||||
        )?;
 | 
			
		||||
    }
 | 
			
		||||
@@ -103,12 +125,13 @@ fn process_accounts(
 | 
			
		||||
    if !invalid.is_empty() {
 | 
			
		||||
        write!(
 | 
			
		||||
            error_text,
 | 
			
		||||
            "\n\nInvalid account fields:\n{:?}",
 | 
			
		||||
            "\n\n{}:\n{:?}",
 | 
			
		||||
            locale.invalid_fields,
 | 
			
		||||
            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))
 | 
			
		||||
}
 | 
			
		||||
@@ -117,10 +140,11 @@ fn process_accounts(
 | 
			
		||||
fn user_from_bytes(
 | 
			
		||||
    bytes: impl AsRef<[u8]>,
 | 
			
		||||
    existing_names: ahash::HashSet<String>,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
) -> crate::Result<Result<User, String>> {
 | 
			
		||||
    let mut user: User = serde_json::from_slice(bytes.as_ref())?;
 | 
			
		||||
    drop(bytes);
 | 
			
		||||
    match process_accounts(&mut user.accounts, existing_names)? {
 | 
			
		||||
    match process_accounts(&mut user.accounts, existing_names, locale)? {
 | 
			
		||||
        Ok(()) => Ok(Ok(user)),
 | 
			
		||||
        Err(error_text) => Ok(Err(error_text)),
 | 
			
		||||
    }
 | 
			
		||||
@@ -132,21 +156,24 @@ async fn user_from_document(
 | 
			
		||||
    db: &Pool,
 | 
			
		||||
    document: Option<&Document>,
 | 
			
		||||
    user_id: u64,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
) -> Result<User, Cow<'static, str>> {
 | 
			
		||||
    let (data, existing_names) = {
 | 
			
		||||
        let file = &validate_document(document)?.file;
 | 
			
		||||
        let data = download_file(bot, file).map_err(|_| "Error downloading the file. Try again");
 | 
			
		||||
        let file = &validate_document(document)
 | 
			
		||||
            .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)
 | 
			
		||||
            .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)?
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    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),
 | 
			
		||||
        _ => 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,
 | 
			
		||||
    db: Pool,
 | 
			
		||||
    dialogue: MainDialogue,
 | 
			
		||||
    locale: LocaleRef,
 | 
			
		||||
    handler: PackagedHandler<User>,
 | 
			
		||||
) -> crate::Result<()> {
 | 
			
		||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
			
		||||
@@ -171,12 +199,17 @@ pub async fn get_user(
 | 
			
		||||
        dialogue.exit().await?;
 | 
			
		||||
        handler
 | 
			
		||||
            .previous
 | 
			
		||||
            .alter_message(&bot, "Successfully cancelled", deletion_markup(), None)
 | 
			
		||||
            .alter_message(
 | 
			
		||||
                &bot,
 | 
			
		||||
                locale.successfully_canceled.as_ref(),
 | 
			
		||||
                deletion_markup(locale),
 | 
			
		||||
                None,
 | 
			
		||||
            )
 | 
			
		||||
            .await?;
 | 
			
		||||
        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,
 | 
			
		||||
        Err(error_text) => {
 | 
			
		||||
            handler
 | 
			
		||||
@@ -191,7 +224,7 @@ pub async fn get_user(
 | 
			
		||||
    let func = handler.func.take().unwrap();
 | 
			
		||||
    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;
 | 
			
		||||
        return Err(err);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,7 @@ type DynHanlder<T> = Box<
 | 
			
		||||
            Pool,
 | 
			
		||||
            MainDialogue,
 | 
			
		||||
            MessageIds,
 | 
			
		||||
            LocaleRef,
 | 
			
		||||
            T,
 | 
			
		||||
        ) -> BoxFuture<'static, crate::Result<()>>
 | 
			
		||||
        + Send,
 | 
			
		||||
@@ -92,6 +93,7 @@ impl<T> Handler<T> {
 | 
			
		||||
                Pool,
 | 
			
		||||
                MainDialogue,
 | 
			
		||||
                MessageIds,
 | 
			
		||||
                LocaleRef,
 | 
			
		||||
                T,
 | 
			
		||||
            ) -> BoxFuture<'static, crate::Result<()>>
 | 
			
		||||
            + Send
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user