mirror of
https://github.com/soywod/himalaya.git
synced 2024-09-29 04:31:11 +00:00
make use of pimalaya-tui
This commit is contained in:
parent
c5b33b9623
commit
5a22cab781
137
Cargo.lock
generated
137
Cargo.lock
generated
|
@ -211,7 +211,7 @@ dependencies = [
|
||||||
"futures-lite 2.3.0",
|
"futures-lite 2.3.0",
|
||||||
"parking",
|
"parking",
|
||||||
"polling 3.7.3",
|
"polling 3.7.3",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"slab",
|
"slab",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
@ -250,7 +250,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"event-listener 3.1.0",
|
"event-listener 3.1.0",
|
||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"slab",
|
"slab",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
|
@ -560,9 +560,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.14"
|
version = "1.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
|
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -671,14 +671,14 @@ dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim 0.11.1",
|
"strsim 0.11.1",
|
||||||
"terminal_size 0.3.0",
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_complete"
|
name = "clap_complete"
|
||||||
version = "4.5.23"
|
version = "4.5.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "531d7959c5bbb6e266cecdd0f20213639c3a5c3e4d615f97db87661745f781ff"
|
checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
]
|
]
|
||||||
|
@ -774,19 +774,6 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console"
|
|
||||||
version = "0.15.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
|
||||||
dependencies = [
|
|
||||||
"encode_unicode",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"unicode-width",
|
|
||||||
"windows-sys 0.52.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -1309,12 +1296,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encode_unicode"
|
|
||||||
version = "0.3.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
|
@ -1463,9 +1444,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.24"
|
version = "0.2.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550"
|
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1910,13 +1891,11 @@ dependencies = [
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
"console",
|
|
||||||
"crossterm 0.27.0",
|
"crossterm 0.27.0",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"email-lib",
|
"email-lib",
|
||||||
"email_address",
|
"email_address",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
"indicatif",
|
|
||||||
"inquire",
|
"inquire",
|
||||||
"mail-builder",
|
"mail-builder",
|
||||||
"md5",
|
"md5",
|
||||||
|
@ -1924,6 +1903,7 @@ dependencies = [
|
||||||
"oauth-lib",
|
"oauth-lib",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
|
"pimalaya-tui",
|
||||||
"process-lib",
|
"process-lib",
|
||||||
"secret-lib",
|
"secret-lib",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1931,14 +1911,12 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shellexpand-utils",
|
"shellexpand-utils",
|
||||||
"sled",
|
"sled",
|
||||||
"terminal_size 0.1.17",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"toml_edit 0.22.20",
|
"toml_edit 0.22.20",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-width",
|
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -2121,7 +2099,7 @@ dependencies = [
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"log",
|
"log",
|
||||||
"rustls 0.23.12",
|
"rustls 0.23.12",
|
||||||
"rustls-native-certs 0.7.2",
|
"rustls-native-certs 0.7.3",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.0",
|
"tokio-rustls 0.26.0",
|
||||||
|
@ -2213,7 +2191,7 @@ source = "git+https://github.com/pimalaya/imap-client#02d6bce5513c8ec6ac3aff0e7b
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"imap-next",
|
"imap-next",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-native-certs 0.7.2",
|
"rustls-native-certs 0.7.3",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.26.0",
|
"tokio-rustls 0.26.0",
|
||||||
|
@ -2223,7 +2201,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-codec"
|
name = "imap-codec"
|
||||||
version = "2.0.0-alpha.4"
|
version = "2.0.0-alpha.4"
|
||||||
source = "git+https://github.com/duesee/imap-codec#fff8355ad0f7133be9e58919be5a6f05f684d421"
|
source = "git+https://github.com/duesee/imap-codec#95de04494f89464a59c114859217e6119a18d426"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"abnf-core",
|
"abnf-core",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -2250,7 +2228,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-types"
|
name = "imap-types"
|
||||||
version = "2.0.0-alpha.3"
|
version = "2.0.0-alpha.3"
|
||||||
source = "git+https://github.com/duesee/imap-codec#fff8355ad0f7133be9e58919be5a6f05f684d421"
|
source = "git+https://github.com/duesee/imap-codec#95de04494f89464a59c114859217e6119a18d426"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bounded-static",
|
"bounded-static",
|
||||||
|
@ -2276,19 +2254,6 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indicatif"
|
|
||||||
version = "0.17.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
|
|
||||||
dependencies = [
|
|
||||||
"console",
|
|
||||||
"instant",
|
|
||||||
"number_prefix",
|
|
||||||
"portable-atomic",
|
|
||||||
"unicode-width",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -2938,12 +2903,6 @@ dependencies = [
|
||||||
"libm",
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "number_prefix"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oauth-lib"
|
name = "oauth-lib"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -3240,6 +3199,22 @@ dependencies = [
|
||||||
"z-base-32",
|
"z-base-32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pimalaya-tui"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/pimalaya/tui#80660dfaf9daafbaa716c711e510bf3cfd04cd69"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm 0.25.0",
|
||||||
|
"dirs 4.0.0",
|
||||||
|
"email-lib",
|
||||||
|
"email_address",
|
||||||
|
"inquire",
|
||||||
|
"oauth-lib",
|
||||||
|
"secret-lib",
|
||||||
|
"shellexpand-utils",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.5"
|
version = "1.1.5"
|
||||||
|
@ -3336,17 +3311,11 @@ dependencies = [
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"hermit-abi 0.4.0",
|
"hermit-abi 0.4.0",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "portable-atomic"
|
|
||||||
version = "1.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
|
@ -3358,9 +3327,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.21"
|
version = "0.2.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a909e6e8053fa1a5ad670f5816c7d93029ee1fa8898718490544a6b0d5d38b3e"
|
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.76",
|
"syn 2.0.76",
|
||||||
|
@ -3710,9 +3679,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
@ -3733,9 +3702,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.34"
|
version = "0.38.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -3766,7 +3735,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-webpki 0.102.6",
|
"rustls-webpki 0.102.7",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
@ -3785,9 +3754,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-native-certs"
|
name = "rustls-native-certs"
|
||||||
version = "0.7.2"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04182dffc9091a404e0fc069ea5cd60e5b866c3adf881eff99a32d048242dffa"
|
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pemfile 2.1.3",
|
"rustls-pemfile 2.1.3",
|
||||||
|
@ -3833,9 +3802,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.102.6"
|
version = "0.102.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
|
checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"ring",
|
"ring",
|
||||||
|
@ -4367,27 +4336,17 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand 2.1.1",
|
"fastrand 2.1.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "terminal_size"
|
|
||||||
version = "0.1.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -4438,9 +4397,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.39.3"
|
version = "1.40.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
|
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -4921,7 +4880,7 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"home",
|
"home",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 0.38.34",
|
"rustix 0.38.35",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -23,24 +23,24 @@ default = [
|
||||||
"smtp",
|
"smtp",
|
||||||
"sendmail",
|
"sendmail",
|
||||||
|
|
||||||
"wizard",
|
|
||||||
# "keyring",
|
# "keyring",
|
||||||
# "oauth2",
|
# "oauth2",
|
||||||
|
"wizard",
|
||||||
|
|
||||||
# "pgp-commands",
|
# "pgp-commands",
|
||||||
# "pgp-gpg",
|
# "pgp-gpg",
|
||||||
# "pgp-native",
|
# "pgp-native",
|
||||||
]
|
]
|
||||||
|
|
||||||
imap = ["email-lib/imap"]
|
imap = ["email-lib/imap", "pimalaya-tui/imap"]
|
||||||
maildir = ["email-lib/maildir"]
|
maildir = ["email-lib/maildir", "pimalaya-tui/maildir"]
|
||||||
notmuch = ["email-lib/notmuch"]
|
notmuch = ["email-lib/notmuch", "pimalaya-tui/notmuch"]
|
||||||
smtp = ["email-lib/smtp"]
|
smtp = ["email-lib/smtp", "pimalaya-tui/smtp"]
|
||||||
sendmail = ["email-lib/sendmail"]
|
sendmail = ["email-lib/sendmail", "pimalaya-tui/sendmail"]
|
||||||
|
|
||||||
keyring = ["email-lib/keyring", "secret-lib?/keyring-tokio"]
|
keyring = ["email-lib/keyring", "pimalaya-tui/keyring", "secret-lib?/keyring-tokio"]
|
||||||
oauth2 = ["dep:oauth-lib", "email-lib/oauth2", "keyring"]
|
oauth2 = ["dep:oauth-lib", "email-lib/oauth2", "pimalaya-tui/oauth2", "keyring"]
|
||||||
wizard = ["dep:secret-lib", "dep:toml_edit", "email-lib/autoconfig"]
|
wizard = ["dep:email_address", "dep:secret-lib", "dep:toml_edit", "email-lib/autoconfig"]
|
||||||
|
|
||||||
pgp = []
|
pgp = []
|
||||||
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"]
|
pgp-commands = ["email-lib/pgp-commands", "mml-lib/pgp-commands", "pgp"]
|
||||||
|
@ -55,13 +55,11 @@ clap_complete = "4.4"
|
||||||
clap_mangen = "0.2"
|
clap_mangen = "0.2"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
comfy-table = "7.1.1"
|
comfy-table = "7.1.1"
|
||||||
console = "0.15.2"
|
|
||||||
crossterm = { version = "0.27", features = ["serde"] }
|
crossterm = { version = "0.27", features = ["serde"] }
|
||||||
dirs = "4"
|
dirs = "4"
|
||||||
email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] }
|
email-lib = { version = "=0.25.0", default-features = false, features = ["derive", "thread", "tracing"] }
|
||||||
email_address = "0.2.4"
|
email_address = { version = "0.2", optional = true }
|
||||||
erased-serde = "0.3"
|
erased-serde = "0.3"
|
||||||
indicatif = "0.17"
|
|
||||||
inquire = "0.7.4"
|
inquire = "0.7.4"
|
||||||
mail-builder = "0.3"
|
mail-builder = "0.3"
|
||||||
md5 = "0.7"
|
md5 = "0.7"
|
||||||
|
@ -69,6 +67,7 @@ mml-lib = { version = "=1.0.14", default-features = false, features = ["derive"]
|
||||||
oauth-lib = { version = "=0.1.1", optional = true }
|
oauth-lib = { version = "=0.1.1", optional = true }
|
||||||
once_cell = "1.16"
|
once_cell = "1.16"
|
||||||
petgraph = "0.6"
|
petgraph = "0.6"
|
||||||
|
pimalaya-tui = { version = "=0.1.0", default-features = false, features = ["email", "path"] }
|
||||||
process-lib = { version = "=0.4.2", features = ["derive"] }
|
process-lib = { version = "=0.4.2", features = ["derive"] }
|
||||||
secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true }
|
secret-lib = { version = "=0.4.6", default-features = false, features = ["command", "derive"], optional = true }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -76,20 +75,19 @@ serde-toml-merge = "0.3"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
shellexpand-utils = "=0.2.1"
|
shellexpand-utils = "=0.2.1"
|
||||||
sled = "=0.34.7"
|
sled = "=0.34.7"
|
||||||
terminal_size = "0.1"
|
|
||||||
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.23", default-features = false, features = ["macros", "rt-multi-thread"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
toml_edit = { version = "0.22", optional = true }
|
toml_edit = { version = "0.22", optional = true }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
unicode-width = "0.1"
|
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
oauth-lib = { git = "https://github.com/pimalaya/core" }
|
|
||||||
imap-codec = { git = "https://github.com/duesee/imap-codec" }
|
imap-codec = { git = "https://github.com/duesee/imap-codec" }
|
||||||
imap-next = { git = "https://github.com/duesee/imap-next", branch = "jakoschiko_poison-message-with-fragmentizer" }
|
imap-next = { git = "https://github.com/duesee/imap-next", branch = "jakoschiko_poison-message-with-fragmentizer" }
|
||||||
imap-client = { git = "https://github.com/pimalaya/imap-client" }
|
imap-client = { git = "https://github.com/pimalaya/imap-client" }
|
||||||
|
oauth-lib = { git = "https://github.com/pimalaya/core" }
|
||||||
email-lib = { git = "https://github.com/pimalaya/core" }
|
email-lib = { git = "https://github.com/pimalaya/core" }
|
||||||
|
pimalaya-tui = { git = "https://github.com/pimalaya/tui" }
|
||||||
|
|
|
@ -4,12 +4,12 @@ use color_eyre::Result;
|
||||||
use email::imap::config::ImapAuthConfig;
|
use email::imap::config::ImapAuthConfig;
|
||||||
#[cfg(feature = "smtp")]
|
#[cfg(feature = "smtp")]
|
||||||
use email::smtp::config::SmtpAuthConfig;
|
use email::smtp::config::SmtpAuthConfig;
|
||||||
|
#[cfg(any(feature = "imap", feature = "smtp", feature = "pgp"))]
|
||||||
|
use pimalaya_tui::prompt;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
#[cfg(any(feature = "imap", feature = "smtp"))]
|
#[cfg(any(feature = "imap", feature = "smtp"))]
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
#[cfg(any(feature = "imap", feature = "smtp", feature = "pgp"))]
|
|
||||||
use crate::ui::prompt;
|
|
||||||
use crate::{account::arg::name::AccountNameArg, config::TomlConfig, printer::Printer};
|
use crate::{account::arg::name::AccountNameArg, config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
/// Configure an account.
|
/// Configure an account.
|
||||||
|
@ -74,12 +74,14 @@ impl AccountConfigureCommand {
|
||||||
if let Some(ref config) = account_config.imap {
|
if let Some(ref config) = account_config.imap {
|
||||||
match &config.auth {
|
match &config.auth {
|
||||||
ImapAuthConfig::Passwd(config) => {
|
ImapAuthConfig::Passwd(config) => {
|
||||||
config.configure(|| prompt::passwd("IMAP password")).await
|
config
|
||||||
|
.configure(|| Ok(prompt::password("IMAP password")?))
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
#[cfg(feature = "oauth2")]
|
#[cfg(feature = "oauth2")]
|
||||||
ImapAuthConfig::OAuth2(config) => {
|
ImapAuthConfig::OAuth2(config) => {
|
||||||
config
|
config
|
||||||
.configure(|| prompt::secret("IMAP OAuth 2.0 client secret"))
|
.configure(|| Ok(prompt::secret("IMAP OAuth 2.0 clientsecret")?))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
|
@ -89,12 +91,14 @@ impl AccountConfigureCommand {
|
||||||
if let Some(ref config) = account_config.smtp {
|
if let Some(ref config) = account_config.smtp {
|
||||||
match &config.auth {
|
match &config.auth {
|
||||||
SmtpAuthConfig::Passwd(config) => {
|
SmtpAuthConfig::Passwd(config) => {
|
||||||
config.configure(|| prompt::passwd("SMTP password")).await
|
config
|
||||||
|
.configure(|| Ok(prompt::password("SMTP password")?))
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
#[cfg(feature = "oauth2")]
|
#[cfg(feature = "oauth2")]
|
||||||
SmtpAuthConfig::OAuth2(config) => {
|
SmtpAuthConfig::OAuth2(config) => {
|
||||||
config
|
config
|
||||||
.configure(|| prompt::secret("SMTP OAuth 2.0 client secret"))
|
.configure(|| Ok(prompt::secret("SMTP OAuth 2.0 client secret")?))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
|
@ -104,7 +108,7 @@ impl AccountConfigureCommand {
|
||||||
if let Some(ref config) = account_config.pgp {
|
if let Some(ref config) = account_config.pgp {
|
||||||
config
|
config
|
||||||
.configure(&account_config.email, || {
|
.configure(&account_config.email, || {
|
||||||
prompt::passwd("PGP secret key password")
|
Ok(prompt::password("PGP secret key password")?)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use color_eyre::{eyre::OptionExt, Result};
|
use color_eyre::Result;
|
||||||
use email_address::EmailAddress;
|
use pimalaya_tui::{print, prompt};
|
||||||
use inquire::validator::{ErrorMessage, Validation};
|
|
||||||
use std::{path::PathBuf, str::FromStr};
|
|
||||||
|
|
||||||
use crate::wizard_warn;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{self, config::BackendConfig, BackendKind},
|
backend::{self, config::BackendConfig, BackendKind},
|
||||||
message::config::{MessageConfig, MessageSendConfig},
|
message::config::{MessageConfig, MessageSendConfig},
|
||||||
|
@ -11,104 +8,66 @@ use crate::{
|
||||||
|
|
||||||
use super::TomlAccountConfig;
|
use super::TomlAccountConfig;
|
||||||
|
|
||||||
pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
pub async fn configure() -> Result<(String, TomlAccountConfig)> {
|
||||||
let mut config = TomlAccountConfig {
|
let email = prompt::email("Email address:", None)?;
|
||||||
email: inquire::Text::new("Email address: ")
|
|
||||||
.with_validator(|email: &_| {
|
|
||||||
if EmailAddress::is_valid(email) {
|
|
||||||
Ok(Validation::Valid)
|
|
||||||
} else {
|
|
||||||
Ok(Validation::Invalid(ErrorMessage::Custom(format!(
|
|
||||||
"Invalid email address: {email}"
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.prompt()?,
|
|
||||||
|
|
||||||
|
let mut config = TomlAccountConfig {
|
||||||
|
email: email.to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let addr = EmailAddress::from_str(&config.email).unwrap();
|
|
||||||
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
let autoconfig_email = config.email.to_owned();
|
let autoconfig_email = config.email.to_owned();
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
let autoconfig =
|
let autoconfig =
|
||||||
tokio::spawn(async move { email::autoconfig::from_addr(&autoconfig_email).await.ok() });
|
tokio::spawn(async move { email::autoconfig::from_addr(&autoconfig_email).await.ok() });
|
||||||
|
|
||||||
let account_name = inquire::Text::new("Account name: ")
|
let default_account_name = email
|
||||||
.with_default(
|
.domain()
|
||||||
addr.domain()
|
|
||||||
.split_once('.')
|
.split_once('.')
|
||||||
.ok_or_eyre("not a valid domain, without any .")?
|
.map(|domain| domain.0)
|
||||||
.0,
|
.unwrap_or(email.domain());
|
||||||
)
|
let account_name = prompt::text("Account name:", Some(default_account_name))?;
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
config.display_name = Some(
|
config.display_name = Some(prompt::text(
|
||||||
inquire::Text::new("Full display name: ")
|
"Full display name:",
|
||||||
.with_default(addr.local_part())
|
Some(email.local_part()),
|
||||||
.prompt()?,
|
)?);
|
||||||
);
|
|
||||||
|
|
||||||
config.downloads_dir = Some(PathBuf::from(
|
config.downloads_dir = Some(prompt::path("Downloads directory:", Some("~/Downloads"))?);
|
||||||
inquire::Text::new("Downloads directory: ")
|
|
||||||
.with_default("~/Downloads")
|
|
||||||
.prompt()?,
|
|
||||||
));
|
|
||||||
|
|
||||||
let email = &config.email;
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
let autoconfig = autoconfig.await?;
|
let autoconfig = autoconfig.await?;
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
let autoconfig = autoconfig.as_ref();
|
let autoconfig = autoconfig.as_ref();
|
||||||
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
if let Some(config) = autoconfig {
|
if let Some(config) = autoconfig {
|
||||||
if config.is_gmail() {
|
if config.is_gmail() {
|
||||||
println!();
|
println!();
|
||||||
wizard_warn!("Warning: Google passwords cannot be used directly, see:");
|
print::warn("Warning: Google passwords cannot be used directly, see:");
|
||||||
wizard_warn!("https://pimalaya.org/himalaya/cli/latest/configuration/gmail.html");
|
print::warn("https://github.com/pimalaya/himalaya?tab=readme-ov-file#configuration");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match backend::wizard::configure(
|
match backend::wizard::configure(&account_name, &email, autoconfig).await? {
|
||||||
&account_name,
|
|
||||||
email,
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
autoconfig,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
Some(BackendConfig::Imap(imap_config)) => {
|
BackendConfig::Imap(imap_config) => {
|
||||||
config.imap = Some(imap_config);
|
config.imap = Some(imap_config);
|
||||||
config.backend = Some(BackendKind::Imap);
|
config.backend = Some(BackendKind::Imap);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "maildir")]
|
#[cfg(feature = "maildir")]
|
||||||
Some(BackendConfig::Maildir(mdir_config)) => {
|
BackendConfig::Maildir(mdir_config) => {
|
||||||
config.maildir = Some(mdir_config);
|
config.maildir = Some(mdir_config);
|
||||||
config.backend = Some(BackendKind::Maildir);
|
config.backend = Some(BackendKind::Maildir);
|
||||||
}
|
}
|
||||||
#[cfg(feature = "notmuch")]
|
#[cfg(feature = "notmuch")]
|
||||||
Some(BackendConfig::Notmuch(notmuch_config)) => {
|
BackendConfig::Notmuch(notmuch_config) => {
|
||||||
config.notmuch = Some(notmuch_config);
|
config.notmuch = Some(notmuch_config);
|
||||||
config.backend = Some(BackendKind::Notmuch);
|
config.backend = Some(BackendKind::Notmuch);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match backend::wizard::configure_sender(
|
match backend::wizard::configure_sender(&account_name, &email, autoconfig).await? {
|
||||||
&account_name,
|
|
||||||
email,
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
autoconfig,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
#[cfg(feature = "smtp")]
|
#[cfg(feature = "smtp")]
|
||||||
Some(BackendConfig::Smtp(smtp_config)) => {
|
BackendConfig::Smtp(smtp_config) => {
|
||||||
config.smtp = Some(smtp_config);
|
config.smtp = Some(smtp_config);
|
||||||
config.message = Some(MessageConfig {
|
config.message = Some(MessageConfig {
|
||||||
send: Some(MessageSendConfig {
|
send: Some(MessageSendConfig {
|
||||||
|
@ -119,7 +78,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[cfg(feature = "sendmail")]
|
#[cfg(feature = "sendmail")]
|
||||||
Some(BackendConfig::Sendmail(sendmail_config)) => {
|
BackendConfig::Sendmail(sendmail_config) => {
|
||||||
config.sendmail = Some(sendmail_config);
|
config.sendmail = Some(sendmail_config);
|
||||||
config.message = Some(MessageConfig {
|
config.message = Some(MessageConfig {
|
||||||
send: Some(MessageSendConfig {
|
send: Some(MessageSendConfig {
|
||||||
|
@ -129,8 +88,8 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some((account_name, config)))
|
Ok((account_name, config))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use email::autoconfig::config::AutoConfig;
|
use email::autoconfig::config::AutoConfig;
|
||||||
use inquire::Select;
|
use email_address::EmailAddress;
|
||||||
|
use pimalaya_tui::{prompt, wizard};
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
use crate::imap;
|
|
||||||
#[cfg(feature = "maildir")]
|
|
||||||
use crate::maildir;
|
|
||||||
#[cfg(feature = "notmuch")]
|
|
||||||
use crate::notmuch;
|
|
||||||
#[cfg(feature = "sendmail")]
|
|
||||||
use crate::sendmail;
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
use crate::smtp;
|
|
||||||
|
|
||||||
use super::{config::BackendConfig, BackendKind};
|
use super::{config::BackendConfig, BackendKind};
|
||||||
|
|
||||||
|
@ -24,6 +14,34 @@ const DEFAULT_BACKEND_KINDS: &[BackendKind] = &[
|
||||||
BackendKind::Notmuch,
|
BackendKind::Notmuch,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub async fn configure(
|
||||||
|
account_name: &str,
|
||||||
|
email: &EmailAddress,
|
||||||
|
autoconfig: Option<&AutoConfig>,
|
||||||
|
) -> Result<BackendConfig> {
|
||||||
|
let backend = prompt::item("Default backend:", &*DEFAULT_BACKEND_KINDS, None)?;
|
||||||
|
|
||||||
|
match backend {
|
||||||
|
#[cfg(feature = "imap")]
|
||||||
|
BackendKind::Imap => {
|
||||||
|
let config = wizard::imap::start(account_name, email, autoconfig).await?;
|
||||||
|
Ok(BackendConfig::Imap(config))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "maildir")]
|
||||||
|
BackendKind::Maildir => {
|
||||||
|
let config = wizard::maildir::start(account_name)?;
|
||||||
|
Ok(BackendConfig::Maildir(config))
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// #[cfg(feature = "notmuch")]
|
||||||
|
// BackendKind::Notmuch => {
|
||||||
|
// let config = wizard::notmuch::start()?;
|
||||||
|
// Ok(BackendConfig::Notmuch(config))
|
||||||
|
// }
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
||||||
#[cfg(feature = "smtp")]
|
#[cfg(feature = "smtp")]
|
||||||
BackendKind::Smtp,
|
BackendKind::Smtp,
|
||||||
|
@ -31,63 +49,30 @@ const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
|
||||||
BackendKind::Sendmail,
|
BackendKind::Sendmail,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) async fn configure(
|
pub async fn configure_sender(
|
||||||
account_name: &str,
|
account_name: &str,
|
||||||
email: &str,
|
email: &EmailAddress,
|
||||||
autoconfig: Option<&AutoConfig>,
|
autoconfig: Option<&AutoConfig>,
|
||||||
) -> Result<Option<BackendConfig>> {
|
) -> Result<BackendConfig> {
|
||||||
let kind = Select::new("Default email backend", DEFAULT_BACKEND_KINDS.to_vec())
|
let backend = prompt::item(
|
||||||
.with_starting_cursor(0)
|
"Backend for sending messages:",
|
||||||
.prompt_skippable()?;
|
&*SEND_MESSAGE_BACKEND_KINDS,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
let config = match kind {
|
match backend {
|
||||||
#[cfg(feature = "imap")]
|
// TODO
|
||||||
Some(kind) if kind == BackendKind::Imap => Some(
|
|
||||||
imap::wizard::configure(
|
|
||||||
account_name,
|
|
||||||
email,
|
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
autoconfig,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
),
|
|
||||||
#[cfg(feature = "maildir")]
|
|
||||||
Some(kind) if kind == BackendKind::Maildir => Some(maildir::wizard::configure()?),
|
|
||||||
#[cfg(feature = "notmuch")]
|
|
||||||
Some(kind) if kind == BackendKind::Notmuch => Some(notmuch::wizard::configure()?),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure_sender(
|
|
||||||
account_name: &str,
|
|
||||||
email: &str,
|
|
||||||
autoconfig: Option<&AutoConfig>,
|
|
||||||
) -> Result<Option<BackendConfig>> {
|
|
||||||
let kind = Select::new(
|
|
||||||
"Backend for sending messages",
|
|
||||||
SEND_MESSAGE_BACKEND_KINDS.to_vec(),
|
|
||||||
)
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
let config = match kind {
|
|
||||||
#[cfg(feature = "smtp")]
|
#[cfg(feature = "smtp")]
|
||||||
Some(kind) if kind == BackendKind::Smtp => Some(
|
BackendKind::Smtp => {
|
||||||
smtp::wizard::configure(
|
let config = wizard::smtp::start(account_name, email, autoconfig).await?;
|
||||||
account_name,
|
Ok(BackendConfig::Smtp(config))
|
||||||
email,
|
}
|
||||||
#[cfg(feature = "wizard")]
|
// TODO
|
||||||
autoconfig,
|
#[cfg(feature = "sendmail")]
|
||||||
)
|
BackendKind::Sendmail => {
|
||||||
.await?,
|
let config = wizard::sendmail::start()?;
|
||||||
),
|
Ok(BackendConfig::Sendmail(config))
|
||||||
#[cfg(feature = "sendmail")]
|
}
|
||||||
Some(kind) if kind == BackendKind::Sendmail => Some(sendmail::wizard::configure()?),
|
_ => unreachable!(),
|
||||||
_ => None,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#[cfg(feature = "wizard")]
|
#[cfg(feature = "wizard")]
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
|
||||||
|
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use color_eyre::{
|
use color_eyre::{
|
||||||
eyre::{bail, eyre, Context},
|
eyre::{bail, eyre, Context},
|
||||||
Result,
|
Result,
|
||||||
|
@ -11,16 +13,15 @@ use email::{
|
||||||
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
|
account::config::AccountConfig, config::Config, envelope::config::EnvelopeConfig,
|
||||||
folder::config::FolderConfig, message::config::MessageConfig,
|
folder::config::FolderConfig, message::config::MessageConfig,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "wizard")]
|
||||||
|
use pimalaya_tui::print;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_toml_merge::merge;
|
use serde_toml_merge::merge;
|
||||||
use shellexpand_utils::{canonicalize, expand};
|
use shellexpand_utils::{canonicalize, expand};
|
||||||
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
|
|
||||||
use toml::{self, Value};
|
use toml::{self, Value};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::account::config::{ListAccountsTableConfig, TomlAccountConfig};
|
use crate::account::config::{ListAccountsTableConfig, TomlAccountConfig};
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
use crate::wizard_warn;
|
|
||||||
|
|
||||||
/// Represents the user config file.
|
/// Represents the user config file.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
@ -121,7 +122,7 @@ impl TomlConfig {
|
||||||
/// NOTE: the wizard can only be used with interactive shells.
|
/// NOTE: the wizard can only be used with interactive shells.
|
||||||
#[cfg(feature = "wizard")]
|
#[cfg(feature = "wizard")]
|
||||||
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
async fn from_wizard(path: &PathBuf) -> Result<Self> {
|
||||||
wizard_warn!("Cannot find existing configuration at {path:?}.");
|
print::warn(format!("Cannot find existing configuration at {path:?}."));
|
||||||
|
|
||||||
let confirm = inquire::Confirm::new("Would you like to create one with the wizard? ")
|
let confirm = inquire::Confirm::new("Would you like to create one with the wizard? ")
|
||||||
.with_default(true)
|
.with_default(true)
|
||||||
|
|
|
@ -1,97 +1,29 @@
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use inquire::{Confirm, Select, Text};
|
use pimalaya_tui::{print, prompt};
|
||||||
use shellexpand_utils::expand;
|
use toml_edit::{DocumentMut, Table};
|
||||||
use std::{fs, path::Path, process};
|
|
||||||
use toml_edit::{DocumentMut, Item};
|
|
||||||
|
|
||||||
use crate::account;
|
use crate::account;
|
||||||
|
|
||||||
use super::TomlConfig;
|
use super::TomlConfig;
|
||||||
|
|
||||||
#[macro_export]
|
pub async fn configure(path: &PathBuf) -> Result<TomlConfig> {
|
||||||
macro_rules! wizard_warn {
|
print::section("Configuring your default account");
|
||||||
($($arg:tt)*) => {
|
|
||||||
println!("{}", console::style(format!($($arg)*)).yellow().bold());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! wizard_prompt {
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
format!("{}", console::style(format!($($arg)*)).italic())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! wizard_log {
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
println!();
|
|
||||||
println!("{}", console::style(format!($($arg)*)).underlined());
|
|
||||||
println!();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure(path: &Path) -> Result<TomlConfig> {
|
|
||||||
wizard_log!("Configuring your first account:");
|
|
||||||
|
|
||||||
let mut config = TomlConfig::default();
|
let mut config = TomlConfig::default();
|
||||||
|
|
||||||
while let Some((name, account_config)) = account::wizard::configure().await? {
|
let (account_name, account_config) = account::wizard::configure().await?;
|
||||||
config.accounts.insert(name, account_config);
|
config.accounts.insert(account_name, account_config);
|
||||||
|
|
||||||
if !Confirm::new("Would you like to configure another account?")
|
let path = prompt::path("Where to save the configuration?", Some(path))?;
|
||||||
.with_default(false)
|
println!("Writing the configuration to {}…", path.display());
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
wizard_log!("Configuring another account:");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If one account is setup, make it the default. If multiple
|
|
||||||
// accounts are setup, decide which will be the default. If no
|
|
||||||
// accounts are setup, exit the process.
|
|
||||||
let default_account = match config.accounts.len() {
|
|
||||||
0 => {
|
|
||||||
wizard_warn!("No account configured, exiting.");
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
1 => Some(config.accounts.values_mut().next().unwrap()),
|
|
||||||
_ => {
|
|
||||||
let accounts = config.accounts.clone();
|
|
||||||
let accounts: Vec<&String> = accounts.keys().collect();
|
|
||||||
|
|
||||||
println!("{} accounts have been configured.", accounts.len());
|
|
||||||
|
|
||||||
Select::new(
|
|
||||||
"Which account would you like to set as your default?",
|
|
||||||
accounts,
|
|
||||||
)
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.and_then(|input| config.accounts.get_mut(input))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(account) = default_account {
|
|
||||||
account.default = Some(true);
|
|
||||||
} else {
|
|
||||||
process::exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = Text::new("Where would you like to save your configuration?")
|
|
||||||
.with_default(&path.to_string_lossy())
|
|
||||||
.prompt()?;
|
|
||||||
let path = expand::path(path);
|
|
||||||
|
|
||||||
println!("Writing the configuration to {path:?}…");
|
|
||||||
let toml = pretty_serialize(&config)?;
|
let toml = pretty_serialize(&config)?;
|
||||||
fs::create_dir_all(path.parent().unwrap_or(&path))?;
|
fs::create_dir_all(path.parent().unwrap_or(&path))?;
|
||||||
fs::write(path, toml)?;
|
fs::write(path, toml)?;
|
||||||
|
|
||||||
println!("Exiting the wizard…");
|
println!("Done! Exiting the wizard…");
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,61 +31,11 @@ fn pretty_serialize(config: &TomlConfig) -> Result<String> {
|
||||||
let mut doc: DocumentMut = toml::to_string(&config)?.parse()?;
|
let mut doc: DocumentMut = toml::to_string(&config)?.parse()?;
|
||||||
|
|
||||||
doc.iter_mut().for_each(|(_, item)| {
|
doc.iter_mut().for_each(|(_, item)| {
|
||||||
if let Some(item) = item.as_table_mut() {
|
if let Some(table) = item.as_table_mut() {
|
||||||
item.iter_mut().for_each(|(_, item)| {
|
table.iter_mut().for_each(|(_, item)| {
|
||||||
set_table_dotted(item, "folder");
|
if let Some(table) = item.as_table_mut() {
|
||||||
if let Some(item) = get_table_mut(item, "folder") {
|
set_table_dotted(table);
|
||||||
let keys = ["alias", "add", "list", "expunge", "purge", "delete", "sync"];
|
|
||||||
set_tables_dotted(item, keys);
|
|
||||||
|
|
||||||
if let Some(item) = get_table_mut(item, "sync") {
|
|
||||||
set_tables_dotted(item, ["filter", "permissions"]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
set_table_dotted(item, "envelope");
|
|
||||||
if let Some(item) = get_table_mut(item, "envelope") {
|
|
||||||
set_tables_dotted(item, ["list", "get"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_table_dotted(item, "flag");
|
|
||||||
if let Some(item) = get_table_mut(item, "flag") {
|
|
||||||
set_tables_dotted(item, ["add", "set", "remove"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_table_dotted(item, "message");
|
|
||||||
if let Some(item) = get_table_mut(item, "message") {
|
|
||||||
let keys = ["add", "send", "peek", "get", "copy", "move", "delete"];
|
|
||||||
set_tables_dotted(item, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "maildir")]
|
|
||||||
set_table_dotted(item, "maildir");
|
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
{
|
|
||||||
set_table_dotted(item, "imap");
|
|
||||||
if let Some(item) = get_table_mut(item, "imap") {
|
|
||||||
set_tables_dotted(item, ["passwd", "oauth2"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "notmuch")]
|
|
||||||
set_table_dotted(item, "notmuch");
|
|
||||||
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
{
|
|
||||||
set_table_dotted(item, "smtp");
|
|
||||||
if let Some(item) = get_table_mut(item, "smtp") {
|
|
||||||
set_tables_dotted(item, ["passwd", "oauth2"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sendmail")]
|
|
||||||
set_table_dotted(item, "sendmail");
|
|
||||||
|
|
||||||
#[cfg(feature = "pgp")]
|
|
||||||
set_table_dotted(item, "pgp");
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -161,19 +43,13 @@ fn pretty_serialize(config: &TomlConfig) -> Result<String> {
|
||||||
Ok(doc.to_string())
|
Ok(doc.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_table_mut<'a>(item: &'a mut Item, key: &'a str) -> Option<&'a mut Item> {
|
fn set_table_dotted(table: &mut Table) {
|
||||||
item.get_mut(key).filter(|item| item.is_table())
|
let keys: Vec<String> = table.iter().map(|(key, _)| key.to_string()).collect();
|
||||||
|
for ref key in keys {
|
||||||
|
if let Some(table) = table.get_mut(key).unwrap().as_table_mut() {
|
||||||
|
table.set_dotted(true);
|
||||||
|
set_table_dotted(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_table_dotted(item: &mut Item, key: &str) {
|
|
||||||
if let Some(table) = get_table_mut(item, key).and_then(|item| item.as_table_mut()) {
|
|
||||||
table.set_dotted(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_tables_dotted<'a>(item: &'a mut Item, keys: impl IntoIterator<Item = &'a str>) {
|
|
||||||
for key in keys {
|
|
||||||
set_table_dotted(item, key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
pub(crate) mod wizard;
|
|
|
@ -1,346 +0,0 @@
|
||||||
use color_eyre::Result;
|
|
||||||
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
use email::{
|
|
||||||
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
|
||||||
autoconfig::config::AuthenticationType,
|
|
||||||
};
|
|
||||||
use email::{
|
|
||||||
account::config::passwd::PasswdConfig,
|
|
||||||
imap::config::{ImapAuthConfig, ImapConfig, ImapEncryptionKind},
|
|
||||||
};
|
|
||||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
|
||||||
use secret::Secret;
|
|
||||||
|
|
||||||
use crate::{backend::config::BackendConfig, ui::prompt};
|
|
||||||
|
|
||||||
const ENCRYPTIONS: &[ImapEncryptionKind] = &[
|
|
||||||
ImapEncryptionKind::Tls,
|
|
||||||
ImapEncryptionKind::StartTls,
|
|
||||||
ImapEncryptionKind::None,
|
|
||||||
];
|
|
||||||
|
|
||||||
const SECRETS: &[&str] = &[
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
KEYRING,
|
|
||||||
RAW,
|
|
||||||
CMD,
|
|
||||||
];
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
const KEYRING: &str = "Ask my password, then save it in my system's global keyring";
|
|
||||||
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
|
|
||||||
const CMD: &str = "Ask me a shell command that exposes my password";
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct U16Validator;
|
|
||||||
|
|
||||||
impl StringValidator for U16Validator {
|
|
||||||
fn validate(
|
|
||||||
&self,
|
|
||||||
input: &str,
|
|
||||||
) -> std::prelude::v1::Result<Validation, inquire::CustomUserError> {
|
|
||||||
if input.parse::<u16>().is_ok() {
|
|
||||||
Ok(Validation::Valid)
|
|
||||||
} else {
|
|
||||||
Ok(Validation::Invalid(ErrorMessage::Custom(format!(
|
|
||||||
"you should enter a number between {} and {}",
|
|
||||||
u16::MIN,
|
|
||||||
u16::MAX
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure(
|
|
||||||
account_name: &str,
|
|
||||||
email: &str,
|
|
||||||
autoconfig: Option<&AutoConfig>,
|
|
||||||
) -> Result<BackendConfig> {
|
|
||||||
use color_eyre::eyre::OptionExt as _;
|
|
||||||
use inquire::{validator, Select, Text};
|
|
||||||
|
|
||||||
let autoconfig_server = autoconfig.and_then(|c| {
|
|
||||||
c.email_provider()
|
|
||||||
.incoming_servers()
|
|
||||||
.into_iter()
|
|
||||||
.find(|server| matches!(server.server_type(), ServerType::Imap))
|
|
||||||
});
|
|
||||||
|
|
||||||
let autoconfig_host = autoconfig_server
|
|
||||||
.and_then(|s| s.hostname())
|
|
||||||
.map(ToOwned::to_owned);
|
|
||||||
|
|
||||||
let default_host =
|
|
||||||
autoconfig_host.unwrap_or_else(|| format!("imap.{}", email.rsplit_once('@').unwrap().1));
|
|
||||||
|
|
||||||
let host = Text::new("IMAP hostname")
|
|
||||||
.with_default(&default_host)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let autoconfig_encryption = autoconfig_server
|
|
||||||
.and_then(|imap| {
|
|
||||||
imap.security_type().map(|encryption| match encryption {
|
|
||||||
SecurityType::Plain => ImapEncryptionKind::None,
|
|
||||||
SecurityType::Starttls => ImapEncryptionKind::StartTls,
|
|
||||||
SecurityType::Tls => ImapEncryptionKind::Tls,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let default_encryption_idx = match &autoconfig_encryption {
|
|
||||||
ImapEncryptionKind::Tls => 0,
|
|
||||||
ImapEncryptionKind::StartTls => 1,
|
|
||||||
ImapEncryptionKind::None => 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encryption_kind = Select::new("IMAP encryption", ENCRYPTIONS.to_vec())
|
|
||||||
.with_starting_cursor(default_encryption_idx)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
let autoconfig_port = autoconfig_server
|
|
||||||
.and_then(|s| s.port())
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.unwrap_or_else(|| match &autoconfig_encryption {
|
|
||||||
ImapEncryptionKind::Tls => 465,
|
|
||||||
ImapEncryptionKind::StartTls => 587,
|
|
||||||
ImapEncryptionKind::None => 25,
|
|
||||||
});
|
|
||||||
|
|
||||||
let (encryption, default_port) = match encryption_kind {
|
|
||||||
Some(idx)
|
|
||||||
if &idx
|
|
||||||
== ENCRYPTIONS.get(default_encryption_idx).ok_or_eyre(
|
|
||||||
"something impossible happened during finding default match for encryption.",
|
|
||||||
)? =>
|
|
||||||
{
|
|
||||||
(Some(autoconfig_encryption), autoconfig_port)
|
|
||||||
}
|
|
||||||
Some(ImapEncryptionKind::Tls) => (Some(ImapEncryptionKind::Tls), 465),
|
|
||||||
Some(ImapEncryptionKind::StartTls) => (Some(ImapEncryptionKind::StartTls), 587),
|
|
||||||
_ => (Some(ImapEncryptionKind::None), 25),
|
|
||||||
};
|
|
||||||
|
|
||||||
let port = Text::new("IMAP port")
|
|
||||||
.with_validators(&[
|
|
||||||
Box::new(validator::MinLengthValidator::new(1)),
|
|
||||||
Box::new(U16Validator {}),
|
|
||||||
])
|
|
||||||
.with_default(&default_port.to_string())
|
|
||||||
.prompt()
|
|
||||||
.map(|input| input.parse::<u16>().unwrap())?;
|
|
||||||
|
|
||||||
let autoconfig_login = autoconfig_server.map(|imap| match imap.username() {
|
|
||||||
Some("%EMAILLOCALPART%") => email.rsplit_once('@').unwrap().0.to_owned(),
|
|
||||||
Some("%EMAILADDRESS%") => email.to_owned(),
|
|
||||||
_ => email.to_owned(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let default_login = autoconfig_login.unwrap_or_else(|| email.to_owned());
|
|
||||||
|
|
||||||
let login = Text::new("IMAP login")
|
|
||||||
.with_default(&default_login)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
let auth = {
|
|
||||||
use inquire::{Confirm, Password};
|
|
||||||
|
|
||||||
const XOAUTH2: &str = "XOAUTH2";
|
|
||||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
|
||||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
|
||||||
|
|
||||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
|
||||||
|
|
||||||
let default_oauth2_enabled = autoconfig_server
|
|
||||||
.and_then(|imap| {
|
|
||||||
imap.authentication_type()
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
|
||||||
})
|
|
||||||
.filter(|_| autoconfig_oauth2.is_some())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
|
||||||
.with_default(default_oauth2_enabled)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if oauth2_enabled {
|
|
||||||
let mut config = OAuth2Config::default();
|
|
||||||
let redirect_host = OAuth2Config::LOCALHOST;
|
|
||||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
|
||||||
|
|
||||||
let method_idx = Select::new("IMAP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
config.method = match method_idx {
|
|
||||||
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
|
||||||
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
|
||||||
_ => OAuth2Method::XOAuth2,
|
|
||||||
};
|
|
||||||
|
|
||||||
config.client_id = Text::new("IMAP OAuth 2.0 client id").prompt()?;
|
|
||||||
|
|
||||||
let client_secret: String = Password::new("IMAP OAuth 2.0 client secret")
|
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
|
||||||
.prompt()?;
|
|
||||||
config.client_secret =
|
|
||||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-client-secret"))?;
|
|
||||||
config
|
|
||||||
.client_secret
|
|
||||||
.set_only_keyring(&client_secret)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let default_auth_url = autoconfig_oauth2
|
|
||||||
.map(|o| o.auth_url().to_owned())
|
|
||||||
.unwrap_or_default();
|
|
||||||
config.auth_url = Text::new("IMAP OAuth 2.0 authorization URL")
|
|
||||||
.with_default(&default_auth_url)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let default_token_url = autoconfig_oauth2
|
|
||||||
.map(|o| o.token_url().to_owned())
|
|
||||||
.unwrap_or_default();
|
|
||||||
config.token_url = Text::new("IMAP OAuth 2.0 token URL")
|
|
||||||
.with_default(&default_token_url)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
|
||||||
|
|
||||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
|
||||||
Ok(match &autoconfig_scopes {
|
|
||||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.map(ToOwned::to_owned),
|
|
||||||
None => Some(Text::new(prompt).prompt()?).filter(|scope| !scope.is_empty()),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("IMAP OAuth 2.0 main scope")? {
|
|
||||||
config.scopes = OAuth2Scopes::Scope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
let confirm_additional_scope = || -> Result<bool> {
|
|
||||||
let confirm = Confirm::new("Would you like to add more IMAP OAuth 2.0 scopes?")
|
|
||||||
.with_default(false)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(confirm)
|
|
||||||
};
|
|
||||||
|
|
||||||
while confirm_additional_scope()? {
|
|
||||||
let mut scopes = match config.scopes {
|
|
||||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
|
||||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("Additional IMAP OAuth 2.0 scope")? {
|
|
||||||
scopes.push(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
|
||||||
.with_default(true)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or(true);
|
|
||||||
|
|
||||||
crate::wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
|
||||||
|
|
||||||
let client = Client::new(
|
|
||||||
config.client_id.clone(),
|
|
||||||
client_secret,
|
|
||||||
config.auth_url.clone(),
|
|
||||||
config.token_url.clone(),
|
|
||||||
)?
|
|
||||||
.with_redirect_host(redirect_host.to_owned())
|
|
||||||
.with_redirect_port(redirect_port)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
|
||||||
.with_redirect_host(redirect_host.to_owned())
|
|
||||||
.with_redirect_port(redirect_port);
|
|
||||||
|
|
||||||
if config.pkce {
|
|
||||||
auth_code_grant = auth_code_grant.with_pkce();
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope in config.scopes.clone() {
|
|
||||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
|
||||||
|
|
||||||
println!("{redirect_url}");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let (access_token, refresh_token) = auth_code_grant
|
|
||||||
.wait_for_redirection(&client, csrf_token)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
config.access_token =
|
|
||||||
Secret::try_new_keyring_entry(format!("{account_name}-imap-oauth2-access-token"))?;
|
|
||||||
config.access_token.set_only_keyring(access_token).await?;
|
|
||||||
|
|
||||||
if let Some(refresh_token) = &refresh_token {
|
|
||||||
config.refresh_token = Secret::try_new_keyring_entry(format!(
|
|
||||||
"{account_name}-imap-oauth2-refresh-token"
|
|
||||||
))?;
|
|
||||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImapAuthConfig::OAuth2(config)
|
|
||||||
} else {
|
|
||||||
configure_passwd(account_name).await?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "oauth2"))]
|
|
||||||
let auth = configure_passwd(account_name).await?;
|
|
||||||
|
|
||||||
let config = ImapConfig {
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
encryption,
|
|
||||||
login,
|
|
||||||
auth,
|
|
||||||
extensions: None,
|
|
||||||
watch: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(BackendConfig::Imap(config))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure_passwd(account_name: &str) -> Result<ImapAuthConfig> {
|
|
||||||
use inquire::{Select, Text};
|
|
||||||
|
|
||||||
let secret_idx = Select::new("IMAP authentication strategy", SECRETS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
let secret = match secret_idx {
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
Some(sec) if sec == KEYRING => {
|
|
||||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-imap-passwd"))?;
|
|
||||||
secret
|
|
||||||
.set_only_keyring(prompt::passwd("IMAP password")?)
|
|
||||||
.await?;
|
|
||||||
secret
|
|
||||||
}
|
|
||||||
Some(sec) if sec == RAW => Secret::new_raw(prompt::passwd("IMAP password")?),
|
|
||||||
Some(sec) if sec == CMD => Secret::new_command(
|
|
||||||
Text::new("Shell command")
|
|
||||||
.with_default(&format!("pass show {account_name}-imap-passwd"))
|
|
||||||
.prompt()?,
|
|
||||||
),
|
|
||||||
_ => Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ImapAuthConfig::Passwd(PasswdConfig(secret)))
|
|
||||||
}
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -6,19 +6,9 @@ pub mod completion;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod email;
|
pub mod email;
|
||||||
pub mod folder;
|
pub mod folder;
|
||||||
#[cfg(feature = "imap")]
|
|
||||||
pub mod imap;
|
|
||||||
#[cfg(feature = "maildir")]
|
|
||||||
pub mod maildir;
|
|
||||||
pub mod manual;
|
pub mod manual;
|
||||||
#[cfg(feature = "notmuch")]
|
|
||||||
pub mod notmuch;
|
|
||||||
pub mod output;
|
pub mod output;
|
||||||
pub mod printer;
|
pub mod printer;
|
||||||
#[cfg(feature = "sendmail")]
|
|
||||||
pub mod sendmail;
|
|
||||||
#[cfg(feature = "smtp")]
|
|
||||||
pub mod smtp;
|
|
||||||
pub mod tracing;
|
pub mod tracing;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
pub(crate) mod wizard;
|
|
|
@ -1,25 +0,0 @@
|
||||||
use color_eyre::Result;
|
|
||||||
use dirs::home_dir;
|
|
||||||
use email::maildir::config::MaildirConfig;
|
|
||||||
use inquire::Text;
|
|
||||||
|
|
||||||
use crate::backend::config::BackendConfig;
|
|
||||||
|
|
||||||
pub(crate) fn configure() -> Result<BackendConfig> {
|
|
||||||
let mut config = MaildirConfig::default();
|
|
||||||
|
|
||||||
let mut input = Text::new("Maildir directory");
|
|
||||||
|
|
||||||
let Some(home) = home_dir() else {
|
|
||||||
config.root_dir = input.prompt()?.into();
|
|
||||||
|
|
||||||
return Ok(BackendConfig::Maildir(config));
|
|
||||||
};
|
|
||||||
|
|
||||||
let def = home.join("Mail").display().to_string();
|
|
||||||
input = input.with_default(&def);
|
|
||||||
|
|
||||||
config.root_dir = input.prompt()?.into();
|
|
||||||
|
|
||||||
Ok(BackendConfig::Maildir(config))
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub(crate) mod wizard;
|
|
|
@ -1,23 +0,0 @@
|
||||||
use color_eyre::Result;
|
|
||||||
use email::notmuch::config::NotmuchConfig;
|
|
||||||
use inquire::Text;
|
|
||||||
|
|
||||||
use crate::backend::config::BackendConfig;
|
|
||||||
|
|
||||||
pub(crate) fn configure() -> Result<BackendConfig> {
|
|
||||||
let config = NotmuchConfig {
|
|
||||||
database_path: Some(
|
|
||||||
Text::new("Notmuch database path")
|
|
||||||
.with_default(
|
|
||||||
&NotmuchConfig::get_default_database_path()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string_lossy(),
|
|
||||||
)
|
|
||||||
.prompt()?
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(BackendConfig::Notmuch(config))
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub(crate) mod wizard;
|
|
|
@ -1,16 +0,0 @@
|
||||||
use color_eyre::Result;
|
|
||||||
use email::sendmail::config::SendmailConfig;
|
|
||||||
use inquire::Text;
|
|
||||||
|
|
||||||
use crate::backend::config::BackendConfig;
|
|
||||||
|
|
||||||
pub(crate) fn configure() -> Result<BackendConfig> {
|
|
||||||
let config = SendmailConfig {
|
|
||||||
cmd: Text::new("Sendmail-compatible shell command to send emails")
|
|
||||||
.with_default("/usr/bin/msmtp")
|
|
||||||
.prompt()?
|
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(BackendConfig::Sendmail(config))
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
#[cfg(feature = "wizard")]
|
|
||||||
pub(crate) mod wizard;
|
|
|
@ -1,344 +0,0 @@
|
||||||
use color_eyre::Result;
|
|
||||||
use email::autoconfig::config::{AutoConfig, SecurityType, ServerType};
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
use email::{
|
|
||||||
account::config::oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
|
||||||
autoconfig::config::AuthenticationType,
|
|
||||||
};
|
|
||||||
use email::{
|
|
||||||
account::config::passwd::PasswdConfig,
|
|
||||||
smtp::config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
|
|
||||||
};
|
|
||||||
use inquire::validator::{ErrorMessage, StringValidator, Validation};
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
|
||||||
use secret::Secret;
|
|
||||||
|
|
||||||
use crate::{backend::config::BackendConfig, ui::prompt};
|
|
||||||
|
|
||||||
const ENCRYPTIONS: &[SmtpEncryptionKind] = &[
|
|
||||||
SmtpEncryptionKind::Tls,
|
|
||||||
SmtpEncryptionKind::StartTls,
|
|
||||||
SmtpEncryptionKind::None,
|
|
||||||
];
|
|
||||||
|
|
||||||
const SECRETS: &[&str] = &[
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
KEYRING,
|
|
||||||
RAW,
|
|
||||||
CMD,
|
|
||||||
];
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
const KEYRING: &str = "Ask my password, then save it in my system's global keyring";
|
|
||||||
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
|
|
||||||
const CMD: &str = "Ask me a shell command that exposes my password";
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct U16Validator;
|
|
||||||
|
|
||||||
impl StringValidator for U16Validator {
|
|
||||||
fn validate(
|
|
||||||
&self,
|
|
||||||
input: &str,
|
|
||||||
) -> std::prelude::v1::Result<Validation, inquire::CustomUserError> {
|
|
||||||
if input.parse::<u16>().is_ok() {
|
|
||||||
Ok(Validation::Valid)
|
|
||||||
} else {
|
|
||||||
Ok(Validation::Invalid(ErrorMessage::Custom(format!(
|
|
||||||
"you should enter a number between {} and {}",
|
|
||||||
u16::MIN,
|
|
||||||
u16::MAX
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure(
|
|
||||||
account_name: &str,
|
|
||||||
email: &str,
|
|
||||||
autoconfig: Option<&AutoConfig>,
|
|
||||||
) -> Result<BackendConfig> {
|
|
||||||
use color_eyre::eyre::OptionExt as _;
|
|
||||||
use inquire::{validator, Select, Text};
|
|
||||||
|
|
||||||
let autoconfig_server = autoconfig.and_then(|c| {
|
|
||||||
c.email_provider()
|
|
||||||
.outgoing_servers()
|
|
||||||
.into_iter()
|
|
||||||
.find(|server| matches!(server.server_type(), ServerType::Smtp))
|
|
||||||
});
|
|
||||||
|
|
||||||
let autoconfig_host = autoconfig_server
|
|
||||||
.and_then(|s| s.hostname())
|
|
||||||
.map(ToOwned::to_owned);
|
|
||||||
|
|
||||||
let default_host =
|
|
||||||
autoconfig_host.unwrap_or_else(|| format!("smtp.{}", email.rsplit_once('@').unwrap().1));
|
|
||||||
|
|
||||||
let host = Text::new("SMTP hostname")
|
|
||||||
.with_default(&default_host)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let autoconfig_encryption = autoconfig_server
|
|
||||||
.and_then(|smtp| {
|
|
||||||
smtp.security_type().map(|encryption| match encryption {
|
|
||||||
SecurityType::Plain => SmtpEncryptionKind::None,
|
|
||||||
SecurityType::Starttls => SmtpEncryptionKind::StartTls,
|
|
||||||
SecurityType::Tls => SmtpEncryptionKind::Tls,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let default_encryption_idx = match &autoconfig_encryption {
|
|
||||||
SmtpEncryptionKind::Tls => 0,
|
|
||||||
SmtpEncryptionKind::StartTls => 1,
|
|
||||||
SmtpEncryptionKind::None => 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let encryption_kind = Select::new("SMTP encryption", ENCRYPTIONS.to_vec())
|
|
||||||
.with_starting_cursor(default_encryption_idx)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
let autoconfig_port = autoconfig_server
|
|
||||||
.and_then(|s| s.port())
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.unwrap_or_else(|| match &autoconfig_encryption {
|
|
||||||
SmtpEncryptionKind::Tls => 465,
|
|
||||||
SmtpEncryptionKind::StartTls => 587,
|
|
||||||
SmtpEncryptionKind::None => 25,
|
|
||||||
});
|
|
||||||
|
|
||||||
let (encryption, default_port) = match encryption_kind {
|
|
||||||
Some(idx)
|
|
||||||
if &idx
|
|
||||||
== ENCRYPTIONS.get(default_encryption_idx).ok_or_eyre(
|
|
||||||
"something impossible happened during finding default match for encryption.",
|
|
||||||
)? =>
|
|
||||||
{
|
|
||||||
(Some(autoconfig_encryption), autoconfig_port)
|
|
||||||
}
|
|
||||||
Some(SmtpEncryptionKind::Tls) => (Some(SmtpEncryptionKind::Tls), 465),
|
|
||||||
Some(SmtpEncryptionKind::StartTls) => (Some(SmtpEncryptionKind::StartTls), 587),
|
|
||||||
_ => (Some(SmtpEncryptionKind::None), 25),
|
|
||||||
};
|
|
||||||
|
|
||||||
let port = Text::new("SMTP port")
|
|
||||||
.with_validators(&[
|
|
||||||
Box::new(validator::MinLengthValidator::new(1)),
|
|
||||||
Box::new(U16Validator {}),
|
|
||||||
])
|
|
||||||
.with_default(&default_port.to_string())
|
|
||||||
.prompt()
|
|
||||||
.map(|input| input.parse::<u16>().unwrap())?;
|
|
||||||
|
|
||||||
let autoconfig_login = autoconfig_server.map(|smtp| match smtp.username() {
|
|
||||||
Some("%EMAILLOCALPART%") => email.rsplit_once('@').unwrap().0.to_owned(),
|
|
||||||
Some("%EMAILADDRESS%") => email.to_owned(),
|
|
||||||
_ => email.to_owned(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let default_login = autoconfig_login.unwrap_or_else(|| email.to_owned());
|
|
||||||
|
|
||||||
let login = Text::new("SMTP login")
|
|
||||||
.with_default(&default_login)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
let auth = {
|
|
||||||
use inquire::{Confirm, Password};
|
|
||||||
|
|
||||||
const XOAUTH2: &str = "XOAUTH2";
|
|
||||||
const OAUTHBEARER: &str = "OAUTHBEARER";
|
|
||||||
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
|
||||||
|
|
||||||
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
|
|
||||||
|
|
||||||
let default_oauth2_enabled = autoconfig_server
|
|
||||||
.and_then(|smtp| {
|
|
||||||
smtp.authentication_type()
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|t| Option::from(matches!(t, AuthenticationType::OAuth2)))
|
|
||||||
})
|
|
||||||
.filter(|_| autoconfig_oauth2.is_some())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let oauth2_enabled = Confirm::new("Would you like to enable OAuth 2.0?")
|
|
||||||
.with_default(default_oauth2_enabled)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if oauth2_enabled {
|
|
||||||
let mut config = OAuth2Config::default();
|
|
||||||
let redirect_host = OAuth2Config::LOCALHOST;
|
|
||||||
let redirect_port = OAuth2Config::get_first_available_port()?;
|
|
||||||
|
|
||||||
let method_idx = Select::new("SMTP OAuth 2.0 mechanism", OAUTH2_MECHANISMS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
config.method = match method_idx {
|
|
||||||
Some(choice) if choice == XOAUTH2 => OAuth2Method::XOAuth2,
|
|
||||||
Some(choice) if choice == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
|
||||||
_ => OAuth2Method::XOAuth2,
|
|
||||||
};
|
|
||||||
|
|
||||||
config.client_id = Text::new("SMTP OAuth 2.0 client id").prompt()?;
|
|
||||||
|
|
||||||
let client_secret: String = Password::new("SMTP OAuth 2.0 client secret")
|
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
|
||||||
.prompt()?;
|
|
||||||
config.client_secret =
|
|
||||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))?;
|
|
||||||
config
|
|
||||||
.client_secret
|
|
||||||
.set_only_keyring(&client_secret)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let default_auth_url = autoconfig_oauth2
|
|
||||||
.map(|o| o.auth_url().to_owned())
|
|
||||||
.unwrap_or_default();
|
|
||||||
config.auth_url = Text::new("SMTP OAuth 2.0 authorization URL")
|
|
||||||
.with_default(&default_auth_url)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let default_token_url = autoconfig_oauth2
|
|
||||||
.map(|o| o.token_url().to_owned())
|
|
||||||
.unwrap_or_default();
|
|
||||||
config.token_url = Text::new("SMTP OAuth 2.0 token URL")
|
|
||||||
.with_default(&default_token_url)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
let autoconfig_scopes = autoconfig_oauth2.map(|o| o.scope());
|
|
||||||
|
|
||||||
let prompt_scope = |prompt: &str| -> Result<Option<String>> {
|
|
||||||
Ok(match &autoconfig_scopes {
|
|
||||||
Some(scopes) => Select::new(prompt, scopes.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.map(ToOwned::to_owned),
|
|
||||||
None => Some(Text::new(prompt).prompt()?).filter(|scope| !scope.is_empty()),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("SMTP OAuth 2.0 main scope")? {
|
|
||||||
config.scopes = OAuth2Scopes::Scope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
let confirm_additional_scope = || -> Result<bool> {
|
|
||||||
let confirm = Confirm::new("Would you like to add more SMTP OAuth 2.0 scopes?")
|
|
||||||
.with_default(false)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(confirm)
|
|
||||||
};
|
|
||||||
|
|
||||||
while confirm_additional_scope()? {
|
|
||||||
let mut scopes = match config.scopes {
|
|
||||||
OAuth2Scopes::Scope(scope) => vec![scope],
|
|
||||||
OAuth2Scopes::Scopes(scopes) => scopes,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(scope) = prompt_scope("Additional SMTP OAuth 2.0 scope")? {
|
|
||||||
scopes.push(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.scopes = OAuth2Scopes::Scopes(scopes);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.pkce = Confirm::new("Would you like to enable PKCE verification?")
|
|
||||||
.with_default(true)
|
|
||||||
.prompt_skippable()?
|
|
||||||
.unwrap_or(true);
|
|
||||||
|
|
||||||
crate::wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
|
||||||
|
|
||||||
let client = Client::new(
|
|
||||||
config.client_id.clone(),
|
|
||||||
client_secret,
|
|
||||||
config.auth_url.clone(),
|
|
||||||
config.token_url.clone(),
|
|
||||||
)?
|
|
||||||
.with_redirect_host(redirect_host.to_owned())
|
|
||||||
.with_redirect_port(redirect_port)
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
|
||||||
.with_redirect_host(redirect_host.to_owned())
|
|
||||||
.with_redirect_port(redirect_port);
|
|
||||||
|
|
||||||
if config.pkce {
|
|
||||||
auth_code_grant = auth_code_grant.with_pkce();
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope in config.scopes.clone() {
|
|
||||||
auth_code_grant = auth_code_grant.with_scope(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
|
||||||
|
|
||||||
println!("{redirect_url}");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let (access_token, refresh_token) = auth_code_grant
|
|
||||||
.wait_for_redirection(&client, csrf_token)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
config.access_token =
|
|
||||||
Secret::try_new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))?;
|
|
||||||
config.access_token.set_only_keyring(access_token).await?;
|
|
||||||
|
|
||||||
if let Some(refresh_token) = &refresh_token {
|
|
||||||
config.refresh_token = Secret::try_new_keyring_entry(format!(
|
|
||||||
"{account_name}-smtp-oauth2-refresh-token"
|
|
||||||
))?;
|
|
||||||
config.refresh_token.set_only_keyring(refresh_token).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
SmtpAuthConfig::OAuth2(config)
|
|
||||||
} else {
|
|
||||||
configure_passwd(account_name).await?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "oauth2"))]
|
|
||||||
let auth = configure_passwd(account_name).await?;
|
|
||||||
|
|
||||||
let config = SmtpConfig {
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
encryption,
|
|
||||||
login,
|
|
||||||
auth,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(BackendConfig::Smtp(config))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn configure_passwd(account_name: &str) -> Result<SmtpAuthConfig> {
|
|
||||||
use inquire::{Select, Text};
|
|
||||||
|
|
||||||
let secret_idx = Select::new("SMTP authentication strategy", SECRETS.to_vec())
|
|
||||||
.with_starting_cursor(0)
|
|
||||||
.prompt_skippable()?;
|
|
||||||
|
|
||||||
let secret = match secret_idx {
|
|
||||||
#[cfg(feature = "keyring")]
|
|
||||||
Some(sec) if sec == KEYRING => {
|
|
||||||
let secret = Secret::try_new_keyring_entry(format!("{account_name}-smtp-passwd"))?;
|
|
||||||
secret
|
|
||||||
.set_only_keyring(prompt::passwd("SMTP password")?)
|
|
||||||
.await?;
|
|
||||||
secret
|
|
||||||
}
|
|
||||||
Some(sec) if sec == RAW => Secret::new_raw(prompt::passwd("SMTP password")?),
|
|
||||||
Some(sec) if sec == CMD => Secret::new_command(
|
|
||||||
Text::new("Shell command")
|
|
||||||
.with_default(&format!("pass show {account_name}-smtp-passwd"))
|
|
||||||
.prompt()?,
|
|
||||||
),
|
|
||||||
_ => Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(SmtpAuthConfig::Passwd(PasswdConfig(secret)))
|
|
||||||
}
|
|
|
@ -1,17 +1,17 @@
|
||||||
use std::fmt::Display;
|
use std::fmt;
|
||||||
|
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use inquire::Select;
|
use pimalaya_tui::prompt;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum PreEditChoice {
|
pub enum PreEditChoice {
|
||||||
Edit,
|
Edit,
|
||||||
Discard,
|
Discard,
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PreEditChoice {
|
impl fmt::Display for PreEditChoice {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
|
@ -24,22 +24,20 @@ impl Display for PreEditChoice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pre_edit() -> Result<PreEditChoice> {
|
static PRE_EDIT_CHOICES: [PreEditChoice; 3] = [
|
||||||
let choices = [
|
|
||||||
PreEditChoice::Edit,
|
PreEditChoice::Edit,
|
||||||
PreEditChoice::Discard,
|
PreEditChoice::Discard,
|
||||||
PreEditChoice::Quit,
|
PreEditChoice::Quit,
|
||||||
];
|
];
|
||||||
|
|
||||||
let user_choice = Select::new(
|
pub fn pre_edit() -> Result<PreEditChoice> {
|
||||||
|
let user_choice = prompt::item(
|
||||||
"A draft was found, what would you like to do with it?",
|
"A draft was found, what would you like to do with it?",
|
||||||
choices.to_vec(),
|
&PRE_EDIT_CHOICES,
|
||||||
)
|
None,
|
||||||
.with_starting_cursor(0)
|
)?;
|
||||||
.with_vim_mode(true)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
Ok(user_choice)
|
Ok(user_choice.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -51,7 +49,7 @@ pub enum PostEditChoice {
|
||||||
Discard,
|
Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PostEditChoice {
|
impl fmt::Display for PostEditChoice {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
@ -67,8 +65,7 @@ impl Display for PostEditChoice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_edit() -> Result<PostEditChoice> {
|
static POST_EDIT_CHOICES: [PostEditChoice; 5] = [
|
||||||
let choices = [
|
|
||||||
PostEditChoice::Send,
|
PostEditChoice::Send,
|
||||||
PostEditChoice::Edit,
|
PostEditChoice::Edit,
|
||||||
PostEditChoice::LocalDraft,
|
PostEditChoice::LocalDraft,
|
||||||
|
@ -76,13 +73,12 @@ pub fn post_edit() -> Result<PostEditChoice> {
|
||||||
PostEditChoice::Discard,
|
PostEditChoice::Discard,
|
||||||
];
|
];
|
||||||
|
|
||||||
let user_choice = inquire::Select::new(
|
pub fn post_edit() -> Result<PostEditChoice> {
|
||||||
|
let user_choice = prompt::item(
|
||||||
"What would you like to do with this message?",
|
"What would you like to do with this message?",
|
||||||
choices.to_vec(),
|
&POST_EDIT_CHOICES,
|
||||||
)
|
None,
|
||||||
.with_starting_cursor(0)
|
)?;
|
||||||
.with_vim_mode(true)
|
|
||||||
.prompt()?;
|
|
||||||
|
|
||||||
Ok(user_choice)
|
Ok(user_choice.clone())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::{env, fs, sync::Arc};
|
||||||
|
|
||||||
use color_eyre::{eyre::Context, Result};
|
use color_eyre::{eyre::Context, Result};
|
||||||
use email::{
|
use email::{
|
||||||
account::config::AccountConfig,
|
account::config::AccountConfig,
|
||||||
|
@ -8,7 +10,6 @@ use email::{
|
||||||
};
|
};
|
||||||
use mml::MmlCompilerBuilder;
|
use mml::MmlCompilerBuilder;
|
||||||
use process::SingleCommand;
|
use process::SingleCommand;
|
||||||
use std::{env, fs, sync::Arc};
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crossterm::style::Color;
|
||||||
|
|
||||||
pub mod choice;
|
pub mod choice;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub(crate) mod prompt;
|
|
||||||
|
|
||||||
pub(crate) fn map_color(color: Color) -> comfy_table::Color {
|
pub(crate) fn map_color(color: Color) -> comfy_table::Color {
|
||||||
match color {
|
match color {
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
pub(crate) fn passwd(prompt: &str) -> io::Result<String> {
|
|
||||||
inquire::Password::new(prompt)
|
|
||||||
.with_custom_confirmation_message("Confirm password")
|
|
||||||
.with_custom_confirmation_error_message("Passwords do not match, please try again.")
|
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
|
||||||
.prompt()
|
|
||||||
.map_err(|e| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Interrupted,
|
|
||||||
format!("failed to get password: {e}"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "oauth2")]
|
|
||||||
pub(crate) fn secret(prompt: &str) -> io::Result<String> {
|
|
||||||
inquire::Password::new(prompt)
|
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
|
||||||
.without_confirmation()
|
|
||||||
.prompt()
|
|
||||||
.map_err(|e| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Interrupted,
|
|
||||||
format!("failed to get secret: {e}"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in a new issue