mirror of
https://github.com/MeexReay/bRAC.git
synced 2025-05-06 13:38:04 +03:00
massive rewrite
This commit is contained in:
parent
036648b5e4
commit
58213cf8c9
110
Cargo.lock
generated
110
Cargo.lock
generated
@ -91,9 +91,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.8.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@ -124,9 +124,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.29"
|
version = "4.5.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
|
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -134,9 +134,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.29"
|
version = "4.5.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
|
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -146,9 +146,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.28"
|
version = "4.5.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
|
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -177,6 +177,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -195,15 +204,17 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.28.1"
|
version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
|
"derive_more",
|
||||||
|
"document-features",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix",
|
"rustix 1.0.5",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"signal-hook-mio",
|
"signal-hook-mio",
|
||||||
"winapi",
|
"winapi",
|
||||||
@ -218,6 +229,36 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -341,6 +382,18 @@ version = "0.4.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@ -377,9 +430,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.13"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@ -588,7 +641,20 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.4.15",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys 0.9.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -638,18 +704,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.217"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.217"
|
version = "1.0.219"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -740,7 +806,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix 0.38.44",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -750,6 +816,12 @@ version = "1.0.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
17
Cargo.toml
17
Cargo.toml
@ -8,9 +8,16 @@ rand = "0.9.0"
|
|||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
colored = "3.0.0"
|
colored = "3.0.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
crossterm = "0.28.1"
|
crossterm = { version = "0.29.0", optional = true }
|
||||||
serde = { version = "1.0.217", features = ["serde_derive"] }
|
serde = { version = "1.0.219", features = ["serde_derive"] }
|
||||||
serde_yml = "0.0.12"
|
serde_yml = "0.0.12"
|
||||||
homedir = "0.3.4"
|
homedir = { version = "0.3.4", optional = true }
|
||||||
clap = { version = "4.5.29", features = ["derive"] }
|
clap = { version = "4.5.36", features = ["derive"] }
|
||||||
native-tls = "0.2.13"
|
native-tls = { version = "0.2.14", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["ssl", "pretty", "homedir"]
|
||||||
|
|
||||||
|
ssl = ["dep:native-tls"]
|
||||||
|
pretty = ["dep:crossterm"]
|
||||||
|
homedir = ["dep:homedir"]
|
11
README.md
11
README.md
@ -27,6 +27,15 @@ better RAC client
|
|||||||
|
|
||||||
go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple.
|
go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple.
|
||||||
|
|
||||||
|
### from flake
|
||||||
|
|
||||||
|
If you have Nix package manager installed, you can use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build github:MeexReay/bRAC # build binary (result/bin/bRAC)
|
||||||
|
nix run github:MeexReay/bRAC # run (builds and runs bRAC)
|
||||||
|
```
|
||||||
|
|
||||||
### build from source
|
### build from source
|
||||||
|
|
||||||
(you have to install [rust](https://www.rust-lang.org/tools/install) at first)
|
(you have to install [rust](https://www.rust-lang.org/tools/install) at first)
|
||||||
@ -35,7 +44,7 @@ go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download
|
|||||||
git clone https://github.com/MeexReay/bRAC.git
|
git clone https://github.com/MeexReay/bRAC.git
|
||||||
cd bRAC
|
cd bRAC
|
||||||
cargo build --release # build release (target/release/bRAC)
|
cargo build --release # build release (target/release/bRAC)
|
||||||
cargo run # run (builds and runs bRAC itself)
|
cargo run --release # run (builds and runs bRAC itself)
|
||||||
```
|
```
|
||||||
|
|
||||||
## default config
|
## default config
|
||||||
|
68
flake.lock
generated
68
flake.lock
generated
@ -1,39 +1,54 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1743550720,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739055578,
|
"lastModified": 1744463964,
|
||||||
"narHash": "sha256-2MhC2Bgd06uI1A0vkdNUyDYsMD0SLNGKtD8600mZ69A=",
|
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "a45fa362d887f4d4a7157d95c28ca9ce2899b70e",
|
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"ref": "nixos-24.11",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1743296961,
|
||||||
|
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736320768,
|
"lastModified": 1736320768,
|
||||||
@ -52,7 +67,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-parts": "flake-parts",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
@ -62,11 +77,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739240901,
|
"lastModified": 1744513456,
|
||||||
"narHash": "sha256-YDtl/9w71m5WcZvbEroYoWrjECDhzJZLZ8E68S3BYok=",
|
"narHash": "sha256-NLVluTmK8d01Iz+WyarQhwFcXpHEwU7m5hH3YQQFJS0=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "03473e2af8a4b490f4d2cdb2e4d3b75f82c8197c",
|
"rev": "730fd8e82799219754418483fabe1844262fd1e2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -74,21 +89,6 @@
|
|||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
63
flake.nix
63
flake.nix
@ -2,34 +2,59 @@
|
|||||||
description = "bRAC - better RAC client";
|
description = "bRAC - better RAC client";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
|
outputs = inputs:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
systems = [ "x86_64-linux" ];
|
||||||
|
perSystem = { config, self', pkgs, lib, system, ... }:
|
||||||
let
|
let
|
||||||
overlays = [ (import rust-overlay) ];
|
runtimeDeps = with pkgs; [ pkg-config openssl ];
|
||||||
pkgs = import nixpkgs {
|
buildDeps = with pkgs; [ pkg-config openssl ];
|
||||||
inherit system overlays;
|
devDeps = with pkgs; [ pkg-config openssl ];
|
||||||
|
|
||||||
|
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
|
msrv = cargoToml.package.rust-version;
|
||||||
|
|
||||||
|
rustPackage = features:
|
||||||
|
(pkgs.makeRustPlatform {
|
||||||
|
cargo = pkgs.rust-bin.stable.latest.minimal;
|
||||||
|
rustc = pkgs.rust-bin.stable.latest.minimal;
|
||||||
|
}).buildRustPackage {
|
||||||
|
inherit (cargoToml.package) name version;
|
||||||
|
src = ./.;
|
||||||
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
|
buildFeatures = features;
|
||||||
|
buildInputs = runtimeDeps;
|
||||||
|
nativeBuildInputs = buildDeps;
|
||||||
|
};
|
||||||
|
|
||||||
|
mkDevShell = rustc:
|
||||||
|
pkgs.mkShell {
|
||||||
|
shellHook = ''
|
||||||
|
export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc}
|
||||||
|
'';
|
||||||
|
buildInputs = runtimeDeps;
|
||||||
|
nativeBuildInputs = buildDeps ++ devDeps ++ [ rustc ];
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
devShells.default = pkgs.mkShell {
|
_module.args.pkgs = import inputs.nixpkgs {
|
||||||
buildInputs = with pkgs; [
|
inherit system;
|
||||||
rust-bin.stable.latest.default
|
overlays = [ (import inputs.rust-overlay) ];
|
||||||
pkg-config
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
packages.default = pkgs.rustPlatform.buildRustPackage {
|
packages.default = self'.packages.bRAC;
|
||||||
pname = "bRAC";
|
devShells.default = self'.devShells.stable;
|
||||||
version = "0.1.1+2.0";
|
|
||||||
src = pkgs.lib.cleanSource ./.;
|
|
||||||
|
|
||||||
cargoLock = {
|
packages.bRAC = (rustPackage "default");
|
||||||
lockFile = ./Cargo.lock;
|
packages.bRAC-minimal = (rustPackage "");
|
||||||
|
|
||||||
|
devShells.nightly = (mkDevShell (pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)));
|
||||||
|
devShells.stable = (mkDevShell pkgs.rust-bin.stable.latest.default);
|
||||||
|
devShells.msrv = (mkDevShell pkgs.rust-bin.stable.${msrv}.default);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
}
|
}
|
416
src/chat.rs
416
src/chat.rs
@ -1,11 +1,29 @@
|
|||||||
use std::{cmp::{max, min}, error::Error, io::{stdout, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::{Duration, SystemTime, UNIX_EPOCH}};
|
use std::{
|
||||||
|
error::Error, io::{stdout, Write},
|
||||||
|
sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock},
|
||||||
|
time::{SystemTime, UNIX_EPOCH}
|
||||||
|
};
|
||||||
|
|
||||||
use colored::{Color, Colorize};
|
use colored::{Color, Colorize};
|
||||||
use crossterm::{cursor::{MoveLeft, MoveRight}, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, execute, terminal::{self, disable_raw_mode, enable_raw_mode}};
|
|
||||||
|
|
||||||
use crate::{proto::{connect, send_message_auth}, util::{char_index_to_byte_index, string_chunks}, IP_REGEX};
|
use super::{
|
||||||
|
proto::{connect, read_messages, send_message, send_message_spoof_auth},
|
||||||
|
IP_REGEX,
|
||||||
|
util::sanitize_text,
|
||||||
|
COLORED_USERNAMES,
|
||||||
|
DATE_REGEX,
|
||||||
|
config::Context
|
||||||
|
};
|
||||||
|
|
||||||
use super::{proto::read_messages, util::sanitize_text, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message};
|
#[cfg(not(feature = "pretty"))]
|
||||||
|
pub mod minimal_tui;
|
||||||
|
#[cfg(not(feature = "pretty"))]
|
||||||
|
pub use minimal_tui::run_main_loop;
|
||||||
|
|
||||||
|
#[cfg(feature = "pretty")]
|
||||||
|
pub mod pretty_tui;
|
||||||
|
#[cfg(feature = "pretty")]
|
||||||
|
pub use pretty_tui::run_main_loop;
|
||||||
|
|
||||||
|
|
||||||
pub struct ChatStorage {
|
pub struct ChatStorage {
|
||||||
@ -34,10 +52,14 @@ impl ChatStorage {
|
|||||||
*self.messages.write().unwrap() = messages;
|
*self.messages.write().unwrap() = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(&self, messages: Vec<String>, packet_size: usize) {
|
pub fn append_and_store(&self, messages: Vec<String>, packet_size: usize) {
|
||||||
self.packet_size.store(packet_size, Ordering::SeqCst);
|
self.packet_size.store(packet_size, Ordering::SeqCst);
|
||||||
self.messages.write().unwrap().append(&mut messages.clone());
|
self.messages.write().unwrap().append(&mut messages.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append(&self, messages: Vec<String>) {
|
||||||
|
self.messages.write().unwrap().append(&mut messages.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +72,7 @@ const HELP_MESSAGE: &str = "Help message:\r
|
|||||||
Press enter to close";
|
Press enter to close";
|
||||||
|
|
||||||
|
|
||||||
fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let command = command.trim_start_matches("/");
|
let command = command.trim_start_matches("/");
|
||||||
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
|
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
|
||||||
let args = args.split(" ").collect::<Vec<&str>>();
|
let args = args.split(" ").collect::<Vec<&str>>();
|
||||||
@ -121,89 +143,7 @@ fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepare_message(context: Arc<Context>, message: &str) -> String {
|
||||||
pub fn print_console(ctx: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
|
|
||||||
let (width, height) = terminal::size()?;
|
|
||||||
let (width, height) = (width as usize, height as usize);
|
|
||||||
|
|
||||||
let mut messages = messages
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|o| string_chunks(&o, width as usize - 1))
|
|
||||||
.map(|o| (o.0.white().blink().to_string(), o.1))
|
|
||||||
.collect::<Vec<(String, usize)>>();
|
|
||||||
|
|
||||||
let messages_size = if messages.len() >= height {
|
|
||||||
messages.len()-height
|
|
||||||
} else {
|
|
||||||
for _ in 0..height-messages.len() {
|
|
||||||
messages.insert(0, (String::new(), 0));
|
|
||||||
}
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size);
|
|
||||||
let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1;
|
|
||||||
|
|
||||||
let messages = if height < messages.len() {
|
|
||||||
if scroll < messages.len() - height {
|
|
||||||
messages[
|
|
||||||
messages.len()-height-scroll..
|
|
||||||
messages.len()-scroll
|
|
||||||
].to_vec()
|
|
||||||
} else {
|
|
||||||
if scroll < messages.len() {
|
|
||||||
messages[
|
|
||||||
0..
|
|
||||||
messages.len()-scroll
|
|
||||||
].to_vec()
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
messages
|
|
||||||
};
|
|
||||||
|
|
||||||
let formatted_messages = if ctx.disable_formatting {
|
|
||||||
messages
|
|
||||||
.into_iter()
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
} else {
|
|
||||||
messages
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, (s, l))| {
|
|
||||||
format!("{}{}{}",
|
|
||||||
s,
|
|
||||||
" ".repeat(width - 1 - l),
|
|
||||||
if i == scroll_f {
|
|
||||||
"▐".bright_yellow()
|
|
||||||
} else {
|
|
||||||
"▕".yellow()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
let text = format!(
|
|
||||||
"{}\r\n{} {}",
|
|
||||||
formatted_messages.join("\r\n"),
|
|
||||||
">".bright_yellow(),
|
|
||||||
input
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut out = stdout().lock();
|
|
||||||
write!(out, "{}", text)?;
|
|
||||||
out.flush()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn prepare_message(context: Arc<Context>, message: &str) -> String {
|
|
||||||
format!("{}{}{}",
|
format!("{}{}{}",
|
||||||
if !context.disable_hiding_ip {
|
if !context.disable_hiding_ip {
|
||||||
"\r\x07"
|
"\r\x07"
|
||||||
@ -229,8 +169,28 @@ fn prepare_message(context: Arc<Context>, message: &str) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
if message.starts_with("/") && !ctx.disable_commands {
|
||||||
|
on_command(ctx.clone(), &message)?;
|
||||||
|
} else {
|
||||||
|
let message = prepare_message(
|
||||||
|
ctx.clone(),
|
||||||
|
&ctx.message_format
|
||||||
|
.replace("{name}", &ctx.name)
|
||||||
|
.replace("{text}", &message)
|
||||||
|
);
|
||||||
|
|
||||||
fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
|
if ctx.enable_auth {
|
||||||
|
send_message_spoof_auth(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
|
||||||
|
} else {
|
||||||
|
send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
|
||||||
let message = sanitize_text(&message);
|
let message = sanitize_text(&message);
|
||||||
|
|
||||||
let date = DATE_REGEX.captures(&message)?;
|
let date = DATE_REGEX.captures(&message)?;
|
||||||
@ -282,7 +242,6 @@ fn format_message(ctx: Arc<Context>, message: String) -> Option<String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
||||||
for (re, color) in COLORED_USERNAMES.iter() {
|
for (re, color) in COLORED_USERNAMES.iter() {
|
||||||
if let Some(captures) = re.captures(message) {
|
if let Some(captures) = re.captures(message) {
|
||||||
@ -291,278 +250,3 @@ fn find_username_color(message: &str) -> Option<(String, String, Color)> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn replace_input(cursor: usize, len: usize, text: &str) {
|
|
||||||
let spaces = if text.chars().count() < len {
|
|
||||||
len-text.chars().count()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
write!(stdout(),
|
|
||||||
"{}{}{}{}",
|
|
||||||
MoveLeft(1).to_string().repeat(cursor),
|
|
||||||
text,
|
|
||||||
" ".repeat(spaces),
|
|
||||||
MoveLeft(1).to_string().repeat(spaces)
|
|
||||||
).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replace_input_left(cursor: usize, len: usize, text: &str, left: usize) {
|
|
||||||
let spaces = if text.chars().count() < len {
|
|
||||||
len-text.chars().count()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
write!(stdout(),
|
|
||||||
"{}{}{}{}",
|
|
||||||
MoveLeft(1).to_string().repeat(cursor),
|
|
||||||
text,
|
|
||||||
" ".repeat(spaces),
|
|
||||||
MoveLeft(1).to_string().repeat(len-left)
|
|
||||||
).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn poll_events(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut history: Vec<String> = vec![String::new()];
|
|
||||||
let mut history_cursor: usize = 0;
|
|
||||||
let mut cursor: usize = 0;
|
|
||||||
|
|
||||||
let input = ctx.input.clone();
|
|
||||||
let messages = ctx.messages.clone();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue }
|
|
||||||
|
|
||||||
let event = match event::read() {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(_) => { continue },
|
|
||||||
};
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::Key(event) => {
|
|
||||||
match event.code {
|
|
||||||
KeyCode::Enter => {
|
|
||||||
let message = input.read().unwrap().clone();
|
|
||||||
|
|
||||||
if !message.is_empty() {
|
|
||||||
replace_input(cursor, message.chars().count(), "");
|
|
||||||
input.write().unwrap().clear();
|
|
||||||
|
|
||||||
cursor = 0;
|
|
||||||
|
|
||||||
history.push(String::new());
|
|
||||||
history_cursor = history.len()-1;
|
|
||||||
|
|
||||||
if message.starts_with("/") && !ctx.disable_commands {
|
|
||||||
on_command(ctx.clone(), &message)?;
|
|
||||||
} else {
|
|
||||||
let message = prepare_message(
|
|
||||||
ctx.clone(),
|
|
||||||
&ctx.message_format
|
|
||||||
.replace("{name}", &ctx.name)
|
|
||||||
.replace("{text}", &message)
|
|
||||||
);
|
|
||||||
|
|
||||||
if ctx.enable_auth {
|
|
||||||
send_message_auth(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
|
|
||||||
} else {
|
|
||||||
send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
""
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if cursor == 0 || !(0..=history[history_cursor].len()).contains(&(cursor)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let len = input.read().unwrap().chars().count();
|
|
||||||
let i = char_index_to_byte_index(&history[history_cursor], cursor-1);
|
|
||||||
history[history_cursor].remove(i);
|
|
||||||
*input.write().unwrap() = history[history_cursor].clone();
|
|
||||||
replace_input_left(cursor, len, &history[history_cursor], cursor-1);
|
|
||||||
cursor -= 1;
|
|
||||||
}
|
|
||||||
KeyCode::Delete => {
|
|
||||||
if cursor == 0 || !(0..history[history_cursor].len()).contains(&(cursor)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let len = input.read().unwrap().chars().count();
|
|
||||||
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
|
||||||
history[history_cursor].remove(i);
|
|
||||||
*input.write().unwrap() = history[history_cursor].clone();
|
|
||||||
replace_input_left(cursor, len, &history[history_cursor], cursor);
|
|
||||||
}
|
|
||||||
KeyCode::Esc => {
|
|
||||||
on_close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Up | KeyCode::Down => {
|
|
||||||
history_cursor = if event.code == KeyCode::Up {
|
|
||||||
max(history_cursor, 1) - 1
|
|
||||||
} else {
|
|
||||||
min(history_cursor + 1, history.len() - 1)
|
|
||||||
};
|
|
||||||
let len = input.read().unwrap().chars().count();
|
|
||||||
*input.write().unwrap() = history[history_cursor].clone();
|
|
||||||
replace_input(cursor, len, &history[history_cursor]);
|
|
||||||
cursor = history[history_cursor].chars().count();
|
|
||||||
}
|
|
||||||
KeyCode::PageUp => {
|
|
||||||
let height = terminal::size().unwrap().1 as usize;
|
|
||||||
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+height, ctx.messages.messages().len()), Ordering::SeqCst);
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
KeyCode::PageDown => {
|
|
||||||
let height = terminal::size().unwrap().1 as usize;
|
|
||||||
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), height)-height, Ordering::SeqCst);
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
KeyCode::Left => {
|
|
||||||
if cursor > 0 {
|
|
||||||
cursor -= 1;
|
|
||||||
write!(stdout(), "{}", MoveLeft(1).to_string(), ).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Right => {
|
|
||||||
if cursor < history[history_cursor].len() {
|
|
||||||
cursor += 1;
|
|
||||||
write!(stdout(), "{}", MoveRight(1).to_string(), ).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
|
||||||
on_close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
|
||||||
history[history_cursor].insert(i, c);
|
|
||||||
input.write().unwrap().insert(i, c);
|
|
||||||
write!(stdout(), "{}{}",
|
|
||||||
history[history_cursor][i..].to_string(),
|
|
||||||
MoveLeft(1).to_string().repeat(history[history_cursor].chars().count()-cursor-1)
|
|
||||||
).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
cursor += 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::Paste(data) => {
|
|
||||||
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
|
||||||
history[history_cursor].insert_str(i, &data);
|
|
||||||
input.write().unwrap().insert_str(i, &data);
|
|
||||||
write!(stdout(), "{}{}",
|
|
||||||
history[history_cursor][cursor..].to_string(),
|
|
||||||
MoveLeft(1).to_string().repeat(history[history_cursor].len()-cursor-1)
|
|
||||||
).unwrap();
|
|
||||||
stdout().lock().flush().unwrap();
|
|
||||||
cursor += data.len();
|
|
||||||
},
|
|
||||||
Event::Resize(_, _) => {
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
)?;
|
|
||||||
},
|
|
||||||
Event::Mouse(data) => {
|
|
||||||
match data.kind {
|
|
||||||
MouseEventKind::ScrollUp => {
|
|
||||||
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+3, ctx.messages.messages().len()), Ordering::SeqCst);
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
)?;
|
|
||||||
},
|
|
||||||
MouseEventKind::ScrollDown => {
|
|
||||||
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst);
|
|
||||||
print_console(
|
|
||||||
ctx.clone(),
|
|
||||||
messages.messages(),
|
|
||||||
&input.read().unwrap()
|
|
||||||
)?;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
|
||||||
match read_messages(
|
|
||||||
&mut connect(&ctx.host, ctx.enable_ssl)?,
|
|
||||||
ctx.max_messages,
|
|
||||||
ctx.messages.packet_size(),
|
|
||||||
!ctx.enable_ssl,
|
|
||||||
ctx.enable_chunked
|
|
||||||
) {
|
|
||||||
Ok(Some((messages, size))) => {
|
|
||||||
let messages: Vec<String> = if ctx.disable_formatting {
|
|
||||||
messages
|
|
||||||
} else {
|
|
||||||
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
if ctx.enable_chunked {
|
|
||||||
ctx.messages.append(messages.clone(), size);
|
|
||||||
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
|
|
||||||
} else {
|
|
||||||
ctx.messages.update(messages.clone(), size);
|
|
||||||
print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(ctx.update_time as u64));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_close() {
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
execute!(stdout(), event::DisableMouseCapture).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_main_loop(ctx: Arc<Context>) {
|
|
||||||
enable_raw_mode().unwrap();
|
|
||||||
execute!(stdout(), event::EnableMouseCapture).unwrap();
|
|
||||||
|
|
||||||
thread::spawn({
|
|
||||||
let ctx = ctx.clone();
|
|
||||||
|
|
||||||
move || {
|
|
||||||
loop {
|
|
||||||
recv_tick(ctx.clone()).expect("Error printing console");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
poll_events(ctx).expect("Error while polling events");
|
|
||||||
}
|
|
67
src/chat/minimal_tui.rs
Normal file
67
src/chat/minimal_tui.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{
|
||||||
|
config::Context,
|
||||||
|
proto::{connect, read_messages},
|
||||||
|
util::get_input
|
||||||
|
}, format_message, on_send_message
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn run_main_loop(ctx: Arc<Context>) {
|
||||||
|
loop {
|
||||||
|
match connect(&ctx.host, ctx.enable_ssl) {
|
||||||
|
Ok(mut stream) => {
|
||||||
|
match read_messages(
|
||||||
|
&mut stream,
|
||||||
|
ctx.max_messages,
|
||||||
|
ctx.messages.packet_size(),
|
||||||
|
!ctx.enable_ssl,
|
||||||
|
ctx.enable_chunked
|
||||||
|
) {
|
||||||
|
Ok(Some((messages, size))) => {
|
||||||
|
let messages: Vec<String> = if ctx.disable_formatting {
|
||||||
|
messages
|
||||||
|
} else {
|
||||||
|
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
if ctx.enable_chunked {
|
||||||
|
ctx.messages.append_and_store(messages.clone(), size);
|
||||||
|
} else {
|
||||||
|
ctx.messages.update(messages.clone(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let msg = format!("Connect error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}\n{} ",
|
||||||
|
ctx.messages.messages()
|
||||||
|
.into_iter()
|
||||||
|
.map(|o| o.white().blink().to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n"),
|
||||||
|
">".bright_yellow()
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(message) = get_input("") {
|
||||||
|
if let Err(e) = on_send_message(ctx.clone(), &message) {
|
||||||
|
let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
377
src/chat/pretty_tui.rs
Normal file
377
src/chat/pretty_tui.rs
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
use crossterm::{
|
||||||
|
cursor::{MoveLeft, MoveRight},
|
||||||
|
event::{self, Event, KeyCode, KeyModifiers, MouseEventKind},
|
||||||
|
execute,
|
||||||
|
terminal::{self, disable_raw_mode, enable_raw_mode}
|
||||||
|
};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cmp::{max, min},
|
||||||
|
error::Error, io::{stdout, Write},
|
||||||
|
sync::{atomic::Ordering, Arc},
|
||||||
|
thread,
|
||||||
|
time::Duration
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{
|
||||||
|
config::Context, proto::{connect, read_messages}, util::{char_index_to_byte_index, string_chunks}
|
||||||
|
}, format_message, on_send_message
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
pub fn print_console(ctx: Arc<Context>, messages: Vec<String>, input: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let (width, height) = terminal::size()?;
|
||||||
|
let (width, height) = (width as usize, height as usize);
|
||||||
|
|
||||||
|
let mut messages = messages
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|o| string_chunks(&o, width as usize - 1))
|
||||||
|
.map(|o| (o.0.white().blink().to_string(), o.1))
|
||||||
|
.collect::<Vec<(String, usize)>>();
|
||||||
|
|
||||||
|
let messages_size = if messages.len() >= height {
|
||||||
|
messages.len()-height
|
||||||
|
} else {
|
||||||
|
for _ in 0..height-messages.len() {
|
||||||
|
messages.insert(0, (String::new(), 0));
|
||||||
|
}
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size);
|
||||||
|
let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1;
|
||||||
|
|
||||||
|
let messages = if height < messages.len() {
|
||||||
|
if scroll < messages.len() - height {
|
||||||
|
messages[
|
||||||
|
messages.len()-height-scroll..
|
||||||
|
messages.len()-scroll
|
||||||
|
].to_vec()
|
||||||
|
} else {
|
||||||
|
if scroll < messages.len() {
|
||||||
|
messages[
|
||||||
|
0..
|
||||||
|
messages.len()-scroll
|
||||||
|
].to_vec()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messages
|
||||||
|
};
|
||||||
|
|
||||||
|
let formatted_messages = if ctx.disable_formatting {
|
||||||
|
messages
|
||||||
|
.into_iter()
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
} else {
|
||||||
|
messages
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (s, l))| {
|
||||||
|
format!("{}{}{}",
|
||||||
|
s,
|
||||||
|
" ".repeat(width - 1 - l),
|
||||||
|
if i == scroll_f {
|
||||||
|
"▐".bright_yellow()
|
||||||
|
} else {
|
||||||
|
"▕".yellow()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = format!(
|
||||||
|
"{}\r\n{} {}",
|
||||||
|
formatted_messages.join("\r\n"),
|
||||||
|
">".bright_yellow(),
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut out = stdout().lock();
|
||||||
|
write!(out, "{}", text)?;
|
||||||
|
out.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn replace_input(cursor: usize, len: usize, text: &str) {
|
||||||
|
let spaces = if text.chars().count() < len {
|
||||||
|
len-text.chars().count()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
write!(stdout(),
|
||||||
|
"{}{}{}{}",
|
||||||
|
MoveLeft(1).to_string().repeat(cursor),
|
||||||
|
text,
|
||||||
|
" ".repeat(spaces),
|
||||||
|
MoveLeft(1).to_string().repeat(spaces)
|
||||||
|
).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_input_left(cursor: usize, len: usize, text: &str, left: usize) {
|
||||||
|
let spaces = if text.chars().count() < len {
|
||||||
|
len-text.chars().count()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
write!(stdout(),
|
||||||
|
"{}{}{}{}",
|
||||||
|
MoveLeft(1).to_string().repeat(cursor),
|
||||||
|
text,
|
||||||
|
" ".repeat(spaces),
|
||||||
|
MoveLeft(1).to_string().repeat(len-left)
|
||||||
|
).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_events(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut history: Vec<String> = vec![String::new()];
|
||||||
|
let mut history_cursor: usize = 0;
|
||||||
|
let mut cursor: usize = 0;
|
||||||
|
|
||||||
|
let input = ctx.input.clone();
|
||||||
|
let messages = ctx.messages.clone();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue }
|
||||||
|
|
||||||
|
let event = match event::read() {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(_) => { continue },
|
||||||
|
};
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Key(event) => {
|
||||||
|
match event.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let message = input.read().unwrap().clone();
|
||||||
|
|
||||||
|
if !message.is_empty() {
|
||||||
|
replace_input(cursor, message.chars().count(), "");
|
||||||
|
input.write().unwrap().clear();
|
||||||
|
|
||||||
|
cursor = 0;
|
||||||
|
|
||||||
|
history.push(String::new());
|
||||||
|
history_cursor = history.len()-1;
|
||||||
|
|
||||||
|
if let Err(e) = on_send_message(ctx.clone(), &message) {
|
||||||
|
let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
""
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if cursor == 0 || !(0..=history[history_cursor].len()).contains(&(cursor)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let len = input.read().unwrap().chars().count();
|
||||||
|
let i = char_index_to_byte_index(&history[history_cursor], cursor-1);
|
||||||
|
history[history_cursor].remove(i);
|
||||||
|
*input.write().unwrap() = history[history_cursor].clone();
|
||||||
|
replace_input_left(cursor, len, &history[history_cursor], cursor-1);
|
||||||
|
cursor -= 1;
|
||||||
|
}
|
||||||
|
KeyCode::Delete => {
|
||||||
|
if cursor == 0 || !(0..history[history_cursor].len()).contains(&(cursor)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let len = input.read().unwrap().chars().count();
|
||||||
|
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
||||||
|
history[history_cursor].remove(i);
|
||||||
|
*input.write().unwrap() = history[history_cursor].clone();
|
||||||
|
replace_input_left(cursor, len, &history[history_cursor], cursor);
|
||||||
|
}
|
||||||
|
KeyCode::Esc => {
|
||||||
|
on_close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
KeyCode::Up | KeyCode::Down => {
|
||||||
|
history_cursor = if event.code == KeyCode::Up {
|
||||||
|
max(history_cursor, 1) - 1
|
||||||
|
} else {
|
||||||
|
min(history_cursor + 1, history.len() - 1)
|
||||||
|
};
|
||||||
|
let len = input.read().unwrap().chars().count();
|
||||||
|
*input.write().unwrap() = history[history_cursor].clone();
|
||||||
|
replace_input(cursor, len, &history[history_cursor]);
|
||||||
|
cursor = history[history_cursor].chars().count();
|
||||||
|
}
|
||||||
|
KeyCode::PageUp => {
|
||||||
|
let height = terminal::size().unwrap().1 as usize;
|
||||||
|
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+height, ctx.messages.messages().len()), Ordering::SeqCst);
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
KeyCode::PageDown => {
|
||||||
|
let height = terminal::size().unwrap().1 as usize;
|
||||||
|
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), height)-height, Ordering::SeqCst);
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
if cursor > 0 {
|
||||||
|
cursor -= 1;
|
||||||
|
write!(stdout(), "{}", MoveLeft(1).to_string(), ).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
if cursor < history[history_cursor].len() {
|
||||||
|
cursor += 1;
|
||||||
|
write!(stdout(), "{}", MoveRight(1).to_string(), ).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
if event.modifiers.contains(KeyModifiers::CONTROL) && "zxcZXCячсЯЧС".contains(c) {
|
||||||
|
on_close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
||||||
|
history[history_cursor].insert(i, c);
|
||||||
|
input.write().unwrap().insert(i, c);
|
||||||
|
write!(stdout(), "{}{}",
|
||||||
|
history[history_cursor][i..].to_string(),
|
||||||
|
MoveLeft(1).to_string().repeat(history[history_cursor].chars().count()-cursor-1)
|
||||||
|
).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
cursor += 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::Paste(data) => {
|
||||||
|
let i = char_index_to_byte_index(&history[history_cursor], cursor);
|
||||||
|
history[history_cursor].insert_str(i, &data);
|
||||||
|
input.write().unwrap().insert_str(i, &data);
|
||||||
|
write!(stdout(), "{}{}",
|
||||||
|
history[history_cursor][cursor..].to_string(),
|
||||||
|
MoveLeft(1).to_string().repeat(history[history_cursor].len()-cursor-1)
|
||||||
|
).unwrap();
|
||||||
|
stdout().lock().flush().unwrap();
|
||||||
|
cursor += data.len();
|
||||||
|
},
|
||||||
|
Event::Resize(_, _) => {
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
Event::Mouse(data) => {
|
||||||
|
match data.kind {
|
||||||
|
MouseEventKind::ScrollUp => {
|
||||||
|
ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+3, ctx.messages.messages().len()), Ordering::SeqCst);
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
MouseEventKind::ScrollDown => {
|
||||||
|
ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst);
|
||||||
|
print_console(
|
||||||
|
ctx.clone(),
|
||||||
|
messages.messages(),
|
||||||
|
&input.read().unwrap()
|
||||||
|
)?;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
||||||
|
match read_messages(
|
||||||
|
&mut connect(&ctx.host, ctx.enable_ssl)?,
|
||||||
|
ctx.max_messages,
|
||||||
|
ctx.messages.packet_size(),
|
||||||
|
!ctx.enable_ssl,
|
||||||
|
ctx.enable_chunked
|
||||||
|
) {
|
||||||
|
Ok(Some((messages, size))) => {
|
||||||
|
let messages: Vec<String> = if ctx.disable_formatting {
|
||||||
|
messages
|
||||||
|
} else {
|
||||||
|
messages.into_iter().flat_map(|o| format_message(ctx.clone(), o)).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
if ctx.enable_chunked {
|
||||||
|
ctx.messages.append_and_store(messages.clone(), size);
|
||||||
|
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
|
||||||
|
} else {
|
||||||
|
ctx.messages.update(messages.clone(), size);
|
||||||
|
print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(ctx.update_time as u64));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_close() {
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
execute!(stdout(), event::DisableMouseCapture).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_main_loop(ctx: Arc<Context>) {
|
||||||
|
enable_raw_mode().unwrap();
|
||||||
|
execute!(stdout(), event::EnableMouseCapture).unwrap();
|
||||||
|
|
||||||
|
thread::spawn({
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
|
||||||
|
move || {
|
||||||
|
loop {
|
||||||
|
if let Err(e) = recv_tick(ctx.clone()) {
|
||||||
|
let msg = format!("Print messages error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(e) = poll_events(ctx.clone()) {
|
||||||
|
let msg = format!("Poll events error: {}", e.to_string()).bright_red().to_string();
|
||||||
|
ctx.messages.append(vec![msg]);
|
||||||
|
let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap());
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
use std::sync::{atomic::AtomicUsize, Arc, RwLock};
|
use std::{str::FromStr, sync::{atomic::AtomicUsize, Arc, RwLock}};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration};
|
use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use homedir::my_home;
|
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use serde_yml;
|
use serde_yml;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@ -114,18 +113,26 @@ pub fn load_config(path: PathBuf) -> Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_config_path() -> PathBuf {
|
pub fn get_config_path() -> PathBuf {
|
||||||
|
let home_dir = PathBuf::from_str(".").ok();
|
||||||
|
|
||||||
|
#[cfg(feature = "homedir")]
|
||||||
|
let home_dir = {
|
||||||
|
use homedir::my_home;
|
||||||
|
my_home().ok().flatten()
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let config_path = Path::new("config.yml").to_path_buf();
|
let config_path = Path::new("config.yml").to_path_buf();
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let config_path = {
|
let config_path = {
|
||||||
let home_dir = my_home().ok().flatten().expect("Config find path error");
|
let home_dir = home_dir.expect("Config find path error");
|
||||||
home_dir.join(".config").join("bRAC").join("config.yml")
|
home_dir.join(".config").join("bRAC").join("config.yml")
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let config_path = {
|
let config_path = {
|
||||||
let home_dir = my_home().ok().flatten().expect("Config find path error");
|
let home_dir = home_dir.expect("Config find path error");
|
||||||
home_dir.join(".config").join("bRAC").join("config.yml")
|
home_dir.join(".config").join("bRAC").join("config.yml")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#[warn(non_snake_case)]
|
||||||
|
|
||||||
|
mod proto;
|
||||||
|
|
||||||
|
pub use proto::*;
|
72
src/proto.rs
72
src/proto.rs
@ -1,10 +1,12 @@
|
|||||||
use std::{error::Error, fmt::Debug, io::{Read, Write}, net::TcpStream};
|
use std::{error::Error, fmt::Debug, io::{Read, Write}, net::TcpStream};
|
||||||
|
|
||||||
use native_tls::TlsConnector;
|
|
||||||
|
|
||||||
pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug {}
|
pub trait RacStream: Read + Write + Unpin + Send + Sync + Debug {}
|
||||||
impl<T: Read + Write + Unpin + Send + Sync + Debug> RacStream for T {}
|
impl<T: Read + Write + Unpin + Send + Sync + Debug> RacStream for T {}
|
||||||
|
|
||||||
|
/// Create RAC connection (also you can just TcpStream::connect)
|
||||||
|
///
|
||||||
|
/// host - host string, example: "example.com:12345", "example.com" (default port is 42666)
|
||||||
|
/// ssl - wrap with ssl client, write false if you dont know what it is
|
||||||
pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Error>> {
|
pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Error>> {
|
||||||
let host = if host.contains(":") {
|
let host = if host.contains(":") {
|
||||||
host.to_string()
|
host.to_string()
|
||||||
@ -12,27 +14,59 @@ pub fn connect(host: &str, ssl: bool) -> Result<Box<dyn RacStream>, Box<dyn Erro
|
|||||||
format!("{host}:42666")
|
format!("{host}:42666")
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(if ssl {
|
#[cfg(feature = "ssl")]
|
||||||
|
{
|
||||||
|
use native_tls::TlsConnector;
|
||||||
|
|
||||||
|
if ssl {
|
||||||
let ip: String = host.split_once(":")
|
let ip: String = host.split_once(":")
|
||||||
.map(|o| o.0.to_string())
|
.map(|o| o.0.to_string())
|
||||||
.unwrap_or(host.clone());
|
.unwrap_or(host.clone());
|
||||||
|
|
||||||
Box::new(TlsConnector::builder()
|
return Ok(Box::new(TlsConnector::builder()
|
||||||
.danger_accept_invalid_certs(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.danger_accept_invalid_hostnames(true)
|
.danger_accept_invalid_hostnames(true)
|
||||||
.build()?
|
.build()?
|
||||||
.connect(&ip, connect(&host, false)?)?)
|
.connect(&ip, connect(&host, false)?)?))
|
||||||
} else {
|
}
|
||||||
Box::new(TcpStream::connect(host)?)
|
}
|
||||||
})
|
|
||||||
|
Ok(Box::new(TcpStream::connect(host)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box<dyn Error>> {
|
/// Send message
|
||||||
|
///
|
||||||
|
/// stream - any stream that can be written to
|
||||||
|
/// message - message text
|
||||||
|
pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
stream.write_all(format!("\x01{message}").as_bytes())?;
|
stream.write_all(format!("\x01{message}").as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_message_auth(stream: &mut (impl Read + Write), message: &str) -> Result<(), Box<dyn Error>> {
|
/// Register user
|
||||||
|
///
|
||||||
|
/// stream - any stream that can be written to
|
||||||
|
/// name - user name
|
||||||
|
/// password - user password
|
||||||
|
pub fn register_user(stream: &mut impl Write, name: &str, password: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send message with auth
|
||||||
|
///
|
||||||
|
/// stream - any stream that can be written to
|
||||||
|
/// message - message text
|
||||||
|
/// name - user name
|
||||||
|
/// password - user password
|
||||||
|
pub fn send_message_auth(stream: &mut impl Write, name: &str, password: &str, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
Ok(stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send message with fake auth
|
||||||
|
///
|
||||||
|
/// im rly bored to explain all of this so if you want to know just check sources
|
||||||
|
pub fn send_message_spoof_auth(stream: &mut (impl Write + Read), message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
|
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
|
||||||
|
|
||||||
stream.write_all(format!("\x02{name}\n{name}\n{message}").as_bytes())?;
|
stream.write_all(format!("\x02{name}\n{name}\n{message}").as_bytes())?;
|
||||||
@ -42,18 +76,14 @@ pub fn send_message_auth(stream: &mut (impl Read + Write), message: &str) -> Res
|
|||||||
let name = format!("\x1f{name}");
|
let name = format!("\x1f{name}");
|
||||||
register_user(stream, &name, &name)?;
|
register_user(stream, &name, &name)?;
|
||||||
let message = format!("{name}> {message}");
|
let message = format!("{name}> {message}");
|
||||||
send_message_auth(stream, &message)
|
send_message_spoof_auth(stream, &message)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_user(stream: &mut (impl Read + Write), name: &str, password: &str) -> Result<(), Box<dyn Error>> {
|
/// Skip null bytes and return first non-null byte
|
||||||
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
|
pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
||||||
loop {
|
loop {
|
||||||
let mut buf = vec![0; 1];
|
let mut buf = vec![0; 1];
|
||||||
stream.read_exact(&mut buf)?;
|
stream.read_exact(&mut buf)?;
|
||||||
@ -63,6 +93,14 @@ fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read messages
|
||||||
|
///
|
||||||
|
/// max_messages - max messages in list
|
||||||
|
/// last_size - last returned packet size
|
||||||
|
/// start_null - start with skipping null bytes
|
||||||
|
/// chunked - is chunked reading enabled
|
||||||
|
///
|
||||||
|
/// returns (messages, packet size)
|
||||||
pub fn read_messages(
|
pub fn read_messages(
|
||||||
stream: &mut (impl Read + Write),
|
stream: &mut (impl Read + Write),
|
||||||
max_messages: usize,
|
max_messages: usize,
|
||||||
|
@ -65,9 +65,12 @@ pub fn sanitize_text(input: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_input(prompt: impl ToString) -> Option<String> {
|
pub fn get_input(prompt: impl ToString) -> Option<String> {
|
||||||
|
let prompt = prompt.to_string();
|
||||||
|
if !prompt.is_empty() {
|
||||||
let mut out = stdout().lock();
|
let mut out = stdout().lock();
|
||||||
out.write_all(prompt.to_string().as_bytes()).ok()?;
|
out.write_all(prompt.as_bytes()).ok()?;
|
||||||
out.flush().ok()?;
|
out.flush().ok()?;
|
||||||
|
}
|
||||||
let input = stdin().lock().lines().next()
|
let input = stdin().lock().lines().next()
|
||||||
.map(|o| o.ok())
|
.map(|o| o.ok())
|
||||||
.flatten()?;
|
.flatten()?;
|
||||||
|
Loading…
Reference in New Issue
Block a user