Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
f4aa86068f | |||
8c7ad0dedb | |||
29cb5e246d | |||
d408ec7272 | |||
3453ca326c | |||
a9fb66a8fd | |||
388bfb7672 | |||
![]() |
76e30c9a08 | ||
![]() |
a9b32a2001 | ||
ae3770dcdf | |||
4f190af7fc | |||
46e74c9bf7 | |||
0042c5a3e1 | |||
92a896e46f | |||
f3b6cbd01c | |||
956612b192 | |||
16d7cf0b29 | |||
9a191eec78 | |||
bbbcb93a7f | |||
5b23a3bd70 | |||
c8b2c7e541 | |||
![]() |
80e7b8c506 | ||
![]() |
aaee249f56 | ||
![]() |
9675dfe87f | ||
![]() |
32cf3839bf | ||
![]() |
5029b51cf6 | ||
![]() |
c9673807d9 | ||
![]() |
c3982a211c | ||
![]() |
3a789aae20 | ||
![]() |
b19b94b4d9 | ||
![]() |
b58aa445ae | ||
955da8a7b5 | |||
7ba01992a8 | |||
29764b0344 | |||
a102d0d260 | |||
![]() |
4ee854d8af | ||
![]() |
5cbc2351ed | ||
d2ad18ba89 | |||
78e0caa641 | |||
0123589ec7 | |||
05f0ec22a8 | |||
![]() |
0b625c33b6 | ||
7406f2cd96 | |||
e582677bce | |||
23d8ebffb5 | |||
f5c33ca31c | |||
d692b8dc99 | |||
5d9fdd1719 | |||
2accb6e73d | |||
c1e9d00d3a | |||
3e75662969 | |||
492668bfd5 | |||
f615811f90 | |||
374dab3951 | |||
0260206500 | |||
9c29d4e742 | |||
efc202153f | |||
97948aa420 | |||
01b3643c14 | |||
9ef7560963 | |||
b206f18829 | |||
2c35c0a18f | |||
89147235dc | |||
d390c1f28b | |||
8c5dad7aa5 |
3
.gitattributes
vendored
@ -1,3 +0,0 @@
|
|||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.ico filter=lfs diff=lfs merge=lfs -text
|
|
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
||||||
|
}
|
779
Cargo.lock
generated
17
Cargo.toml
@ -12,19 +12,22 @@ homedir = "0.3.4"
|
|||||||
native-tls = "0.2.14"
|
native-tls = "0.2.14"
|
||||||
clap = { version = "4.5.36", features = ["derive"] }
|
clap = { version = "4.5.36", features = ["derive"] }
|
||||||
serde = { version = "1.0.219", features = ["serde_derive"] }
|
serde = { version = "1.0.219", features = ["serde_derive"] }
|
||||||
gtk4 = { version = "0.9.6", features = [ "v4_10" ] }
|
gtk4 = { version = "0.9.6", features = [ "v4_12" ], optional = true }
|
||||||
chrono = "0.4.40"
|
chrono = "0.4.40"
|
||||||
serde_default = "0.2.0"
|
serde_default = "0.2.0"
|
||||||
socks = "0.3.4"
|
socks = "0.3.4"
|
||||||
libnotify = { version = "1.0.3", optional = true }
|
libnotify = { version = "1.0.3", optional = true }
|
||||||
|
notify-rust = { version = "4.11.7", optional = true }
|
||||||
gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE
|
gdk-pixbuf = { version = "0.3.0", optional = true } # DO NOT UPDATE
|
||||||
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
|
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
|
||||||
tungstenite = "0.27.0"
|
tungstenite = "0.27.0"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
libnotify = ["dep:libnotify", "dep:gdk-pixbuf"]
|
|
||||||
winapi = ["dep:winapi"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
winresource = "0.1.20"
|
winresource = { version = "0.1.20", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["gtk"]
|
||||||
|
gtk = ["dep:gtk4"]
|
||||||
|
libnotify = ["dep:libnotify", "dep:gdk-pixbuf"]
|
||||||
|
notify-rust = ["dep:notify-rust"]
|
||||||
|
winapi = ["dep:winapi", "dep:winresource"]
|
2
Cross.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[target.x86_64-pc-windows-gnu]
|
||||||
|
image = "mglolenstine/gtk4-cross:rust-gtk-4.12"
|
33
Makefile
@ -1,15 +1,42 @@
|
|||||||
.PHONY: clean install uninstall
|
.PHONY: clean install uninstall build_linux build_windows build_all
|
||||||
|
|
||||||
|
TARGETS = \
|
||||||
|
i686-unknown-linux-gnu \
|
||||||
|
i686-unknown-linux-musl \
|
||||||
|
x86_64-unknown-linux-none \
|
||||||
|
x86_64-unknown-linux-gnu \
|
||||||
|
x86_64-unknown-linux-musl \
|
||||||
|
aarch64-unknown-linux-gnu \
|
||||||
|
aarch64-unknown-linux-musl
|
||||||
|
|
||||||
install: target/release/bRAC
|
install: target/release/bRAC
|
||||||
|
mkdir -p ~/.local
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
mkdir -p ~/.local/share
|
||||||
cp $< ~/.local/bin/bRAC
|
cp $< ~/.local/bin/bRAC
|
||||||
chmod +x ~/.local/bin/bRAC
|
chmod +x ~/.local/bin/bRAC
|
||||||
mkdir ~/.local/share/bRAC -p
|
mkdir ~/.local/share/bRAC -p
|
||||||
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
|
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
|
||||||
cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop
|
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -rf ~/.config/bRAC ~/.local/share/bRAC
|
rm -rf ~/.config/bRAC ~/.local/share/bRAC
|
||||||
rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop
|
rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop
|
||||||
target/release/bRAC:
|
target/release/bRAC:
|
||||||
cargo build -r
|
cargo build -r
|
||||||
|
|
||||||
|
build_all: build_linux build_windows
|
||||||
|
|
||||||
|
build_linux:
|
||||||
|
mkdir -p build
|
||||||
|
mkdir -p build/linux
|
||||||
|
for target in $(TARGETS); do \
|
||||||
|
cargo build -r --target $$target; \
|
||||||
|
cp target/$$target/bRAC build/linux/$$target-bRAC; \
|
||||||
|
done
|
||||||
|
|
||||||
|
build_windows:
|
||||||
|
echo "Windows build is in development!!!"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cargo clean
|
cargo clean
|
||||||
|
rm -rf build
|
||||||
|
32
README.md
@ -11,15 +11,15 @@ better RAC client
|
|||||||
|
|
||||||
- gtk4 modern GUI
|
- gtk4 modern GUI
|
||||||
- RACv1.99.x and RACv2.0 compatible
|
- RACv1.99.x and RACv2.0 compatible
|
||||||
- WRAC compatible (can be enabled in the settings)
|
- WRAC compatible ([docs](docs/wrac.md))
|
||||||
- chat commands (type /help)
|
- chat commands (type /help)
|
||||||
- no ip and date visible for anyone
|
- uses tor proxy as default (wracs://meex.lol:11234)
|
||||||
- uses TOR proxy server by default (meex.lol:11234)
|
- no ip and date visible for anyone (almost)
|
||||||
- coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc)
|
- coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc.)
|
||||||
- many command-line options (--help)
|
- many command-line options (see --help)
|
||||||
- rich configuration (--config-path to get file path and --configure to edit)
|
- rich configuration (--config-path to get file path)
|
||||||
- RACS compatible (--enable-ssl or in --configure enable SSL)
|
- RACS/WRACS compatible (ex: wracs://meex.lol)
|
||||||
- chunked reading messages
|
- reading messages chunked (less traffic usage)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -39,6 +39,8 @@ better RAC client
|
|||||||
|
|
||||||
NO SOLUTION
|
NO SOLUTION
|
||||||
|
|
||||||
|
Read [compiling docs](docs/compiling.md) to build it manually.
|
||||||
|
|
||||||
### download binary
|
### download binary
|
||||||
|
|
||||||
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.
|
||||||
@ -59,6 +61,8 @@ cargo build -r # build release (target/release/bRAC)
|
|||||||
cargo run -r # build and run
|
cargo run -r # build and run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Read more about that on the [compiling docs](docs/compiling.md).
|
||||||
|
|
||||||
### nix package
|
### nix package
|
||||||
|
|
||||||
If you have Nix package manager installed, you can use:
|
If you have Nix package manager installed, you can use:
|
||||||
@ -82,16 +86,20 @@ messages starting with a slash are sent to chat only if the `--disable-commands`
|
|||||||
|
|
||||||
## docs
|
## docs
|
||||||
|
|
||||||
- [Message formats](docs/message_formats.md)
|
- [Compiling](docs/compiling.md)
|
||||||
|
- [User agents](docs/user_agents.md)
|
||||||
|
- [Using as crate](docs/crate.md)
|
||||||
- [Authenticated mode](docs/auth_mode.md)
|
- [Authenticated mode](docs/auth_mode.md)
|
||||||
- [Cross compile](docs/cross_compile.md)
|
- [WRAC protocol (v2.0)](docs/wrac.md)
|
||||||
|
- [About RAC URL](docs/url.md)
|
||||||
- [FAQ](docs/faq.md)
|
- [FAQ](docs/faq.md)
|
||||||
|
|
||||||
## see also
|
## see also
|
||||||
|
|
||||||
- [RAC-Hub - all about RAC protocol](https://the-stratosphere-solutions.github.io/RAC-Hub/)
|
- [RAC-Hub - all about RAC protocol](https://meexreay.github.io/RAC-Hub/)
|
||||||
- [RAC protocol (v2.0)](https://gitea.bedohswe.eu.org/pixtaded/crab#rac-protocol)
|
- [RAC protocol (v2.0)](https://gitea.bedohswe.eu.org/pixtaded/crab#rac-protocol)
|
||||||
- [CRAB - client & server for RAC](https://gitea.bedohswe.eu.org/pixtaded/crab)
|
- [CRAB - client & server for RAC](https://gitea.bedohswe.eu.org/pixtaded/crab)
|
||||||
- [Mefidroniy - client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client)
|
- [Mefidroniy - TUI client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client)
|
||||||
|
- [cRACk - client for RAC kettles](https://github.com/pansangg/cRACk)
|
||||||
- [AlmatyD - server for RACv1.0](https://gitea.bedohswe.eu.org/bedohswe/almatyd)
|
- [AlmatyD - server for RACv1.0](https://gitea.bedohswe.eu.org/bedohswe/almatyd)
|
||||||
- [RAC protocol (v1.0)](https://bedohswe.eu.org/text/rac/protocol.md.html)
|
- [RAC protocol (v1.0)](https://bedohswe.eu.org/text/rac/protocol.md.html)
|
||||||
|
20
build.rs
@ -1,16 +1,12 @@
|
|||||||
use {
|
use std::io;
|
||||||
std::{
|
|
||||||
env,
|
|
||||||
io,
|
|
||||||
},
|
|
||||||
winresource::WindowsResource,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
#[cfg(feature = "winapi")]
|
||||||
WindowsResource::new()
|
{
|
||||||
.set_icon("misc/icon.ico")
|
use {std::env, winresource::WindowsResource};
|
||||||
.compile()?;
|
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
||||||
|
WindowsResource::new().set_icon("misc/icon.ico").compile()?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
85
docs/compiling.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# How to compile it
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
1. Install [rustup](https://rustup.rs/)
|
||||||
|
2. Install [MSVC](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and run `rustup default stable-msvc`
|
||||||
|
3. Extract [GTK4 from gvsbuild](https://github.com/wingtk/gvsbuild/releases/latest) to `C:\gtk`
|
||||||
|
4. Update environment variables:
|
||||||
|
- Go to Start, search for 'Advanced system settings' (or click on Properties of My Computer in the Explorer, then you'll find 'Advanced system settings')
|
||||||
|
- Click 'Environment Variables...'
|
||||||
|
- Add `C:\gtk\lib\pkgconfig` to the PKG_CONFIG_PATH variable (or create one if doesnt exist)
|
||||||
|
- Add `C:\gtk\bin` to the PATH variable (or create one if doesnt exist)
|
||||||
|
- Add `C:\gtk\lib` to the Lib variable (or create one if doesnt exist)
|
||||||
|
- Apply and close the window (maybe restart PC)
|
||||||
|
5. Open the repository directory in console (download it from github or with `git clone https://github.com/MeexReay/bRAC.git`)
|
||||||
|
6. Run `cargo build -r -F winapi,notify-rust`
|
||||||
|
7. Done! Your finished binary is in the `target/release` folder.
|
||||||
|
|
||||||
|
## Linux / MacOS
|
||||||
|
|
||||||
|
1. Install `rust`, `openssl-dev`, `gtk4-dev` with your package manager
|
||||||
|
2. Open the repository directory in console (download it from github or with `git clone https://github.com/MeexReay/bRAC.git`)
|
||||||
|
3. Run `cargo build -r`
|
||||||
|
4. Done! Your finished binary is in the `target/release` folder.
|
||||||
|
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
### Black frame around the window
|
||||||
|
|
||||||
|
Black frame appears on connecting to the server or when bRAC just freezes. Be patient.
|
||||||
|
|
||||||
|
## MacOS
|
||||||
|
|
||||||
|
### Notifications dont work
|
||||||
|
|
||||||
|
There are two solutions:
|
||||||
|
|
||||||
|
- Switch to `libnotify`:
|
||||||
|
|
||||||
|
Add the feature `libnotify` to cargo: `cargo build -r -F libnotify`
|
||||||
|
|
||||||
|
- Switch to `notify-rust`:
|
||||||
|
|
||||||
|
Add the feature `notify-rust` to cargo: `cargo build -r -F notify-rust`
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
### Notifications dont work
|
||||||
|
|
||||||
|
There are two solutions:
|
||||||
|
|
||||||
|
- Switch to `libnotify`:
|
||||||
|
|
||||||
|
Just add the new feature to cargo: `cargo build -r -F libnotify` \
|
||||||
|
Libnotify sucks in many situations, but it always work
|
||||||
|
|
||||||
|
- Make a desktop file:
|
||||||
|
|
||||||
|
Enter the repository folder and run: `./misc/create-desktop.sh` \
|
||||||
|
You'll get a desktop file contents, just edit paths here and write it to a new file in the `~/.local/share/applications` or `/usr/share/applications`\
|
||||||
|
All of these, with adding icons and other, makes this command: `make install` (using `gnumake` package) \
|
||||||
|
But make sure, that you have `.local/bin` in the `PATH` variable, otherwise it won't work. \
|
||||||
|
Now, if you'll run with the desktop file, GNotifications will work perfectly.
|
||||||
|
|
||||||
|
# Cross-compiling
|
||||||
|
|
||||||
|
## From Linux to Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./misc/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## From NixOS to Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix-shell -p rustup gcc cargo-cross
|
||||||
|
rustup toolchain install stable
|
||||||
|
./misc/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## From Windows to Linux
|
||||||
|
|
||||||
|
That's your problem
|
60
docs/crate.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Using as crate
|
||||||
|
|
||||||
|
This article describes how to use the client as rust crate
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To use exact version:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies.bRAC]
|
||||||
|
git = "https://github.com/MeexReay/bRAC"
|
||||||
|
tag = "0.1.2+2.0"
|
||||||
|
default-features = false
|
||||||
|
```
|
||||||
|
|
||||||
|
To use with latest changes:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies.bRAC]
|
||||||
|
git = "https://github.com/MeexReay/bRAC"
|
||||||
|
default-features = false
|
||||||
|
```
|
||||||
|
|
||||||
|
`default-features = false` here removes GTK4 gui from installation.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
As the code structure was changed like about gazillion times,
|
||||||
|
you need to explore it yourself, if you are using an old version.
|
||||||
|
Here is example of usage on commit [80e7b8c](https://github.com/MeexReay/bRAC/commit/80e7b8c50642f9b76be06980305ed03253858d0c)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bRAC::proto::*;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut conn = connect("rac://meex.lol", None)?; // read docs/url.md
|
||||||
|
|
||||||
|
send_message(&mut conn, "<dude> hi RAC-loving kikes!")?;
|
||||||
|
register_user(&mut conn, "dude", "password")?;
|
||||||
|
send_message_auth(&mut conn, "dude", "password", "my auth message")?;
|
||||||
|
send_message_spoof_auth(&mut conn, "<dude> this message totally fucks auth system")?;
|
||||||
|
|
||||||
|
let (mut all_messages, last_size) = read_messages(&mut conn, 10, 0, false)?.unwrap(); // limits with 10 messages
|
||||||
|
|
||||||
|
/* imagine that new messages were written here */
|
||||||
|
|
||||||
|
let (mut new_messages, last_size) = read_messages(&mut conn, 10, last_size, true)?.unwrap(); // chunked reading!
|
||||||
|
|
||||||
|
all_messages.append(&mut new_messages);
|
||||||
|
|
||||||
|
println!("all_messages: {all_messages:?}. last_size: {last_size}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See more
|
||||||
|
|
||||||
|
- [rac-rs - A Rust client library for RAC protocol. (with async support)](https://github.com/kostya-zero/rac-rs)
|
@ -1,15 +0,0 @@
|
|||||||
# Cross-compile on Linux to Windows
|
|
||||||
|
|
||||||
## Install dev packages
|
|
||||||
|
|
||||||
on Nix:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix-shell -p pkgsCross.mingwW64.stdenv.cc pkgsCross.mingwW64.windows.pthreads pkgsCross.mingwW64.gtk4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
build build/windows-x86_64
|
|
||||||
```
|
|
@ -1,47 +0,0 @@
|
|||||||
# message formats
|
|
||||||
|
|
||||||
## types
|
|
||||||
|
|
||||||
### bRAC
|
|
||||||
|
|
||||||
this client
|
|
||||||
|
|
||||||
```yml
|
|
||||||
format: "리㹰<{name}> {text}"
|
|
||||||
regex: "\uB9AC\u3E70<(.*?)> (.*)"
|
|
||||||
color: "green"
|
|
||||||
```
|
|
||||||
|
|
||||||
### CRAB
|
|
||||||
|
|
||||||
[CRAB - client & server for RAC written in java](https://gitea.bedohswe.eu.org/pixtaded/crab)
|
|
||||||
|
|
||||||
```yml
|
|
||||||
format: "═══<{name}> {text}"
|
|
||||||
regex: "\u2550\u2550\u2550<(.*?)> (.*)"
|
|
||||||
color: "light red"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mefedroniy
|
|
||||||
|
|
||||||
[Mefidroniy - client for RAC written in rust](https://github.com/OctoBanon-Main/mefedroniy-client)
|
|
||||||
|
|
||||||
```yml
|
|
||||||
format: "°ʘ<{name}> {text}"
|
|
||||||
regex: "\u00B0\u0298<(.*?)> (.*)"
|
|
||||||
color: "light magenta"
|
|
||||||
```
|
|
||||||
|
|
||||||
### clRAC
|
|
||||||
|
|
||||||
official client
|
|
||||||
|
|
||||||
```yml
|
|
||||||
format: "<{name}> {text}"
|
|
||||||
regex: "<(.*?)> (.*)"
|
|
||||||
color: "cyan"
|
|
||||||
```
|
|
||||||
|
|
||||||
## developer notes
|
|
||||||
|
|
||||||
in auth-mode, there is must to be `> ` after name (`{name}> {text}`)
|
|
16
docs/url.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# How does RAC URL work?
|
||||||
|
|
||||||
|
RAC URL is used in sRAC and bRAC as the default way of specifying host, running a RAC or WRAC server.
|
||||||
|
|
||||||
|
Format of RAC URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
<protocol>://<address>[:<port>]
|
||||||
|
```
|
||||||
|
|
||||||
|
Protocol can be one of these:
|
||||||
|
|
||||||
|
| | **SSL** | **No SSL** |
|
||||||
|
| :--: | :--: | :--: |
|
||||||
|
| **WebSocket** | wracs:// | wrac:// |
|
||||||
|
| **No Websocket** | racs:// | rac:// |
|
20
docs/user_agents.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# user agents
|
||||||
|
|
||||||
|
User agents in RAC is the way how to get know from what client the message was sent. It works by just checking the message text throught regex.
|
||||||
|
|
||||||
|
## clients
|
||||||
|
|
||||||
|
Here are listed the most common clients, and their name colors in the chat.
|
||||||
|
|
||||||
|
| Client | Format | Regex | Color |
|
||||||
|
| :----: | :----: | :----: | :----: |
|
||||||
|
| [bRAC](https://github.com/MeexReay/bRAC) | 리㹰<{name}> {text} | `\uB9AC\u3E70<(.*?)> (.*)` | green
|
||||||
|
| [CRAB](https://gitea.bedohswe.eu.org/pixtaded/crab) | ═══<{name}> {text} | `\u2550\u2550\u2550<(.*?)> (.*)` | light red
|
||||||
|
| [Mefidroniy](https://github.com/OctoBanon-Main/mefedroniy-client) | °ʘ<{name}> {text} | `\u00B0\u0298<(.*?)> (.*)` | light magenta
|
||||||
|
| [cRACk](https://github.com/pansangg/cRACk) | ⁂<{name}> {text} | `\u2042<(.*?)> (.*)` | gold
|
||||||
|
| [Snowdrop](https://github.com/Forbirdden/Snowdrop) | ඞ<{name}> {text} | `\u0D9E<(.*?)> (.*)` | light green
|
||||||
|
| clRAC | <{name}> {text} | `<(.*?)> (.*)` | cyan
|
||||||
|
|
||||||
|
## developer notes
|
||||||
|
|
||||||
|
in auth-mode, there is must to be `> ` after name (`{name}> {text}`)
|
84
docs/wrac.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# WRACv2.0 Protocol
|
||||||
|
|
||||||
|
Uses websocket for connections, and sends binary data only (works in packet-way manner)
|
||||||
|
|
||||||
|
Totally inherits all packets of RACv2, except of reading messages
|
||||||
|
|
||||||
|
## Sending messages
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x01`
|
||||||
|
- Message text
|
||||||
|
|
||||||
|
## Sending authorized messages
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x02`
|
||||||
|
- Username
|
||||||
|
- `\n`
|
||||||
|
- Password
|
||||||
|
- `\n`
|
||||||
|
- Message
|
||||||
|
|
||||||
|
Server sends:
|
||||||
|
|
||||||
|
- nothing if message was sent successfully
|
||||||
|
- `0x01` if the user does not exists
|
||||||
|
- `0x02` if the password is incorrect
|
||||||
|
|
||||||
|
## Registration users
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x03`
|
||||||
|
- Username
|
||||||
|
- `\n`
|
||||||
|
- Password
|
||||||
|
|
||||||
|
Server sends:
|
||||||
|
|
||||||
|
- nothing if user was registered successfully
|
||||||
|
- `0x01` if the username is already taken
|
||||||
|
|
||||||
|
## Reading messages
|
||||||
|
|
||||||
|
### Getting message length
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x00`
|
||||||
|
|
||||||
|
Server sends:
|
||||||
|
|
||||||
|
- Size of all messages in ASCII (data_size)
|
||||||
|
|
||||||
|
### Normal reading
|
||||||
|
|
||||||
|
This packet is independent from getting message length packet.
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x00`
|
||||||
|
- Byte `0x01`
|
||||||
|
|
||||||
|
Server sends:
|
||||||
|
|
||||||
|
- All messages
|
||||||
|
|
||||||
|
### Chunked reading
|
||||||
|
|
||||||
|
This packet is independent from getting message length packet.
|
||||||
|
|
||||||
|
Client sends:
|
||||||
|
|
||||||
|
- Byte `0x00`
|
||||||
|
- Byte `0x02`
|
||||||
|
- Size of messages you have in ASCII (last_size)
|
||||||
|
|
||||||
|
Server sends:
|
||||||
|
|
||||||
|
- All new messages
|
||||||
|
|
||||||
|
*for example: if you want to read last N bytes, last_size = data_size - N*
|
23
examples/protocol.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use bRAC::proto::*;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut conn = connect("rac://meex.lol", None)?;
|
||||||
|
|
||||||
|
send_message(&mut conn, "<dude> hi RAC-loving kikes!")?;
|
||||||
|
register_user(&mut conn, "dude", "password")?;
|
||||||
|
send_message_auth(&mut conn, "dude", "password", "my auth message")?;
|
||||||
|
send_message_spoof_auth(&mut conn, "<dude> this message totally fucks auth system")?;
|
||||||
|
|
||||||
|
let (mut all_messages, last_size) = read_messages(&mut conn, 10, 0, false)?.unwrap(); // limits with 10 messages
|
||||||
|
|
||||||
|
/* imagine that new messages were written here */
|
||||||
|
|
||||||
|
let (mut new_messages, last_size) = read_messages(&mut conn, 10, last_size, true)?.unwrap(); // chunked reading!
|
||||||
|
|
||||||
|
all_messages.append(&mut new_messages);
|
||||||
|
|
||||||
|
println!("all_messages: {all_messages:?}. last_size: {last_size}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Name=bRAC
|
|
||||||
Version=0.1.4
|
|
||||||
Type=Application
|
|
||||||
Comment=better RAC client
|
|
||||||
Icon=~/.local/share/bRAC/icon.png
|
|
||||||
Exec=~/.local/bin/bRAC
|
|
||||||
Categories=Network;
|
|
||||||
StartupNotify=true
|
|
||||||
DBusActivatable=true
|
|
||||||
Terminal=false
|
|
||||||
X-GNOME-UsesNotifications=true
|
|
BIN
misc/bRAC.png
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 30 KiB |
43
misc/build.sh
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Run this script only from repository root!"
|
||||||
|
echo "This script depends on:"
|
||||||
|
echo " - fact that you are on linux x86_64!"
|
||||||
|
echo " - wine. install it with your distro's package manager"
|
||||||
|
echo " - cross crate. to install it, run this: cargo install cross --git https://github.com/cross-rs/cross"
|
||||||
|
echo " - docker, so you should run something like this on your distro: sudo systemctl start docker"
|
||||||
|
read -p "Press enter if you really want to do rm -rf build/"
|
||||||
|
|
||||||
|
rm -rf build
|
||||||
|
mkdir build
|
||||||
|
|
||||||
|
build_linux() {
|
||||||
|
mkdir build/linux-x86_64
|
||||||
|
mkdir build/linux-x86_64/misc
|
||||||
|
cargo build -r
|
||||||
|
cp target/release/bRAC build/linux-x86_64/misc/bRAC-gnotif
|
||||||
|
cp misc/user-install.sh build/linux-x86_64/install.sh
|
||||||
|
cp misc/bRAC.png build/linux-x86_64/misc
|
||||||
|
cp misc/create-desktop.sh build/linux-x86_64/misc
|
||||||
|
cargo build -r -F libnotify
|
||||||
|
cp target/release/bRAC build/linux-x86_64
|
||||||
|
cp README.md build/linux-x86_64
|
||||||
|
cp LICENSE build/linux-x86_64
|
||||||
|
zip -r build/bRAC-linux-x86_64.zip build/linux-x86_64
|
||||||
|
}
|
||||||
|
|
||||||
|
build_windows() {
|
||||||
|
chmod +x misc/mslink.sh
|
||||||
|
curl -L https://github.com/wingtk/gvsbuild/releases/download/2025.5.0/GTK4_Gvsbuild_2025.5.0_x64.zip -o build/gvsbuild.zip
|
||||||
|
unzip build/gvsbuild.zip "bin/*" -d build/windows-x86_64
|
||||||
|
rm build/gvsbuild.zip
|
||||||
|
cross build --target x86_64-pc-windows-gnu -F notify-rust,winapi -r
|
||||||
|
cp target/x86_64-pc-windows-gnu/release/bRAC.exe build/windows-x86_64/bin
|
||||||
|
./misc/mslink.sh -l bin\\bRAC.exe -o build/windows-x86_64/bRAC.lnk
|
||||||
|
cp README.md build/windows-x86_64
|
||||||
|
curl https://raw.githubusercontent.com/wingtk/gvsbuild/refs/heads/main/COPYING -o build/windows-x86_64/LICENSE
|
||||||
|
zip -r build/bRAC-windows-x86_64.zip build/windows-x86_64
|
||||||
|
}
|
||||||
|
|
||||||
|
build_linux
|
||||||
|
build_windows
|
15
misc/create-desktop.sh
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
version=$(grep -m1 '^version' Cargo.toml | sed -E 's/version *= *"(.*)"/\1/')
|
||||||
|
|
||||||
|
echo "[Desktop Entry]"
|
||||||
|
echo "Name=bRAC"
|
||||||
|
echo "Version=$version"
|
||||||
|
echo "Type=Application"
|
||||||
|
echo "Comment=better RAC client"
|
||||||
|
echo "Icon=$HOME/.local/share/bRAC/icon.png"
|
||||||
|
echo "Exec=$HOME/.local/bin/bRAC"
|
||||||
|
echo "Categories=Network;"
|
||||||
|
echo "StartupNotify=true"
|
||||||
|
echo "Terminal=false"
|
||||||
|
echo "X-GNOME-UsesNotifications=true"
|
BIN
misc/icon.ico
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 66 KiB |
BIN
misc/image.png
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 61 KiB |
BIN
misc/logo.gif
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 20 KiB |
243
misc/mslink.sh
Executable file
@ -0,0 +1,243 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# mslink: Allow to create Windows Shortcut without the need of Windows
|
||||||
|
#
|
||||||
|
# Copyright (C) 2019 Mikaël Le Bohec
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
# mslink.sh v1.3
|
||||||
|
#############################################################################################
|
||||||
|
# Ce script permet de créer un Raccourci Windows (Fichier .LNK)
|
||||||
|
# Script créé en se basant sur la doc
|
||||||
|
# http://msdn.microsoft.com/en-us/library/dd871305.aspx
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
OPTIONS=$(getopt -q -n ${0} -o hpl:o:n:w:a:i: -l help,lnk-target:,output-file:,name:,working-dir:,arguments:,icon:,printer-link -- "$@")
|
||||||
|
|
||||||
|
eval set -- ${OPTIONS}
|
||||||
|
|
||||||
|
IS_PRINTER_LNK=0
|
||||||
|
while true; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help) HELP=1 ;;
|
||||||
|
-p|--printer-link) IS_PRINTER_LNK=1 ;;
|
||||||
|
-l|--lnk-target) LNK_TARGET="$2" ; shift ;;
|
||||||
|
-o|--output-file) OUTPUT_FILE="$2" ; shift ;;
|
||||||
|
-n|--name) param_HasName="$2" ; shift ;;
|
||||||
|
-w|--working-dir) param_HasWorkingDir="$2" ; shift ;;
|
||||||
|
-a|--arguments) param_HasArguments="$2" ; shift ;;
|
||||||
|
-i|--icon) param_HasIconLocation="$2" ; shift ;;
|
||||||
|
--) shift ; break ;;
|
||||||
|
*) echo "Option inconnue : $1" ; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
echo "Option(s) inconnue(s) : $@"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ ${#LNK_TARGET} -eq 0 ] || [ ${#OUTPUT_FILE} -eq 0 ] && echo "
|
||||||
|
Usage :
|
||||||
|
${0} -l cible_du_fichier_lnk [-n description] [-w working_dir] [-a cmd_args] [-i icon_path] -o mon_fichier.lnk [-p]
|
||||||
|
|
||||||
|
Options :
|
||||||
|
-l, --lnk-target Précise la cible du raccourci
|
||||||
|
-o, --output-file Enregistre le raccourci dans un fichier
|
||||||
|
-n, --name Spécifie une description au raccourci
|
||||||
|
-w, --working-dir Spécifie le répertoire de lancement de la commande
|
||||||
|
-a, --arguments Spécifie les arguments de la commande lancée
|
||||||
|
-i, --icon Spécifie le chemin de l'icône
|
||||||
|
-p, --printer-link Génère un raccourci de type imprimante réseau
|
||||||
|
" && exit 1
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
# Fonctions
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
function ascii2hex() {
|
||||||
|
echo $(echo -n ${1} | hexdump -v -e '/1 " x%02x"'|sed s/\ /\\\\/g)
|
||||||
|
}
|
||||||
|
|
||||||
|
function gen_LinkFlags() {
|
||||||
|
echo '\x'$(printf '%02x' "$((HasLinkTargetIDList + HasName + HasWorkingDir + HasArguments + HasIconLocation))")${LinkFlags_2_3_4}
|
||||||
|
}
|
||||||
|
|
||||||
|
function gen_Data_string() {
|
||||||
|
ITEM_SIZE=$(printf '%04x' $((${#1})))
|
||||||
|
echo '\x'${ITEM_SIZE:2:2}'\x'${ITEM_SIZE:0:2}$(ascii2hex ${1})
|
||||||
|
}
|
||||||
|
|
||||||
|
function gen_IDLIST() {
|
||||||
|
ITEM_SIZE=$(printf '%04x' $((${#1}/4+2)))
|
||||||
|
echo '\x'${ITEM_SIZE:2:2}'\x'${ITEM_SIZE:0:2}${1}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_CLSID_to_DATA() {
|
||||||
|
echo -n ${1:6:2}${1:4:2}${1:2:2}${1:0:2}${1:11:2}${1:9:2}${1:16:2}${1:14:2}${1:19:4}${1:24:12}|sed s/"\([A-Fa-f0-9][A-Fa-f0-9]\)"/\\\\x\\1/g
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
# Variables issues de la documentation officielle de Microsoft
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
HasLinkTargetIDList=0x01
|
||||||
|
HasName=0x04
|
||||||
|
HasWorkingDir=0x10
|
||||||
|
HasArguments=0x20
|
||||||
|
HasIconLocation=0x40
|
||||||
|
|
||||||
|
HeaderSize='\x4c\x00\x00\x00' # HeaderSize
|
||||||
|
LinkCLSID=$(convert_CLSID_to_DATA "00021401-0000-0000-c000-000000000046") # LinkCLSID
|
||||||
|
LinkFlags_2_3_4='\x01\x00\x00' # ForceNoLinkInfo
|
||||||
|
LinkFlags=""
|
||||||
|
|
||||||
|
FileAttributes_Directory='\x10\x00\x00\x00' # FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
FileAttributes_File='\x20\x00\x00\x00' # FILE_ATTRIBUTE_ARCHIVE
|
||||||
|
|
||||||
|
CreationTime='\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
AccessTime='\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
WriteTime='\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
FileSize='\x00\x00\x00\x00'
|
||||||
|
IconIndex='\x00\x00\x00\x00'
|
||||||
|
ShowCommand='\x01\x00\x00\x00' # SW_SHOWNORMAL
|
||||||
|
Hotkey='\x00\x00' # No Hotkey
|
||||||
|
Reserved='\x00\x00' # Valeur non modifiable
|
||||||
|
Reserved2='\x00\x00\x00\x00' # Valeur non modifiable
|
||||||
|
Reserved3='\x00\x00\x00\x00' # Valeur non modifiable
|
||||||
|
TerminalID='\x00\x00' # Valeur non modifiable
|
||||||
|
|
||||||
|
CLSID_Computer="20d04fe0-3aea-1069-a2d8-08002b30309d" # Poste de travail
|
||||||
|
CLSID_Network="208d2c60-3aea-1069-a2d7-08002b30309d" # Favoris réseau
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
# Constantes trouvées à partir de l'analyse de fichiers lnk
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
PREFIX_LOCAL_ROOT='\x2f' # Disque local
|
||||||
|
PREFIX_FOLDER='\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Dossier de fichiers
|
||||||
|
PREFIX_FILE='\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # Fichier
|
||||||
|
PREFIX_NETWORK_ROOT='\xc3\x01\x81' # Racine de serveur de fichiers réseau
|
||||||
|
PREFIX_NETWORK_PRINTER='\xc3\x02\xc1' # Imprimante réseau
|
||||||
|
|
||||||
|
END_OF_STRING='\x00'
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
if [ ! -z "${param_HasName}" ]; then
|
||||||
|
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasName})
|
||||||
|
else
|
||||||
|
HasName=0x00
|
||||||
|
fi
|
||||||
|
if [ ! -z "${param_HasWorkingDir}" ]; then
|
||||||
|
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasWorkingDir})
|
||||||
|
else
|
||||||
|
HasWorkingDir=0x00
|
||||||
|
fi
|
||||||
|
if [ ! -z "${param_HasArguments}" ]; then
|
||||||
|
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasArguments})
|
||||||
|
else
|
||||||
|
HasArguments=0x00
|
||||||
|
fi
|
||||||
|
if [ ! -z "${param_HasIconLocation}" ]; then
|
||||||
|
STRING_DATA=${STRING_DATA}$(gen_Data_string ${param_HasIconLocation})
|
||||||
|
else
|
||||||
|
HasIconLocation=0x00
|
||||||
|
fi
|
||||||
|
|
||||||
|
LinkFlags=$(gen_LinkFlags)
|
||||||
|
|
||||||
|
# On retire l'anti-slash final s'il y en a un
|
||||||
|
LNK_TARGET=${LNK_TARGET%\\}
|
||||||
|
|
||||||
|
# On sépare le chemin racine du lien de la cible finale
|
||||||
|
# On distingue aussi si le lien est de type local ou réseau
|
||||||
|
# On définie la valeur Item_Data suivant le cas d'un lien réseau ou local
|
||||||
|
|
||||||
|
IS_ROOT_LNK=0
|
||||||
|
IS_NETWORK_LNK=0
|
||||||
|
|
||||||
|
if [[ ${LNK_TARGET} == \\\\* ]]; then
|
||||||
|
IS_NETWORK_LNK=1
|
||||||
|
PREFIX_ROOT=${PREFIX_NETWORK_ROOT}
|
||||||
|
Item_Data='\x1f\x58'$(convert_CLSID_to_DATA ${CLSID_Network})
|
||||||
|
|
||||||
|
TARGET_ROOT=${LNK_TARGET%\\*}
|
||||||
|
if [[ ${LNK_TARGET} == \\\\*\\* ]]; then
|
||||||
|
TARGET_LEAF=${LNK_TARGET##*\\}
|
||||||
|
fi
|
||||||
|
if [ ${TARGET_ROOT} == \\ ]; then
|
||||||
|
TARGET_ROOT=${LNK_TARGET}
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
PREFIX_ROOT=${PREFIX_LOCAL_ROOT}
|
||||||
|
Item_Data='\x1f\x50'$(convert_CLSID_to_DATA ${CLSID_Computer})
|
||||||
|
|
||||||
|
TARGET_ROOT=${LNK_TARGET%%\\*}
|
||||||
|
if [[ ${LNK_TARGET} == *\\* ]]; then
|
||||||
|
TARGET_LEAF=${LNK_TARGET#*\\}
|
||||||
|
fi
|
||||||
|
[[ ! ${TARGET_ROOT} == *\\ ]] && TARGET_ROOT=${TARGET_ROOT}'\'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${IS_PRINTER_LNK} -eq 1 ]; then
|
||||||
|
PREFIX_ROOT=${PREFIX_NETWORK_PRINTER}
|
||||||
|
TARGET_ROOT=${LNK_TARGET}
|
||||||
|
IS_ROOT_LNK=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ ${#TARGET_LEAF} -eq 0 ] && IS_ROOT_LNK=1
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
# On sélectionne le préfixe qui sera utilisé pour afficher l'icône du raccourci
|
||||||
|
|
||||||
|
if [[ ${TARGET_LEAF} == *.??? ]]; then
|
||||||
|
PREFIX_OF_TARGET=${PREFIX_FILE}
|
||||||
|
TYPE_TARGET="fichier"
|
||||||
|
FileAttributes=${FileAttributes_File}
|
||||||
|
else
|
||||||
|
PREFIX_OF_TARGET=${PREFIX_FOLDER}
|
||||||
|
TYPE_TARGET="dossier"
|
||||||
|
FileAttributes=${FileAttributes_Directory}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# On convertit les valeurs des cibles en binaire
|
||||||
|
TARGET_ROOT=$(ascii2hex "${TARGET_ROOT}")
|
||||||
|
TARGET_ROOT=${TARGET_ROOT}$(for i in `seq 1 21`;do echo -n '\x00';done) # Nécessaire à partir de Vista et supérieur sinon le lien est considéré comme vide (je n'ai trouvé nul part d'informations à ce sujet)
|
||||||
|
|
||||||
|
TARGET_LEAF=$(ascii2hex "${TARGET_LEAF}")
|
||||||
|
|
||||||
|
# On crée l'IDLIST qui représente le cœur du fichier LNK
|
||||||
|
|
||||||
|
if [ ${IS_ROOT_LNK} -eq 1 ];then
|
||||||
|
IDLIST_ITEMS=$(gen_IDLIST ${Item_Data})$(gen_IDLIST ${PREFIX_ROOT}${TARGET_ROOT}${END_OF_STRING})
|
||||||
|
else
|
||||||
|
IDLIST_ITEMS=$(gen_IDLIST ${Item_Data})$(gen_IDLIST ${PREFIX_ROOT}${TARGET_ROOT}${END_OF_STRING})$(gen_IDLIST ${PREFIX_OF_TARGET}${TARGET_LEAF}${END_OF_STRING})
|
||||||
|
fi
|
||||||
|
|
||||||
|
IDLIST=$(gen_IDLIST ${IDLIST_ITEMS})
|
||||||
|
|
||||||
|
#############################################################################################
|
||||||
|
|
||||||
|
if [ ${IS_NETWORK_LNK} -eq 1 ]; then
|
||||||
|
TYPE_LNK="réseau"
|
||||||
|
if [ ${IS_PRINTER_LNK} -eq 1 ]; then
|
||||||
|
TYPE_TARGET="imprimante"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TYPE_LNK="local"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Création d'un raccourci de type \""${TYPE_TARGET}" "${TYPE_LNK}"\" avec pour cible "${LNK_TARGET} ${param_HasArguments} 1>&2
|
||||||
|
|
||||||
|
echo -ne ${HeaderSize}${LinkCLSID}${LinkFlags}${FileAttributes}${CreationTime}${AccessTime}${WriteTime}${FileSize}${IconIndex}${ShowCommand}${Hotkey}${Reserved}${Reserved2}${Reserved3}${IDLIST}${TerminalID}${STRING_DATA} > "${OUTPUT_FILE}"
|
||||||
|
|
@ -27,4 +27,4 @@ set "v=%TEMP%\_s.vbs"
|
|||||||
>>"%v%" echo l.Save
|
>>"%v%" echo l.Save
|
||||||
wscript "%v%" >nul
|
wscript "%v%" >nul
|
||||||
del "%v%" >nul
|
del "%v%" >nul
|
||||||
exit /b
|
exit /b
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "this script is deprecated, fix it yourself if you wanna to"; exit
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script must be run as root"
|
echo "This script must be run as root"
|
||||||
exit 1
|
exit 1
|
||||||
@ -8,4 +10,4 @@ fi
|
|||||||
cp bRAC /bin/bRAC
|
cp bRAC /bin/bRAC
|
||||||
chmod +x /bin/bRAC
|
chmod +x /bin/bRAC
|
||||||
cp ru.themixray.bRAC.png /usr/share/pixmaps
|
cp ru.themixray.bRAC.png /usr/share/pixmaps
|
||||||
cp ru.themixray.bRAC.desktop /usr/share/applications
|
cp ru.themixray.bRAC.desktop /usr/share/applications
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "this script is deprecated, fix it yourself if you wanna to"; exit
|
||||||
|
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script must be run as root"
|
echo "This script must be run as root"
|
||||||
exit 1
|
exit 1
|
||||||
@ -11,4 +13,4 @@ done
|
|||||||
|
|
||||||
rm -f /bin/bRAC
|
rm -f /bin/bRAC
|
||||||
rm -f /usr/share/pixmaps/ru.themixray.bRAC.png
|
rm -f /usr/share/pixmaps/ru.themixray.bRAC.png
|
||||||
rm -f /usr/share/applications/ru.themixray.bRAC.desktop
|
rm -f /usr/share/applications/ru.themixray.bRAC.desktop
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cp bRAC ~/.local/bin/bRAC
|
mkdir -p ~/.local
|
||||||
|
mkdir -p ~/.local/bin
|
||||||
|
mkdir -p ~/.local/share
|
||||||
|
mkdir -p ~/.local/share/bRAC
|
||||||
|
|
||||||
|
cp misc/bRAC-gnotif ~/.local/bin/bRAC
|
||||||
chmod +x ~/.local/bin/bRAC
|
chmod +x ~/.local/bin/bRAC
|
||||||
mkdir ~/.local/share/bRAC -p
|
|
||||||
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
|
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
|
||||||
cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop
|
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop
|
||||||
|
@ -1,74 +1,90 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
use serde_yml;
|
|
||||||
use serde_default::DefaultFromSerde;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use serde_default::DefaultFromSerde;
|
||||||
|
use serde_yml;
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
|
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
|
||||||
|
|
||||||
fn default_true() -> bool { true }
|
fn default_true() -> bool {
|
||||||
pub fn default_max_messages() -> usize { 200 }
|
true
|
||||||
pub fn default_update_time() -> usize { 50 }
|
}
|
||||||
pub fn default_host() -> String { "meex.lol:11234".to_string() }
|
pub fn default_max_messages() -> usize {
|
||||||
pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
|
200
|
||||||
|
}
|
||||||
|
pub fn default_update_time() -> usize {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
pub fn default_oof_update_time() -> usize {
|
||||||
|
10000
|
||||||
|
}
|
||||||
|
pub fn default_konata_size() -> usize {
|
||||||
|
100
|
||||||
|
}
|
||||||
|
pub fn default_host() -> String {
|
||||||
|
"wracs://meex.lol:11234".to_string()
|
||||||
|
}
|
||||||
|
pub fn default_message_format() -> String {
|
||||||
|
MESSAGE_FORMAT.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "default_host")] pub host: String,
|
#[serde(default = "default_host")]
|
||||||
#[serde(default)] pub name: Option<String>,
|
pub host: String,
|
||||||
#[serde(default = "default_message_format")] pub message_format: String,
|
#[serde(default)]
|
||||||
#[serde(default = "default_update_time")] pub update_time: usize,
|
pub name: Option<String>,
|
||||||
#[serde(default = "default_max_messages")] pub max_messages: usize,
|
#[serde(default = "default_message_format")]
|
||||||
#[serde(default = "default_true")] pub hide_my_ip: bool,
|
pub message_format: String,
|
||||||
#[serde(default)] pub show_other_ip: bool,
|
#[serde(default = "default_update_time")]
|
||||||
#[serde(default)] pub auth_enabled: bool,
|
pub update_time: usize,
|
||||||
#[serde(default)] pub ssl_enabled: bool,
|
#[serde(default = "default_oof_update_time")]
|
||||||
#[serde(default = "default_true")] pub chunked_enabled: bool,
|
pub oof_update_time: usize,
|
||||||
#[serde(default = "default_true")] pub formatting_enabled: bool,
|
#[serde(default = "default_max_messages")]
|
||||||
#[serde(default = "default_true")] pub commands_enabled: bool,
|
pub max_messages: usize,
|
||||||
#[serde(default)] pub wrac_enabled: bool,
|
#[serde(default = "default_konata_size")]
|
||||||
#[serde(default)] pub proxy: Option<String>,
|
pub konata_size: usize,
|
||||||
#[serde(default = "default_true")] pub notifications_enabled: bool,
|
#[serde(default)]
|
||||||
|
pub remove_gui_shit: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub hide_my_ip: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub show_other_ip: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub auth_enabled: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub chunked_enabled: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub formatting_enabled: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub commands_enabled: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub proxy: Option<String>,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub notifications_enabled: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub debug_logs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_config_path() -> PathBuf {
|
pub fn get_config_path() -> PathBuf {
|
||||||
let mut config_dir = PathBuf::from_str(".").unwrap();
|
use std::env;
|
||||||
|
use std::str::FromStr;
|
||||||
|
env::var("APPDATA")
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| Some(PathBuf::from_str(&o).ok()?.join("bRAC")))
|
||||||
|
.unwrap_or("bRAC/config.yml".into())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||||
if let Some(dir) = {
|
pub fn get_config_path() -> PathBuf {
|
||||||
let home_dir = {
|
use homedir::my_home;
|
||||||
use homedir::my_home;
|
my_home()
|
||||||
my_home().ok().flatten()
|
.ok()
|
||||||
};
|
.flatten()
|
||||||
|
.map(|o| o.join(".config"))
|
||||||
#[cfg(target_os = "linux")]
|
.map(|o| o.join("bRAC"))
|
||||||
let config_dir = {
|
.unwrap_or("bRAC".into())
|
||||||
let home_dir = home_dir.map(|o| o.join("bRAC"));
|
.join("config.yml")
|
||||||
home_dir.map(|o| o.join(".config"))
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let config_dir = {
|
|
||||||
let home_dir = home_dir.map(|o| o.join("bRAC"));
|
|
||||||
home_dir.map(|o| o.join(".config"))
|
|
||||||
};
|
|
||||||
|
|
||||||
config_dir
|
|
||||||
} {
|
|
||||||
config_dir = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
if let Some(dir) = {
|
|
||||||
use std::env;
|
|
||||||
env::var("APPDATA")
|
|
||||||
.ok()
|
|
||||||
.and_then(|o| Some(PathBuf::from_str(&o).ok()?.join("bRAC")))
|
|
||||||
} {
|
|
||||||
config_dir = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
config_dir.join("config.yml")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(path: PathBuf) -> Config {
|
pub fn load_config(path: PathBuf) -> Config {
|
||||||
@ -94,50 +110,105 @@ pub fn save_config(path: PathBuf, config: &Config) {
|
|||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Print config path
|
/// Print config path
|
||||||
#[arg(short='p', long)]
|
#[arg(short = 'p', long)]
|
||||||
pub config_path: bool,
|
pub config_path: bool,
|
||||||
|
|
||||||
/// Print unformatted messages from chat and exit
|
/// Print unformatted messages from chat and exit
|
||||||
#[arg(short='r', long)]
|
#[arg(short = 'r', long)]
|
||||||
pub read_messages: bool,
|
pub read_messages: bool,
|
||||||
|
|
||||||
/// Send unformatted message to chat and exit
|
/// Send unformatted message to chat and exit
|
||||||
#[arg(short='s', long, value_name="MESSAGE")]
|
#[arg(short = 's', long, value_name = "MESSAGE")]
|
||||||
pub send_message: Option<String>,
|
pub send_message: Option<String>,
|
||||||
|
|
||||||
#[arg(short='H', long)] pub host: Option<String>,
|
#[arg(short = 'H', long)]
|
||||||
#[arg(short='n', long)] pub name: Option<String>,
|
pub host: Option<String>,
|
||||||
#[arg(long)] pub message_format: Option<String>,
|
#[arg(short = 'n', long)]
|
||||||
#[arg(long)] pub update_time: Option<usize>,
|
pub name: Option<String>,
|
||||||
#[arg(long)] pub max_messages: Option<usize>,
|
#[arg(long)]
|
||||||
#[arg(long)] pub hide_my_ip: Option<bool>,
|
pub message_format: Option<String>,
|
||||||
#[arg(long)] pub show_other_ip: Option<bool>,
|
#[arg(long)]
|
||||||
#[arg(long)] pub auth_enabled:Option <bool>,
|
pub update_time: Option<usize>,
|
||||||
#[arg(long)] pub ssl_enabled: Option<bool>,
|
#[arg(long)]
|
||||||
#[arg(long)] pub chunked_enabled: Option<bool>,
|
pub oof_update_time: Option<usize>,
|
||||||
#[arg(long)] pub formatting_enabled: Option<bool>,
|
#[arg(long)]
|
||||||
#[arg(long)] pub commands_enabled: Option<bool>,
|
pub max_messages: Option<usize>,
|
||||||
#[arg(long)] pub notifications_enabled: Option<bool>,
|
#[arg(long)]
|
||||||
#[arg(long)] pub wrac_enabled: Option<bool>,
|
pub konata_size: Option<usize>,
|
||||||
#[arg(long)] pub proxy: Option<String>,
|
#[arg(long)]
|
||||||
|
pub hide_my_ip: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub show_other_ip: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub auth_enabled: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub remove_gui_shit: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub chunked_enabled: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub formatting_enabled: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub commands_enabled: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub notifications_enabled: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub proxy: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
pub debug_logs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub fn patch_config(&self, config: &mut Config) {
|
pub fn patch_config(&self, config: &mut Config) {
|
||||||
if let Some(v) = self.host.clone() { config.host = v }
|
if let Some(v) = self.host.clone() {
|
||||||
if let Some(v) = self.name.clone() { config.name = Some(v) }
|
config.host = v
|
||||||
if let Some(v) = self.proxy.clone() { config.proxy = Some(v) }
|
}
|
||||||
if let Some(v) = self.message_format.clone() { config.message_format = v }
|
if let Some(v) = self.name.clone() {
|
||||||
if let Some(v) = self.update_time { config.update_time = v }
|
config.name = Some(v)
|
||||||
if let Some(v) = self.max_messages { config.max_messages = v }
|
}
|
||||||
if let Some(v) = self.hide_my_ip { config.hide_my_ip = v }
|
if let Some(v) = self.proxy.clone() {
|
||||||
if let Some(v) = self.show_other_ip { config.show_other_ip = v }
|
config.proxy = Some(v)
|
||||||
if let Some(v) = self.auth_enabled { config.auth_enabled = v }
|
}
|
||||||
if let Some(v) = self.ssl_enabled { config.ssl_enabled = v }
|
if let Some(v) = self.message_format.clone() {
|
||||||
if let Some(v) = self.chunked_enabled { config.chunked_enabled = v }
|
config.message_format = v
|
||||||
if let Some(v) = self.formatting_enabled { config.formatting_enabled = v }
|
}
|
||||||
if let Some(v) = self.commands_enabled { config.commands_enabled = v }
|
if let Some(v) = self.update_time {
|
||||||
if let Some(v) = self.notifications_enabled { config.notifications_enabled = v }
|
config.update_time = v
|
||||||
if let Some(v) = self.wrac_enabled { config.wrac_enabled = v }
|
}
|
||||||
|
if let Some(v) = self.oof_update_time {
|
||||||
|
config.oof_update_time = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.max_messages {
|
||||||
|
config.max_messages = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.konata_size {
|
||||||
|
config.konata_size = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.hide_my_ip {
|
||||||
|
config.hide_my_ip = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.show_other_ip {
|
||||||
|
config.show_other_ip = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.remove_gui_shit {
|
||||||
|
config.remove_gui_shit = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.auth_enabled {
|
||||||
|
config.auth_enabled = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.chunked_enabled {
|
||||||
|
config.chunked_enabled = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.formatting_enabled {
|
||||||
|
config.formatting_enabled = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.commands_enabled {
|
||||||
|
config.commands_enabled = v
|
||||||
|
}
|
||||||
|
if let Some(v) = self.notifications_enabled {
|
||||||
|
config.notifications_enabled = v
|
||||||
|
}
|
||||||
|
if self.debug_logs {
|
||||||
|
config.debug_logs = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use std::sync::{atomic::{AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock};
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||||
|
mpsc::Sender,
|
||||||
|
Arc, RwLock,
|
||||||
|
};
|
||||||
|
|
||||||
use rand::random;
|
use rand::random;
|
||||||
|
|
||||||
@ -7,10 +11,11 @@ use super::config::Config;
|
|||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub registered: RwLock<Option<String>>,
|
pub registered: RwLock<Option<String>>,
|
||||||
pub config: RwLock<Config>,
|
pub config: RwLock<Config>,
|
||||||
pub sender: RwLock<Option<Arc<Sender<(String, bool)>>>>,
|
pub sender: RwLock<Option<Arc<Sender<(Vec<String>, bool)>>>>,
|
||||||
pub messages: RwLock<Vec<String>>,
|
pub messages: RwLock<Vec<String>>,
|
||||||
pub packet_size: AtomicUsize,
|
pub packet_size: AtomicUsize,
|
||||||
pub name: RwLock<String>
|
pub name: RwLock<String>,
|
||||||
|
pub is_focused: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
@ -21,7 +26,13 @@ impl Context {
|
|||||||
sender: RwLock::new(None),
|
sender: RwLock::new(None),
|
||||||
messages: RwLock::new(Vec::new()),
|
messages: RwLock::new(Vec::new()),
|
||||||
packet_size: AtomicUsize::default(),
|
packet_size: AtomicUsize::default(),
|
||||||
name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()))),
|
name: RwLock::new(
|
||||||
|
config
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>())),
|
||||||
|
),
|
||||||
|
is_focused: AtomicBool::new(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,13 +42,16 @@ impl Context {
|
|||||||
|
|
||||||
pub fn set_config(&self, config: &Config) {
|
pub fn set_config(&self, config: &Config) {
|
||||||
*self.config.write().unwrap() = config.clone();
|
*self.config.write().unwrap() = config.clone();
|
||||||
*self.name.write().unwrap() = config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
|
*self.name.write().unwrap() = config
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
|
||||||
*self.registered.write().unwrap() = None;
|
*self.registered.write().unwrap() = None;
|
||||||
*self.messages.write().unwrap() = Vec::new();
|
*self.messages.write().unwrap() = Vec::new();
|
||||||
self.packet_size.store(0, Ordering::SeqCst);
|
self.packet_size.store(0, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config<T>(&self, map: fn (&Config) -> T) -> T {
|
pub fn config<T>(&self, map: fn(&Config) -> T) -> T {
|
||||||
map(&self.config.read().unwrap())
|
map(&self.config.read().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +63,12 @@ impl Context {
|
|||||||
self.messages.read().unwrap().clone()
|
self.messages.read().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_messages_packet(&self, max_length: usize, messages: Vec<String>, packet_size: usize) {
|
pub fn put_messages_packet(
|
||||||
|
&self,
|
||||||
|
max_length: usize,
|
||||||
|
messages: Vec<String>,
|
||||||
|
packet_size: usize,
|
||||||
|
) {
|
||||||
self.packet_size.store(packet_size, Ordering::SeqCst);
|
self.packet_size.store(packet_size, Ordering::SeqCst);
|
||||||
let mut messages = messages;
|
let mut messages = messages;
|
||||||
if messages.len() > max_length {
|
if messages.len() > max_length {
|
||||||
@ -58,7 +77,12 @@ impl Context {
|
|||||||
*self.messages.write().unwrap() = messages;
|
*self.messages.write().unwrap() = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_messages_packet(&self, max_length: usize, messages: Vec<String>, packet_size: usize) {
|
pub fn add_messages_packet(
|
||||||
|
&self,
|
||||||
|
max_length: usize,
|
||||||
|
messages: Vec<String>,
|
||||||
|
packet_size: usize,
|
||||||
|
) {
|
||||||
self.packet_size.store(packet_size, Ordering::SeqCst);
|
self.packet_size.store(packet_size, Ordering::SeqCst);
|
||||||
self.add_message(max_length, messages);
|
self.add_message(max_length, messages);
|
||||||
}
|
}
|
||||||
@ -73,12 +97,10 @@ impl Context {
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! connect_rac {
|
macro_rules! connect_rac {
|
||||||
($ctx:ident) => {
|
($ctx:ident) => {
|
||||||
&mut connect(
|
&mut connect(
|
||||||
&$ctx.config(|o| o.host.clone()),
|
&$ctx.config(|o| o.host.clone()),
|
||||||
$ctx.config(|o| o.ssl_enabled),
|
$ctx.config(|o| o.proxy.clone()),
|
||||||
$ctx.config(|o| o.proxy.clone()),
|
)?
|
||||||
$ctx.config(|o| o.wrac_enabled)
|
|
||||||
)?
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
903
src/chat/gui.rs
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 20 KiB |
189
src/chat/mod.rs
@ -1,19 +1,26 @@
|
|||||||
use std::{
|
use std::{
|
||||||
error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
|
error::Error,
|
||||||
|
sync::Arc,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::connect_rac;
|
use crate::connect_rac;
|
||||||
|
|
||||||
use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth};
|
use super::proto::{
|
||||||
|
connect, read_messages, register_user, send_message, send_message_auth, send_message_spoof_auth,
|
||||||
|
};
|
||||||
|
|
||||||
use gui::{add_chat_message, clear_chat_messages};
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use ctx::Context;
|
use ctx::Context;
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
|
pub mod gui;
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub use gui::run_main_loop;
|
pub use gui::run_main_loop;
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
|
use gui::{add_chat_messages, clear_chat_messages};
|
||||||
|
|
||||||
const HELP_MESSAGE: &str = "Help message:
|
const HELP_MESSAGE: &str = "Help message:
|
||||||
/help - show help message
|
/help - show help message
|
||||||
@ -31,21 +38,22 @@ lazy_static! {
|
|||||||
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
|
||||||
|
|
||||||
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
|
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
|
||||||
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "green".to_string()), // bRAC
|
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "#70fa7a".to_string()), // bRAC
|
||||||
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "red".to_string()), // CRAB
|
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "#fa7070".to_string()), // CRAB
|
||||||
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "magenta".to_string()), // Mefidroniy
|
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "#da70fa".to_string()), // Mefidroniy
|
||||||
(Regex::new(r"<(.*?)> (.*)").unwrap(), "cyan".to_string()), // clRAC
|
(Regex::new(r"\u{2042}<(.*?)> (.*)").unwrap(), "#f8b91b".to_string()), // cRACk
|
||||||
|
(Regex::new(r"\u{0D9E}<(.*?)> (.*)").unwrap(), "#aeff00".to_string()), // Snowdrop
|
||||||
|
(Regex::new(r"<(.*?)> (.*)").unwrap(), "#70fadc".to_string()), // clRAC
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static ref SERVER_LIST: Vec<String> = vec![
|
pub static ref SERVER_LIST: Vec<String> = vec![
|
||||||
"rac://meex.lol".to_string(),
|
"wracs://meex.lol:11234".to_string(),
|
||||||
"rac://meex.lol:11234".to_string(),
|
"rac://meex.lol".to_string(),
|
||||||
|
"wracs://meex.lol".to_string(),
|
||||||
"rac://91.192.22.20".to_string()
|
"rac://91.192.22.20".to_string()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub mod gui;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod ctx;
|
pub mod ctx;
|
||||||
|
|
||||||
@ -55,59 +63,67 @@ pub fn sanitize_text(input: &str) -> String {
|
|||||||
cleaned_text.into_owned()
|
cleaned_text.into_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
for i in message.split("\n")
|
for i in message.split("\n").map(|o| o.to_string()) {
|
||||||
.map(|o| o.to_string()) {
|
|
||||||
print_message(ctx.clone(), i)?;
|
print_message(ctx.clone(), i)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub 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>>();
|
||||||
|
|
||||||
if command == "clear" {
|
if command == "clear" {
|
||||||
let Some(times) = args.get(0) else { return Ok(()) };
|
let Some(times) = args.get(0) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
let times = times.parse()?;
|
let times = times.parse()?;
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
send_message(connect_rac!(ctx), "\r")?;
|
send_message(connect_rac!(ctx), "\r")?;
|
||||||
}
|
}
|
||||||
} else if command == "spam" {
|
} else if command == "spam" {
|
||||||
let Some(times) = args.get(0) else { return Ok(()) };
|
let Some(times) = args.get(0) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
let times = times.parse()?;
|
let times = times.parse()?;
|
||||||
let msg = args[1..].join(" ");
|
let msg = args[1..].join(" ");
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
send_message(connect_rac!(ctx), &("\r".to_string()+&msg))?;
|
send_message(connect_rac!(ctx), &("\r".to_string() + &msg))?;
|
||||||
}
|
}
|
||||||
} else if command == "help" {
|
} else if command == "help" {
|
||||||
add_message(ctx.clone(), HELP_MESSAGE)?;
|
add_message(ctx.clone(), HELP_MESSAGE)?;
|
||||||
} else if command == "register" {
|
} else if command == "register" {
|
||||||
let Some(pass) = args.get(0) else {
|
let Some(pass) = args.get(0) else {
|
||||||
add_message(ctx.clone(), "please provide password as the first argument")?;
|
add_message(ctx.clone(), "please provide password as the first argument")?;
|
||||||
return Ok(())
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
match register_user(connect_rac!(ctx), &ctx.name(), pass, !ctx.config(|o| o.ssl_enabled)) {
|
match register_user(connect_rac!(ctx), &ctx.name(), pass) {
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
add_message(ctx.clone(), "you was registered successfully bro")?;
|
add_message(ctx.clone(), "you was registered successfully bro")?;
|
||||||
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
||||||
},
|
}
|
||||||
Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?,
|
Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?,
|
||||||
Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?
|
Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))?,
|
||||||
};
|
};
|
||||||
} else if command == "login" {
|
} else if command == "login" {
|
||||||
let Some(pass) = args.get(0) else {
|
let Some(pass) = args.get(0) else {
|
||||||
add_message(ctx.clone(), "please provide password as the first argument")?;
|
add_message(ctx.clone(), "please provide password as the first argument")?;
|
||||||
return Ok(())
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
add_message(ctx.clone(), "ye bro you was logged in")?;
|
add_message(ctx.clone(), "ye bro you was logged in")?;
|
||||||
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
*ctx.registered.write().unwrap() = Some(pass.to_string());
|
||||||
} else if command == "ping" {
|
} else if command == "ping" {
|
||||||
let mut before = ctx.packet_size();
|
let mut before = ctx.packet_size();
|
||||||
let message = format!("Checking ping... {:X}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis());
|
let message = format!(
|
||||||
|
"Checking ping... {:X}",
|
||||||
|
SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis()
|
||||||
|
);
|
||||||
|
|
||||||
send_message(connect_rac!(ctx), &message)?;
|
send_message(connect_rac!(ctx), &message)?;
|
||||||
|
|
||||||
@ -115,12 +131,13 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let data = read_messages(
|
let data = read_messages(
|
||||||
connect_rac!(ctx),
|
connect_rac!(ctx),
|
||||||
ctx.config(|o| o.max_messages),
|
ctx.config(|o| o.max_messages),
|
||||||
before,
|
before,
|
||||||
!ctx.config(|o| o.ssl_enabled),
|
ctx.config(|o| o.chunked_enabled),
|
||||||
ctx.config(|o| o.chunked_enabled)
|
)
|
||||||
).ok().flatten();
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
if let Some((data, size)) = data {
|
if let Some((data, size)) = data {
|
||||||
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
|
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
|
||||||
@ -135,7 +152,10 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_message(ctx.clone(), &format!("Ping = {}ms", start.elapsed().unwrap().as_millis()))?;
|
add_message(
|
||||||
|
ctx.clone(),
|
||||||
|
&format!("Ping = {}ms", start.elapsed().unwrap().as_millis()),
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
add_message(ctx.clone(), "Unknown command bruh")?;
|
add_message(ctx.clone(), "Unknown command bruh")?;
|
||||||
}
|
}
|
||||||
@ -144,23 +164,18 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
|
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
|
||||||
format!("{}{}{}",
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
if ctx.config(|o| o.hide_my_ip) {
|
if ctx.config(|o| o.hide_my_ip) {
|
||||||
"\r\x07"
|
"\r\x07"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
},
|
},
|
||||||
message,
|
message,
|
||||||
if !ctx.config(|o| o.hide_my_ip) {
|
if !ctx.config(|o| o.hide_my_ip) {
|
||||||
let spaces = if ctx.config(|o| o.auth_enabled) {
|
if message.chars().count() < 54 {
|
||||||
39
|
" ".repeat(54 - message.chars().count())
|
||||||
} else {
|
} else {
|
||||||
54
|
|
||||||
};
|
|
||||||
|
|
||||||
if message.chars().count() < spaces {
|
|
||||||
" ".repeat(spaces-message.chars().count())
|
|
||||||
} else {
|
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -169,101 +184,92 @@ pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub fn print_message(ctx: Arc<Context>, message: String) -> Result<(), Box<dyn Error>> {
|
pub fn print_message(ctx: Arc<Context>, message: String) -> Result<(), Box<dyn Error>> {
|
||||||
ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]);
|
ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]);
|
||||||
add_chat_message(ctx.clone(), message);
|
add_chat_messages(ctx.clone(), vec![message]);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
|
||||||
let last_size = ctx.packet_size();
|
let last_size = ctx.packet_size();
|
||||||
|
|
||||||
match read_messages(
|
match read_messages(
|
||||||
connect_rac!(ctx),
|
connect_rac!(ctx),
|
||||||
ctx.config(|o| o.max_messages),
|
ctx.config(|o| o.max_messages),
|
||||||
ctx.packet_size(),
|
ctx.packet_size(),
|
||||||
!ctx.config(|o| o.ssl_enabled),
|
ctx.config(|o| o.chunked_enabled),
|
||||||
ctx.config(|o| o.chunked_enabled)
|
|
||||||
) {
|
) {
|
||||||
Ok(Some((messages, size))) => {
|
Ok(Some((messages, size))) => {
|
||||||
if ctx.config(|o| o.chunked_enabled) {
|
if ctx.config(|o| o.chunked_enabled) {
|
||||||
ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
||||||
if last_size == 0 {
|
if last_size == 0 {
|
||||||
if messages.len() >= 1 {
|
clear_chat_messages(ctx.clone(), messages);
|
||||||
clear_chat_messages(ctx.clone(), messages[0].clone());
|
|
||||||
if messages.len() >= 2 {
|
|
||||||
for msg in &messages[1..] {
|
|
||||||
add_chat_message(ctx.clone(), msg.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for msg in messages {
|
add_chat_messages(ctx.clone(), messages);
|
||||||
add_chat_message(ctx.clone(), msg.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
|
||||||
if messages.len() >= 1 {
|
clear_chat_messages(ctx.clone(), messages);
|
||||||
clear_chat_messages(ctx.clone(), messages[0].clone());
|
|
||||||
if messages.len() >= 2 {
|
|
||||||
for msg in &messages[1..] {
|
|
||||||
add_chat_message(ctx.clone(), msg.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Read messages error: {}", e.to_string())
|
if ctx.config(|o| o.debug_logs) {
|
||||||
|
add_chat_messages(
|
||||||
|
ctx.clone(),
|
||||||
|
vec![format!("Read messages error: {}", e.to_string())],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(ctx.config(|o| o.update_time) as u64));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gtk")]
|
||||||
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
|
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
|
||||||
on_command(ctx.clone(), &message)?;
|
on_command(ctx.clone(), &message)?;
|
||||||
} else {
|
} else {
|
||||||
let message = prepare_message(
|
let message = prepare_message(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
&ctx.config(|o| o.message_format.clone())
|
&ctx.config(|o| o.message_format.clone())
|
||||||
.replace("{name}", &ctx.name())
|
.replace("{name}", &ctx.name())
|
||||||
.replace("{text}", &message)
|
.replace("{text}", &message),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(password) = ctx.registered.read().unwrap().clone() {
|
if let Some(password) = ctx.registered.read().unwrap().clone() {
|
||||||
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message, !ctx.config(|o| o.ssl_enabled))?;
|
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message)?;
|
||||||
} else if ctx.config(|o| o.auth_enabled) {
|
} else if ctx.config(|o| o.auth_enabled) {
|
||||||
send_message_spoof_auth(connect_rac!(ctx), &message, !ctx.config(|o| o.ssl_enabled))?;
|
send_message_spoof_auth(connect_rac!(ctx), &message)?;
|
||||||
} else {
|
} else {
|
||||||
send_message(connect_rac!(ctx), &message)?;
|
send_message(connect_rac!(ctx), &message)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sanitize_message(message: String) -> Option<String> {
|
pub fn sanitize_message(message: String) -> Option<String> {
|
||||||
let message = sanitize_text(&message);
|
let message = sanitize_text(&message);
|
||||||
|
|
||||||
let message = message.trim().to_string();
|
let message = message.trim().to_string();
|
||||||
|
|
||||||
Some(message)
|
Some(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// message -> (date, ip, text, (name, color))
|
/// message -> (date, ip, text, (name, color))
|
||||||
pub fn parse_message(message: String) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
|
pub fn parse_message(
|
||||||
|
message: String,
|
||||||
|
) -> Option<(String, Option<String>, String, Option<(String, String)>)> {
|
||||||
if message.is_empty() {
|
if message.is_empty() {
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let date = DATE_REGEX.captures(&message)?;
|
let date = DATE_REGEX.captures(&message)?;
|
||||||
let (date, message) = (
|
let (date, message) = (
|
||||||
date.get(1)?.as_str().to_string(),
|
date.get(1)?.as_str().to_string(),
|
||||||
date.get(2)?.as_str().to_string(),
|
date.get(2)?.as_str().to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let message = message
|
let message = message
|
||||||
@ -274,11 +280,14 @@ pub fn parse_message(message: String) -> Option<(String, Option<String>, String,
|
|||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
|
let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) {
|
||||||
(Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string())
|
(
|
||||||
|
Some(message.get(1)?.as_str().to_string()),
|
||||||
|
message.get(2)?.as_str().to_string(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(None, message)
|
(None, message)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (message, nick) = match find_username_color(&message) {
|
let (message, nick) = match find_username_color(&message) {
|
||||||
Some((name, content, color)) => (content, Some((name, color))),
|
Some((name, content, color)) => (content, Some((name, color))),
|
||||||
None => (message, None),
|
None => (message, None),
|
||||||
@ -291,8 +300,12 @@ pub fn parse_message(message: String) -> Option<(String, Option<String>, String,
|
|||||||
pub fn find_username_color(message: &str) -> Option<(String, String, String)> {
|
pub fn find_username_color(message: &str) -> Option<(String, String, String)> {
|
||||||
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) {
|
||||||
return Some((captures[1].to_string(), captures[2].to_string(), color.clone()))
|
return Some((
|
||||||
|
captures[1].to_string(),
|
||||||
|
captures[2].to_string(),
|
||||||
|
color.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
.message-content { color:rgb(255, 255, 255); }
|
|
||||||
|
/* Now made with GTK Pango Markup */
|
||||||
|
|
||||||
|
/* .message-content { color:rgb(255, 255, 255); }
|
||||||
.message-date { color:rgb(146, 146, 146); }
|
.message-date { color:rgb(146, 146, 146); }
|
||||||
.message-ip { color:rgb(73, 73, 73); }
|
.message-ip { color:rgb(73, 73, 73); } */
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
.message-content { color:rgb(0, 0, 0); }
|
|
||||||
|
/* Now made with GTK Pango Markup */
|
||||||
|
|
||||||
|
/* .message-content { color:rgb(0, 0, 0); }
|
||||||
.message-date { color:rgb(41, 41, 41); }
|
.message-date { color:rgb(41, 41, 41); }
|
||||||
.message-ip { color:rgb(88, 88, 88); }
|
.message-ip { color:rgb(88, 88, 88); } */
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
.send-button, .send-text { border-radius: 0; }
|
.send-button, .send-text { border-radius: 0; }
|
||||||
.calendar {
|
.calendar {
|
||||||
transform: scale(0.6);
|
transform: scale(0.6);
|
||||||
@ -16,9 +14,11 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-name { font-weight: bold; }
|
/* Now made with GTK Pango Markup */
|
||||||
|
|
||||||
|
/* .message-name { font-weight: bold; }
|
||||||
|
|
||||||
.message-name-green { color: #70fa7a; }
|
.message-name-green { color: #70fa7a; }
|
||||||
.message-name-red { color: #fa7070; }
|
.message-name-red { color: #fa7070; }
|
||||||
.message-name-magenta { color: #da70fa; }
|
.message-name-magenta { color: #da70fa; }
|
||||||
.message-name-cyan { color: #70fadc; }
|
.message-name-cyan { color: #70fadc; } */
|
@ -1,4 +1,4 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
55
src/main.rs
@ -1,15 +1,20 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bRAC::chat::{
|
||||||
|
config::{get_config_path, load_config, Args},
|
||||||
|
ctx::Context,
|
||||||
|
};
|
||||||
use bRAC::proto::{connect, read_messages, send_message};
|
use bRAC::proto::{connect, read_messages, send_message};
|
||||||
use bRAC::chat::{config::{get_config_path, load_config, Args}, ctx::Context, run_main_loop};
|
use clap::Parser;
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "winapi")]
|
#[cfg(feature = "winapi")]
|
||||||
unsafe { winapi::um::wincon::FreeConsole() };
|
unsafe {
|
||||||
|
winapi::um::wincon::FreeConsole()
|
||||||
|
};
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let config_path = get_config_path();
|
let config_path = get_config_path();
|
||||||
|
|
||||||
if args.config_path {
|
if args.config_path {
|
||||||
@ -19,37 +24,39 @@ fn main() {
|
|||||||
|
|
||||||
let mut config = load_config(config_path);
|
let mut config = load_config(config_path);
|
||||||
|
|
||||||
if args.read_messages {
|
args.patch_config(&mut config);
|
||||||
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error reading message");
|
|
||||||
|
|
||||||
print!("{}", read_messages(
|
if args.read_messages {
|
||||||
&mut stream,
|
let mut stream =
|
||||||
config.max_messages,
|
connect(&config.host, config.proxy.clone()).expect("Error reading message");
|
||||||
0,
|
|
||||||
!config.ssl_enabled,
|
print!(
|
||||||
false
|
"{}",
|
||||||
)
|
read_messages(&mut stream, config.max_messages, 0, false)
|
||||||
.ok().flatten()
|
.ok()
|
||||||
.expect("Error reading messages").0.join("\n")
|
.flatten()
|
||||||
|
.expect("Error reading messages")
|
||||||
|
.0
|
||||||
|
.join("\n")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(message) = &args.send_message {
|
if let Some(message) = &args.send_message {
|
||||||
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error sending message");
|
let mut stream =
|
||||||
|
connect(&config.host, config.proxy.clone()).expect("Error sending message");
|
||||||
|
|
||||||
send_message(
|
send_message(&mut stream, message).expect("Error sending message");
|
||||||
&mut stream,
|
|
||||||
message
|
|
||||||
).expect("Error sending message");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.send_message.is_some() || args.read_messages {
|
if args.send_message.is_some() || args.read_messages {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
args.patch_config(&mut config);
|
#[cfg(feature = "gtk")]
|
||||||
|
{
|
||||||
let ctx = Arc::new(Context::new(&config));
|
let ctx = Arc::new(Context::new(&config));
|
||||||
|
|
||||||
run_main_loop(ctx.clone());
|
use bRAC::chat::run_main_loop;
|
||||||
|
run_main_loop(ctx.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
224
src/proto/mod.rs
@ -1,8 +1,14 @@
|
|||||||
use std::{error::Error, fmt::Debug, io::{Read, Write}, net::{TcpStream, ToSocketAddrs}, time::Duration};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::Debug,
|
||||||
|
io::{Read, Write},
|
||||||
|
net::{TcpStream, ToSocketAddrs},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use native_tls::{TlsConnector, TlsStream};
|
use native_tls::{TlsConnector, TlsStream};
|
||||||
use socks::Socks5Stream;
|
use socks::Socks5Stream;
|
||||||
use tungstenite::WebSocket;
|
use tungstenite::{client::client_with_config, protocol::WebSocketConfig, WebSocket};
|
||||||
|
|
||||||
pub mod rac;
|
pub mod rac;
|
||||||
pub mod wrac;
|
pub mod wrac;
|
||||||
@ -13,28 +19,44 @@ pub trait Stream: Read + Write + Unpin + Send + Sync + Debug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for TcpStream {
|
impl Stream for TcpStream {
|
||||||
fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(&self, Some(timeout)); }
|
fn set_read_timeout(&self, timeout: Duration) {
|
||||||
fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(&self, Some(timeout)); }
|
let _ = TcpStream::set_read_timeout(&self, Some(timeout));
|
||||||
|
}
|
||||||
|
fn set_write_timeout(&self, timeout: Duration) {
|
||||||
|
let _ = TcpStream::set_write_timeout(&self, Some(timeout));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Socks5Stream {
|
impl Stream for Socks5Stream {
|
||||||
fn set_read_timeout(&self, timeout: Duration) { let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout)); }
|
fn set_read_timeout(&self, timeout: Duration) {
|
||||||
fn set_write_timeout(&self, timeout: Duration) { let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout)); }
|
let _ = TcpStream::set_read_timeout(self.get_ref(), Some(timeout));
|
||||||
|
}
|
||||||
|
fn set_write_timeout(&self, timeout: Duration) {
|
||||||
|
let _ = TcpStream::set_write_timeout(self.get_ref(), Some(timeout));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Stream> Stream for TlsStream<T> {
|
impl<T: Stream> Stream for TlsStream<T> {
|
||||||
fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
|
fn set_read_timeout(&self, timeout: Duration) {
|
||||||
fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
|
self.get_ref().set_read_timeout(timeout);
|
||||||
|
}
|
||||||
|
fn set_write_timeout(&self, timeout: Duration) {
|
||||||
|
self.get_ref().set_write_timeout(timeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for TlsStream<Box<dyn Stream>> {
|
impl Stream for TlsStream<Box<dyn Stream>> {
|
||||||
fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
|
fn set_read_timeout(&self, timeout: Duration) {
|
||||||
fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
|
self.get_ref().set_read_timeout(timeout);
|
||||||
|
}
|
||||||
|
fn set_write_timeout(&self, timeout: Duration) {
|
||||||
|
self.get_ref().set_write_timeout(timeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum RacStream {
|
pub enum RacStream {
|
||||||
WRAC(WebSocket<Box<dyn Stream>>),
|
WRAC(WebSocket<Box<dyn Stream>>),
|
||||||
RAC(Box<dyn Stream>)
|
RAC(Box<dyn Stream>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \
|
/// `socks5://user:pass@127.0.0.1:12345/path -> ("127.0.0.1:12345", ("user", "pass"))` \
|
||||||
@ -64,46 +86,42 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> {
|
|||||||
let (scheme, url) = url.split_once("://").unwrap_or(("rac", url));
|
let (scheme, url) = url.split_once("://").unwrap_or(("rac", url));
|
||||||
let (host, _) = url.split_once("/").unwrap_or((url, ""));
|
let (host, _) = url.split_once("/").unwrap_or((url, ""));
|
||||||
match scheme.to_lowercase().as_str() {
|
match scheme.to_lowercase().as_str() {
|
||||||
"rac" => {
|
"rac" => Some((
|
||||||
Some((
|
if host.contains(":") {
|
||||||
if host.contains(":") {
|
host.to_string()
|
||||||
host.to_string()
|
} else {
|
||||||
} else {
|
format!("{host}:42666")
|
||||||
format!("{host}:42666")
|
},
|
||||||
},
|
false,
|
||||||
false, false
|
false,
|
||||||
))
|
)),
|
||||||
},
|
"racs" => Some((
|
||||||
"racs" => {
|
if host.contains(":") {
|
||||||
Some((
|
host.to_string()
|
||||||
if host.contains(":") {
|
} else {
|
||||||
host.to_string()
|
format!("{host}:42667")
|
||||||
} else {
|
},
|
||||||
format!("{host}:42667")
|
true,
|
||||||
},
|
false,
|
||||||
true, false
|
)),
|
||||||
))
|
"wrac" => Some((
|
||||||
},
|
if host.contains(":") {
|
||||||
"wrac" => {
|
host.to_string()
|
||||||
Some((
|
} else {
|
||||||
if host.contains(":") {
|
format!("{host}:52666")
|
||||||
host.to_string()
|
},
|
||||||
} else {
|
false,
|
||||||
format!("{host}:52666")
|
true,
|
||||||
},
|
)),
|
||||||
false, true
|
"wracs" => Some((
|
||||||
))
|
if host.contains(":") {
|
||||||
},
|
host.to_string()
|
||||||
"wracs" => {
|
} else {
|
||||||
Some((
|
format!("{host}:52667")
|
||||||
if host.contains(":") {
|
},
|
||||||
host.to_string()
|
true,
|
||||||
} else {
|
true,
|
||||||
format!("{host}:52667")
|
)),
|
||||||
},
|
|
||||||
true, true
|
|
||||||
))
|
|
||||||
},
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,14 +132,19 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> {
|
|||||||
/// ssl - wrap with ssl client, write false if you dont know what it is
|
/// ssl - wrap with ssl client, write false if you dont know what it is
|
||||||
/// proxy - socks5 proxy (host, (user, pass))
|
/// proxy - socks5 proxy (host, (user, pass))
|
||||||
/// wrac - to use wrac protocol
|
/// wrac - to use wrac protocol
|
||||||
pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Result<RacStream, Box<dyn Error>> {
|
pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn Error>> {
|
||||||
let (host, ssl_, wrac_) = parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
|
let (host, ssl, wrac) =
|
||||||
let (ssl, wrac) = (ssl_ || ssl, wrac_ || wrac);
|
parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
|
||||||
|
|
||||||
let stream: Box<dyn Stream> = if let Some(proxy) = proxy {
|
let stream: Box<dyn Stream> = if let Some(proxy) = proxy {
|
||||||
if let Some((proxy, auth)) = parse_socks5_url(&proxy) {
|
if let Some((proxy, auth)) = parse_socks5_url(&proxy) {
|
||||||
if let Some((user, pass)) = auth {
|
if let Some((user, pass)) = auth {
|
||||||
Box::new(Socks5Stream::connect_with_password(&proxy, host.as_str(), &user, &pass)?)
|
Box::new(Socks5Stream::connect_with_password(
|
||||||
|
&proxy,
|
||||||
|
host.as_str(),
|
||||||
|
&user,
|
||||||
|
&pass,
|
||||||
|
)?)
|
||||||
} else {
|
} else {
|
||||||
Box::new(Socks5Stream::connect(&proxy, host.as_str())?)
|
Box::new(Socks5Stream::connect(&proxy, host.as_str())?)
|
||||||
}
|
}
|
||||||
@ -129,32 +152,39 @@ pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Resu
|
|||||||
return Err("proxy parse error".into());
|
return Err("proxy parse error".into());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let addr = host.to_socket_addrs()?.next().ok_or::<Box<dyn Error>>("addr parse error".into())?;
|
let addr = host
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.next()
|
||||||
|
.ok_or::<Box<dyn Error>>("addr parse error".into())?;
|
||||||
|
|
||||||
Box::new(TcpStream::connect(&addr)?)
|
Box::new(TcpStream::connect(&addr)?)
|
||||||
};
|
};
|
||||||
|
|
||||||
let stream = if ssl {
|
let stream = 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()
|
Box::new(
|
||||||
.danger_accept_invalid_certs(true)
|
TlsConnector::builder()
|
||||||
.danger_accept_invalid_hostnames(true)
|
.danger_accept_invalid_certs(true)
|
||||||
.build()?
|
.danger_accept_invalid_hostnames(true)
|
||||||
.connect(&ip, stream)?)
|
.build()?
|
||||||
|
.connect(&ip, stream)?,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
stream
|
stream
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.set_read_timeout(Duration::from_secs(3));
|
stream.set_read_timeout(Duration::from_secs(15)); // TODO: softcode this
|
||||||
stream.set_write_timeout(Duration::from_secs(3));
|
stream.set_write_timeout(Duration::from_secs(15));
|
||||||
|
|
||||||
if wrac {
|
if wrac {
|
||||||
let (client, _) = tungstenite::client(
|
let (client, _) = client_with_config(
|
||||||
&format!("ws{}://{host}", if ssl { "s" } else { "" }),
|
&format!("ws://{host}"),
|
||||||
stream
|
stream,
|
||||||
|
Some(WebSocketConfig::default().max_message_size(Some(512 * 1024 * 1024))), // TODO: softcode this
|
||||||
)?;
|
)?;
|
||||||
Ok(RacStream::WRAC(client))
|
Ok(RacStream::WRAC(client))
|
||||||
} else {
|
} else {
|
||||||
@ -172,32 +202,33 @@ pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Resu
|
|||||||
/// register_user(stream, name, name)
|
/// register_user(stream, name, name)
|
||||||
/// send_message_spoof_auth(stream, name + "> " + message)
|
/// send_message_spoof_auth(stream, name + "> " + message)
|
||||||
/// }
|
/// }
|
||||||
pub fn send_message_spoof_auth(stream: &mut RacStream, message: &str, remove_null: bool) -> Result<(), Box<dyn Error>> {
|
pub fn send_message_spoof_auth(
|
||||||
let Some((name, message)) = message.split_once("> ") else { return send_message(stream, message) };
|
stream: &mut RacStream,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let Some((name, message)) = message.split_once("> ") else {
|
||||||
|
return send_message(stream, message);
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(f) = send_message_auth(stream, &name, &message, &message, remove_null) {
|
if let Ok(f) = send_message_auth(stream, &name, &name, &message) {
|
||||||
if f != 0 {
|
if f != 0 {
|
||||||
let name = format!("\x1f{name}");
|
let name = format!("\x1f{name}");
|
||||||
register_user(stream, &name, &name, remove_null)?;
|
register_user(stream, &name, &name)?;
|
||||||
send_message_spoof_auth(stream, &format!("{name}> {message}"), remove_null)?;
|
send_message_spoof_auth(stream, &format!("{name}> {message}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Send message
|
/// Send message
|
||||||
///
|
///
|
||||||
/// stream - any stream that can be written to
|
/// stream - any stream that can be written to
|
||||||
/// message - message text
|
/// message - message text
|
||||||
pub fn send_message(
|
pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn Error>> {
|
||||||
stream: &mut RacStream,
|
|
||||||
message: &str
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
match stream {
|
match stream {
|
||||||
RacStream::WRAC(websocket) => wrac::send_message(websocket, message),
|
RacStream::WRAC(websocket) => wrac::send_message(websocket, message),
|
||||||
RacStream::RAC(stream) => rac::send_message(stream, message)
|
RacStream::RAC(stream) => rac::send_message(stream, message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,14 +241,13 @@ pub fn send_message(
|
|||||||
///
|
///
|
||||||
/// returns whether the user was registered
|
/// returns whether the user was registered
|
||||||
pub fn register_user(
|
pub fn register_user(
|
||||||
stream: &mut RacStream,
|
stream: &mut RacStream,
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
remove_null: bool
|
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
match stream {
|
match stream {
|
||||||
RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password),
|
RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password),
|
||||||
RacStream::RAC(stream) => rac::register_user(stream, name, password, remove_null)
|
RacStream::RAC(stream) => rac::register_user(stream, name, password),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,15 +263,14 @@ pub fn register_user(
|
|||||||
/// returns 1 if the user does not exist
|
/// returns 1 if the user does not exist
|
||||||
/// returns 2 if the password is incorrect
|
/// returns 2 if the password is incorrect
|
||||||
pub fn send_message_auth(
|
pub fn send_message_auth(
|
||||||
stream: &mut RacStream,
|
stream: &mut RacStream,
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
remove_null: bool
|
|
||||||
) -> Result<u8, Box<dyn Error>> {
|
) -> Result<u8, Box<dyn Error>> {
|
||||||
match stream {
|
match stream {
|
||||||
RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message),
|
RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message),
|
||||||
RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message, remove_null)
|
RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,14 +283,15 @@ pub fn send_message_auth(
|
|||||||
///
|
///
|
||||||
/// returns (messages, packet size)
|
/// returns (messages, packet size)
|
||||||
pub fn read_messages(
|
pub fn read_messages(
|
||||||
stream: &mut RacStream,
|
stream: &mut RacStream,
|
||||||
max_messages: usize,
|
max_messages: usize,
|
||||||
last_size: usize,
|
last_size: usize,
|
||||||
remove_null: bool,
|
chunked: bool,
|
||||||
chunked: bool
|
|
||||||
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||||
match stream {
|
match stream {
|
||||||
RacStream::WRAC(websocket) => wrac::read_messages(websocket, max_messages, last_size, chunked),
|
RacStream::WRAC(websocket) => {
|
||||||
RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, remove_null, chunked)
|
wrac::read_messages(websocket, max_messages, last_size, chunked)
|
||||||
|
}
|
||||||
|
RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, chunked),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
112
src/proto/rac.rs
@ -1,4 +1,7 @@
|
|||||||
use std::{error::Error, io::{Read, Write}};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
io::{Read, Write},
|
||||||
|
};
|
||||||
|
|
||||||
/// Send message
|
/// Send message
|
||||||
///
|
///
|
||||||
@ -18,25 +21,15 @@ pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dy
|
|||||||
///
|
///
|
||||||
/// returns whether the user was registered
|
/// returns whether the user was registered
|
||||||
pub fn register_user(
|
pub fn register_user(
|
||||||
stream: &mut (impl Write + Read),
|
stream: &mut (impl Write + Read),
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
remove_null: bool
|
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
|
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
|
||||||
if remove_null {
|
if let Ok(out) = skip_null(stream) {
|
||||||
if let Ok(out) = skip_null(stream) {
|
Ok(out[0] == 0)
|
||||||
Ok(out[0] == 0)
|
|
||||||
} else {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let mut buf = vec![0];
|
Ok(true)
|
||||||
if let Ok(1) = stream.read(&mut buf) {
|
|
||||||
Ok(buf[0] == 0)
|
|
||||||
} else {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,27 +45,16 @@ pub fn register_user(
|
|||||||
/// returns 1 if the user does not exist
|
/// returns 1 if the user does not exist
|
||||||
/// returns 2 if the password is incorrect
|
/// returns 2 if the password is incorrect
|
||||||
pub fn send_message_auth(
|
pub fn send_message_auth(
|
||||||
stream: &mut (impl Write + Read),
|
stream: &mut (impl Write + Read),
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
message: &str,
|
message: &str,
|
||||||
remove_null: bool
|
|
||||||
) -> Result<u8, Box<dyn Error>> {
|
) -> Result<u8, Box<dyn Error>> {
|
||||||
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?;
|
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?;
|
||||||
|
if let Ok(out) = skip_null(stream) {
|
||||||
if remove_null {
|
Ok(out[0])
|
||||||
if let Ok(out) = skip_null(stream) {
|
|
||||||
Ok(out[0])
|
|
||||||
} else {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let mut buf = vec![0];
|
Ok(0)
|
||||||
if let Ok(1) = stream.read(&mut buf) {
|
|
||||||
Ok(buf[0])
|
|
||||||
} else {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +64,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
|||||||
let mut buf = vec![0; 1];
|
let mut buf = vec![0; 1];
|
||||||
stream.read_exact(&mut buf)?;
|
stream.read_exact(&mut buf)?;
|
||||||
if buf[0] != 0 {
|
if buf[0] != 0 {
|
||||||
break Ok(buf)
|
break Ok(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +72,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
|
|||||||
/// remove trailing null bytes in vector
|
/// remove trailing null bytes in vector
|
||||||
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
|
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||||
while vec.ends_with(&[0]) {
|
while vec.ends_with(&[0]) {
|
||||||
vec.remove(vec.len()-1);
|
vec.remove(vec.len() - 1);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -104,29 +86,20 @@ pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
|
|||||||
///
|
///
|
||||||
/// returns (messages, packet size)
|
/// 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,
|
||||||
last_size: usize,
|
last_size: usize,
|
||||||
remove_null: bool,
|
chunked: bool,
|
||||||
chunked: bool
|
|
||||||
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||||
stream.write_all(&[0x00])?;
|
stream.write_all(&[0x00])?;
|
||||||
|
|
||||||
let packet_size = {
|
let packet_size = {
|
||||||
let data = if remove_null {
|
let mut data = skip_null(stream)?;
|
||||||
let mut data = skip_null(stream)?;
|
let mut buf = vec![0; 10];
|
||||||
let mut buf = vec![0; 10];
|
let len = stream.read(&mut buf)?;
|
||||||
let len = stream.read(&mut buf)?;
|
buf.truncate(len);
|
||||||
buf.truncate(len);
|
data.append(&mut buf);
|
||||||
data.append(&mut buf);
|
remove_trailing_null(&mut data)?;
|
||||||
remove_trailing_null(&mut data)?;
|
|
||||||
data
|
|
||||||
} else {
|
|
||||||
let mut data = vec![0; 10];
|
|
||||||
let len = stream.read(&mut data)?;
|
|
||||||
data.truncate(len);
|
|
||||||
data
|
|
||||||
};
|
|
||||||
|
|
||||||
String::from_utf8(data)?
|
String::from_utf8(data)?
|
||||||
.trim_matches(char::from(0))
|
.trim_matches(char::from(0))
|
||||||
@ -145,25 +118,24 @@ pub fn read_messages(
|
|||||||
packet_size - last_size
|
packet_size - last_size
|
||||||
};
|
};
|
||||||
|
|
||||||
let packet_data = if remove_null {
|
let mut packet_data = skip_null(stream)?;
|
||||||
let mut data = skip_null(stream)?;
|
let mut buf = vec![0; to_read - 1];
|
||||||
let mut buf = vec![0; to_read - 1];
|
stream.read_exact(&mut buf)?;
|
||||||
stream.read_exact(&mut buf)?;
|
packet_data.append(&mut buf);
|
||||||
data.append(&mut buf);
|
|
||||||
data
|
|
||||||
} else {
|
|
||||||
let mut data = vec![0; to_read];
|
|
||||||
stream.read_exact(&mut data)?;
|
|
||||||
data
|
|
||||||
};
|
|
||||||
|
|
||||||
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
|
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
|
||||||
|
|
||||||
let lines: Vec<&str> = packet_data.split("\n").collect();
|
let lines: Vec<&str> = packet_data.split("\n").collect();
|
||||||
let lines: Vec<String> = lines.clone().into_iter()
|
let lines: Vec<String> = lines
|
||||||
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.skip(if lines.len() >= max_messages {
|
||||||
|
lines.len() - max_messages
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
})
|
||||||
.map(|o| o.to_string())
|
.map(|o| o.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Some((lines, packet_size)))
|
Ok(Some((lines, packet_size)))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use std::{error::Error, io::{Read, Write}};
|
use std::{
|
||||||
use tungstenite::{WebSocket, Message};
|
error::Error,
|
||||||
|
io::{Read, Write},
|
||||||
|
};
|
||||||
|
use tungstenite::{Message, WebSocket};
|
||||||
|
|
||||||
/// Send message
|
/// Send message
|
||||||
///
|
///
|
||||||
@ -8,9 +10,11 @@ use tungstenite::{WebSocket, Message};
|
|||||||
/// message - message text
|
/// message - message text
|
||||||
pub fn send_message(
|
pub fn send_message(
|
||||||
stream: &mut WebSocket<impl Write + Read>,
|
stream: &mut WebSocket<impl Write + Read>,
|
||||||
message: &str
|
message: &str,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
stream.write(Message::Binary(format!("\x01{message}").as_bytes().to_vec().into()))?;
|
stream.write(Message::Binary(
|
||||||
|
format!("\x01{message}").as_bytes().to_vec().into(),
|
||||||
|
))?;
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -23,11 +27,13 @@ pub fn send_message(
|
|||||||
///
|
///
|
||||||
/// returns whether the user was registered
|
/// returns whether the user was registered
|
||||||
pub fn register_user(
|
pub fn register_user(
|
||||||
stream: &mut WebSocket<impl Write + Read>,
|
stream: &mut WebSocket<impl Write + Read>,
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str
|
password: &str,
|
||||||
) -> Result<bool, Box<dyn Error>> {
|
) -> Result<bool, Box<dyn Error>> {
|
||||||
stream.write(Message::Binary(format!("\x03{name}\n{password}").as_bytes().to_vec().into()))?;
|
stream.write(Message::Binary(
|
||||||
|
format!("\x03{name}\n{password}").as_bytes().to_vec().into(),
|
||||||
|
))?;
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
if let Ok(msg) = stream.read() {
|
if let Ok(msg) = stream.read() {
|
||||||
Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0)
|
Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0)
|
||||||
@ -47,12 +53,17 @@ pub fn register_user(
|
|||||||
/// returns 1 if the user does not exist
|
/// returns 1 if the user does not exist
|
||||||
/// returns 2 if the password is incorrect
|
/// returns 2 if the password is incorrect
|
||||||
pub fn send_message_auth(
|
pub fn send_message_auth(
|
||||||
stream: &mut WebSocket<impl Write + Read>,
|
stream: &mut WebSocket<impl Write + Read>,
|
||||||
name: &str,
|
name: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
message: &str
|
message: &str,
|
||||||
) -> Result<u8, Box<dyn Error>> {
|
) -> Result<u8, Box<dyn Error>> {
|
||||||
stream.write(Message::Binary(format!("\x02{name}\n{password}\n{message}").as_bytes().to_vec().into()))?;
|
stream.write(Message::Binary(
|
||||||
|
format!("\x02{name}\n{password}\n{message}")
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec()
|
||||||
|
.into(),
|
||||||
|
))?;
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
if let Ok(msg) = stream.read() {
|
if let Ok(msg) = stream.read() {
|
||||||
if msg.is_binary() {
|
if msg.is_binary() {
|
||||||
@ -73,10 +84,10 @@ pub fn send_message_auth(
|
|||||||
///
|
///
|
||||||
/// returns (messages, packet size)
|
/// returns (messages, packet size)
|
||||||
pub fn read_messages(
|
pub fn read_messages(
|
||||||
stream: &mut WebSocket<impl Write + Read>,
|
stream: &mut WebSocket<impl Write + Read>,
|
||||||
max_messages: usize,
|
max_messages: usize,
|
||||||
last_size: usize,
|
last_size: usize,
|
||||||
chunked: bool
|
chunked: bool,
|
||||||
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
|
||||||
stream.write(Message::Binary(vec![0x00].into()))?;
|
stream.write(Message::Binary(vec![0x00].into()))?;
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
@ -101,7 +112,9 @@ pub fn read_messages(
|
|||||||
stream.write(Message::Binary(vec![0x00, 0x01].into()))?;
|
stream.write(Message::Binary(vec![0x00, 0x01].into()))?;
|
||||||
packet_size
|
packet_size
|
||||||
} else {
|
} else {
|
||||||
stream.write(Message::Binary(format!("\x00\x02{}", last_size).as_bytes().to_vec().into()))?;
|
stream.write(Message::Binary(
|
||||||
|
format!("\x00\x02{}", last_size).as_bytes().to_vec().into(),
|
||||||
|
))?;
|
||||||
packet_size - last_size
|
packet_size - last_size
|
||||||
};
|
};
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
@ -119,10 +132,16 @@ pub fn read_messages(
|
|||||||
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
|
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
|
||||||
|
|
||||||
let lines: Vec<&str> = packet_data.split("\n").collect();
|
let lines: Vec<&str> = packet_data.split("\n").collect();
|
||||||
let lines: Vec<String> = lines.clone().into_iter()
|
let lines: Vec<String> = lines
|
||||||
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.skip(if lines.len() >= max_messages {
|
||||||
|
lines.len() - max_messages
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
})
|
||||||
.map(|o| o.to_string())
|
.map(|o| o.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Some((lines, packet_size)))
|
Ok(Some((lines, packet_size)))
|
||||||
}
|
}
|
||||||
|