Compare commits

..

No commits in common. "main" and "0.1.5+2.0" have entirely different histories.

43 changed files with 931 additions and 2539 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text

View File

@ -1,6 +0,0 @@
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
}
}

775
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,22 +12,19 @@ homedir = "0.3.4"
native-tls = "0.2.14"
clap = { version = "4.5.36", features = ["derive"] }
serde = { version = "1.0.219", features = ["serde_derive"] }
gtk4 = { version = "0.9.6", optional = true }
gtk4 = { version = "0.9.6", features = [ "v4_10" ] }
chrono = "0.4.40"
serde_default = "0.2.0"
socks = "0.3.4"
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
winapi = { version = "0.3.9", optional = true, features = ["wincon", "winuser"] }
tungstenite = "0.27.0"
[build-dependencies]
winresource = { version = "0.1.20", optional = true }
[features]
default = ["gtk"]
gtk = ["dep:gtk4"]
default = []
libnotify = ["dep:libnotify", "dep:gdk-pixbuf"]
notify-rust = ["dep:notify-rust"]
winapi = ["dep:winapi", "dep:winresource"]
winapi = ["dep:winapi"]
[build-dependencies]
winresource = "0.1.20"

View File

@ -1,2 +0,0 @@
[target.x86_64-pc-windows-gnu]
image = "mglolenstine/gtk4-cross:rust-gtk-latest"

View File

@ -1,42 +1,15 @@
.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
.PHONY: clean install uninstall
install: target/release/bRAC
mkdir -p ~/.local
mkdir -p ~/.local/bin
mkdir -p ~/.local/share
cp $< ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop
cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop
uninstall:
rm -rf ~/.config/bRAC ~/.local/share/bRAC
rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop
target/release/bRAC:
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:
cargo clean
rm -rf build
cargo clean

View File

@ -11,15 +11,15 @@ better RAC client
- gtk4 modern GUI
- RACv1.99.x and RACv2.0 compatible
- WRAC compatible ([docs](docs/wrac.md))
- WRAC compatible (can be enabled in the settings)
- chat commands (type /help)
- uses tor proxy as default (wracs://meex.lol:11234)
- no ip and date visible for anyone (almost)
- coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc.)
- many command-line options (see --help)
- rich configuration (--config-path to get file path)
- RACS/WRACS compatible (ex: wracs://meex.lol)
- reading messages chunked (less traffic usage)
- no ip and date visible for anyone
- uses TOR proxy server by default (meex.lol:11234)
- coloring usernames by their clients (CRAB, clRAC, Mefidroniy, etc)
- many command-line options (--help)
- rich configuration (--config-path to get file path and --configure to edit)
- RACS compatible (--enable-ssl or in --configure enable SSL)
- chunked reading messages
![screenshot](misc/image.png)
@ -39,8 +39,6 @@ better RAC client
NO SOLUTION
Read [compiling docs](docs/compiling.md) to build it manually.
### download binary
go to [releases](https://github.com/MeexReay/bRAC/releases/latest) and download file you need. its simple.
@ -61,8 +59,6 @@ cargo build -r # build release (target/release/bRAC)
cargo run -r # build and run
```
Read more about that on the [compiling docs](docs/compiling.md).
### nix package
If you have Nix package manager installed, you can use:
@ -86,21 +82,16 @@ messages starting with a slash are sent to chat only if the `--disable-commands`
## docs
- [Compiling](docs/compiling.md)
- [User agents](docs/user_agents.md)
- [Using as crate](docs/crate.md)
- [Message formats](docs/message_formats.md)
- [Authenticated mode](docs/auth_mode.md)
- [WRAC protocol (v2.0)](docs/wrac.md)
- [About RAC URL](docs/url.md)
- [Cross compile](docs/cross_compile.md)
- [FAQ](docs/faq.md)
## see also
- [Racinfo - webpage with info about RAC](https://racinfo.kostyazero.com/)
- [RAC-Hub - all about RAC protocol](https://meexreay.github.io/RAC-Hub/)
- [RAC-Hub - all about RAC protocol](https://the-stratosphere-solutions.github.io/RAC-Hub/)
- [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)
- [Mefidroniy - TUI client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client)
- [cRACk - client for RAC kettles](https://github.com/pansangg/cRACk)
- [Mefidroniy - client for RAC](https://github.com/OctoBanon-Main/mefedroniy-client)
- [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)

View File

@ -1,12 +1,16 @@
use std::io;
use {
std::{
env,
io,
},
winresource::WindowsResource,
};
fn main() -> io::Result<()> {
#[cfg(feature = "winapi")]
{
use {std::env, winresource::WindowsResource};
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
WindowsResource::new().set_icon("misc/icon.ico").compile()?;
}
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
WindowsResource::new()
.set_icon("misc/icon.ico")
.compile()?;
}
Ok(())
}
}

View File

@ -1,85 +0,0 @@
# 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 zip unzip curl
rustup toolchain install stable
./misc/build.sh
```
## From Windows to Linux
That's your problem

View File

@ -1,65 +0,0 @@
# 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("wracs://meex.lol", None)?;
// read docs/url.md
// this keep-alive way with only one connection
// works only for WRAC, for a regular RAC,
// you have to connect to the server on each request
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)

15
docs/cross_compile.md Normal file
View File

@ -0,0 +1,15 @@
# 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
```

47
docs/message_formats.md Normal file
View File

@ -0,0 +1,47 @@
# 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}`)

View File

@ -1,16 +0,0 @@
# 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:// |

View File

@ -1,20 +0,0 @@
# 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}`)

View File

@ -1,84 +0,0 @@
# 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*

View File

@ -1,23 +0,0 @@
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(())
}

12
misc/bRAC.desktop Normal file
View File

@ -0,0 +1,12 @@
[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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,46 +0,0 @@
#!/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 " - zip, unzip, curl. 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 # TODO: make this link auto-update
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
echo "@echo off" > build/windows-x86_64/start.bat
echo "set \"PATH=%CD%\bin;%PATH%\"" >> build/windows-x86_64/start.bat
echo "start \"\" /B \"bin\bRAC.exe\"" >> build/windows-x86_64/start.bat
./misc/mslink.sh -l "%COMSPEC% /C start start.bat" -o build/windows-x86_64/bRAC.lnk # TODO: fix this 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

View File

@ -1,15 +0,0 @@
#!/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"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,243 +0,0 @@
#!/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}"

View File

@ -27,4 +27,4 @@ set "v=%TEMP%\_s.vbs"
>>"%v%" echo l.Save
wscript "%v%" >nul
del "%v%" >nul
exit /b
exit /b

View File

@ -1,7 +1,5 @@
#!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
@ -10,4 +8,4 @@ fi
cp bRAC /bin/bRAC
chmod +x /bin/bRAC
cp ru.themixray.bRAC.png /usr/share/pixmaps
cp ru.themixray.bRAC.desktop /usr/share/applications
cp ru.themixray.bRAC.desktop /usr/share/applications

View File

@ -1,7 +1,5 @@
#!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
@ -13,4 +11,4 @@ done
rm -f /bin/bRAC
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

View File

@ -1,12 +1,7 @@
#!/bin/bash
mkdir -p ~/.local
mkdir -p ~/.local/bin
mkdir -p ~/.local/share
mkdir -p ~/.local/share/bRAC
cp misc/bRAC-gnotif ~/.local/bin/bRAC
cp bRAC ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop
cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop

View File

@ -1,88 +1,74 @@
use clap::Parser;
use serde_default::DefaultFromSerde;
use serde_yml;
use std::str::FromStr;
use std::{fs, path::PathBuf};
use serde_yml;
use serde_default::DefaultFromSerde;
use clap::Parser;
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
fn default_true() -> bool {
true
}
pub fn default_max_messages() -> usize {
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()
}
fn default_true() -> bool { true }
pub fn default_max_messages() -> usize { 200 }
pub fn default_update_time() -> usize { 50 }
pub fn default_host() -> String { "meex.lol:11234".to_string() }
pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
pub struct Config {
#[serde(default = "default_host")]
pub host: String,
#[serde(default)]
pub name: Option<String>,
#[serde(default = "default_message_format")]
pub message_format: String,
#[serde(default = "default_update_time")]
pub update_time: usize,
#[serde(default = "default_oof_update_time")]
pub oof_update_time: usize,
#[serde(default = "default_max_messages")]
pub max_messages: usize,
#[serde(default = "default_konata_size")]
pub konata_size: usize,
#[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 = "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,
#[serde(default = "default_host")] pub host: String,
#[serde(default)] pub name: Option<String>,
#[serde(default = "default_message_format")] pub message_format: String,
#[serde(default = "default_update_time")] pub update_time: usize,
#[serde(default = "default_max_messages")] pub max_messages: usize,
#[serde(default = "default_true")] pub hide_my_ip: bool,
#[serde(default)] pub show_other_ip: bool,
#[serde(default)] pub auth_enabled: bool,
#[serde(default)] pub ssl_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 wrac_enabled: bool,
#[serde(default)] pub proxy: Option<String>,
#[serde(default = "default_true")] pub notifications_enabled: bool,
}
#[cfg(target_os = "windows")]
pub fn get_config_path() -> PathBuf {
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())
}
let mut config_dir = PathBuf::from_str(".").unwrap();
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn get_config_path() -> PathBuf {
use homedir::my_home;
my_home()
.ok()
.flatten()
.map(|o| o.join(".config"))
.map(|o| o.join("bRAC"))
.unwrap_or("bRAC".into())
.join("config.yml")
#[cfg(not(target_os = "windows"))]
if let Some(dir) = {
let home_dir = {
use homedir::my_home;
my_home().ok().flatten()
};
#[cfg(target_os = "linux")]
let config_dir = {
let home_dir = home_dir.map(|o| o.join("bRAC"));
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 {
@ -108,100 +94,50 @@ pub fn save_config(path: PathBuf, config: &Config) {
#[command(version, about, long_about = None)]
pub struct Args {
/// Print config path
#[arg(short = 'p', long)]
#[arg(short='p', long)]
pub config_path: bool,
/// Print unformatted messages from chat and exit
#[arg(short = 'r', long)]
#[arg(short='r', long)]
pub read_messages: bool,
/// 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>,
#[arg(short = 'H', long)]
pub host: Option<String>,
#[arg(short = 'n', long)]
pub name: Option<String>,
#[arg(long)]
pub message_format: Option<String>,
#[arg(long)]
pub update_time: Option<usize>,
#[arg(long)]
pub oof_update_time: Option<usize>,
#[arg(long)]
pub max_messages: Option<usize>,
#[arg(long)]
pub konata_size: Option<usize>,
#[arg(long)]
pub hide_my_ip: Option<bool>,
#[arg(long)]
pub show_other_ip: 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,
#[arg(short='H', long)] pub host: Option<String>,
#[arg(short='n', long)] pub name: Option<String>,
#[arg(long)] pub message_format: Option<String>,
#[arg(long)] pub update_time: Option<usize>,
#[arg(long)] pub max_messages: Option<usize>,
#[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 ssl_enabled: 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 wrac_enabled: Option<bool>,
#[arg(long)] pub proxy: Option<String>,
}
impl Args {
pub fn patch_config(&self, config: &mut Config) {
if let Some(v) = self.host.clone() {
config.host = v
}
if let Some(v) = self.name.clone() {
config.name = Some(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.update_time {
config.update_time = 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.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
}
if let Some(v) = self.host.clone() { config.host = v }
if let Some(v) = self.name.clone() { config.name = Some(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.update_time { config.update_time = 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.show_other_ip { config.show_other_ip = 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.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 let Some(v) = self.wrac_enabled { config.wrac_enabled = v }
}
}
}

View File

@ -1,8 +1,4 @@
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc::Sender,
Arc, RwLock,
};
use std::sync::{atomic::{AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock};
use rand::random;
@ -11,11 +7,10 @@ use super::config::Config;
pub struct Context {
pub registered: RwLock<Option<String>>,
pub config: RwLock<Config>,
pub sender: RwLock<Option<Arc<Sender<(Vec<String>, bool)>>>>,
pub sender: RwLock<Option<Arc<Sender<(String, bool)>>>>,
pub messages: RwLock<Vec<String>>,
pub packet_size: AtomicUsize,
pub name: RwLock<String>,
pub is_focused: AtomicBool,
pub name: RwLock<String>
}
impl Context {
@ -26,13 +21,7 @@ impl Context {
sender: RwLock::new(None),
messages: RwLock::new(Vec::new()),
packet_size: AtomicUsize::default(),
name: RwLock::new(
config
.name
.clone()
.unwrap_or_else(|| format!("Anon#{:X}", random::<u16>())),
),
is_focused: AtomicBool::new(true),
name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()))),
}
}
@ -42,16 +31,13 @@ impl Context {
pub fn set_config(&self, config: &Config) {
*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.messages.write().unwrap() = Vec::new();
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())
}
@ -63,12 +49,7 @@ impl Context {
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);
let mut messages = messages;
if messages.len() > max_length {
@ -77,12 +58,7 @@ impl Context {
*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.add_message(max_length, messages);
}
@ -97,10 +73,12 @@ impl Context {
#[macro_export]
macro_rules! connect_rac {
($ctx:ident) => {
($ctx:ident) => {
&mut connect(
&$ctx.config(|o| o.host.clone()),
$ctx.config(|o| o.proxy.clone()),
)?
&$ctx.config(|o| o.host.clone()),
$ctx.config(|o| o.ssl_enabled),
$ctx.config(|o| o.proxy.clone()),
$ctx.config(|o| o.wrac_enabled)
)?
};
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 130 B

View File

@ -1,26 +1,19 @@
use std::{
error::Error,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
};
use crate::connect_rac;
use super::proto::{
connect, read_messages, register_user, send_message, send_message_auth,
};
use super::proto::{connect, read_messages, send_message, send_message_spoof_auth, register_user, send_message_auth};
use gui::{add_chat_message, clear_chat_messages};
use lazy_static::lazy_static;
use regex::Regex;
use ctx::Context;
#[cfg(feature = "gtk")]
pub mod gui;
#[cfg(feature = "gtk")]
pub use gui::run_main_loop;
#[cfg(feature = "gtk")]
use gui::{add_chat_messages, clear_chat_messages};
const HELP_MESSAGE: &str = "Help message:
/help - show help message
@ -38,22 +31,21 @@ lazy_static! {
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "#70fa7a".to_string()), // bRAC
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "#fa7070".to_string()), // CRAB
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "#da70fa".to_string()), // Mefidroniy
(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
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "green".to_string()), // bRAC
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "red".to_string()), // CRAB
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "magenta".to_string()), // Mefidroniy
(Regex::new(r"<(.*?)> (.*)").unwrap(), "cyan".to_string()), // clRAC
];
pub static ref SERVER_LIST: Vec<String> = vec![
"wracs://meex.lol:11234".to_string(),
"rac://meex.lol".to_string(),
"wracs://meex.lol".to_string(),
"rac://meex.lol".to_string(),
"rac://meex.lol:11234".to_string(),
"rac://91.192.22.20".to_string()
];
}
pub mod gui;
pub mod config;
pub mod ctx;
@ -63,67 +55,59 @@ pub fn sanitize_text(input: &str) -> String {
cleaned_text.into_owned()
}
#[cfg(feature = "gtk")]
pub fn add_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
for i in message.split("\n").map(|o| o.to_string()) {
for i in message.split("\n")
.map(|o| o.to_string()) {
print_message(ctx.clone(), i)?;
}
Ok(())
}
#[cfg(feature = "gtk")]
pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>> {
let command = command.trim_start_matches("/");
let (command, args) = command.split_once(" ").unwrap_or((&command, ""));
let args = args.split(" ").collect::<Vec<&str>>();
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()?;
for _ in 0..times {
send_message(connect_rac!(ctx), "\r")?;
}
} 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 msg = args[1..].join(" ");
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" {
add_message(ctx.clone(), HELP_MESSAGE)?;
} 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")?;
return Ok(());
return Ok(())
};
match register_user(connect_rac!(ctx), &ctx.name(), pass) {
match register_user(connect_rac!(ctx), &ctx.name(), pass, !ctx.config(|o| o.ssl_enabled)) {
Ok(true) => {
add_message(ctx.clone(), "you was registered successfully bro")?;
*ctx.registered.write().unwrap() = Some(pass.to_string());
}
},
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" {
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")?;
return Ok(());
return Ok(())
};
add_message(ctx.clone(), "ye bro you was logged in")?;
*ctx.registered.write().unwrap() = Some(pass.to_string());
} else if command == "ping" {
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)?;
@ -131,13 +115,12 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
loop {
let data = read_messages(
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
before,
ctx.config(|o| o.chunked_enabled),
)
.ok()
.flatten();
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
before,
!ctx.config(|o| o.ssl_enabled),
ctx.config(|o| o.chunked_enabled)
).ok().flatten();
if let Some((data, size)) = data {
if let Some(last) = data.iter().rev().find(|o| o.contains(&message)) {
@ -152,10 +135,7 @@ 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 {
add_message(ctx.clone(), "Unknown command bruh")?;
}
@ -164,18 +144,23 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
}
pub fn prepare_message(ctx: Arc<Context>, message: &str) -> String {
format!(
"{}{}{}",
format!("{}{}{}",
if ctx.config(|o| o.hide_my_ip) {
"\r\x07"
} else {
""
},
message,
if !ctx.config(|o| o.hide_my_ip) {
if message.chars().count() < 54 {
" ".repeat(54 - message.chars().count())
if !ctx.config(|o| o.hide_my_ip) {
let spaces = if ctx.config(|o| o.auth_enabled) {
39
} else {
54
};
if message.chars().count() < spaces {
" ".repeat(spaces-message.chars().count())
} else {
String::new()
}
} else {
@ -184,90 +169,101 @@ 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>> {
ctx.add_message(ctx.config(|o| o.max_messages), vec![message.clone()]);
add_chat_messages(ctx.clone(), vec![message]);
add_chat_message(ctx.clone(), message);
Ok(())
}
#[cfg(feature = "gtk")]
pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
let last_size = ctx.packet_size();
match read_messages(
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
ctx.packet_size(),
ctx.config(|o| o.chunked_enabled),
connect_rac!(ctx),
ctx.config(|o| o.max_messages),
ctx.packet_size(),
!ctx.config(|o| o.ssl_enabled),
ctx.config(|o| o.chunked_enabled)
) {
Ok(Some((messages, size))) => {
if ctx.config(|o| o.chunked_enabled) {
ctx.add_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
if last_size == 0 {
clear_chat_messages(ctx.clone(), messages);
if messages.len() >= 1 {
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 {
add_chat_messages(ctx.clone(), messages);
for msg in messages {
add_chat_message(ctx.clone(), msg.clone());
}
}
} else {
ctx.put_messages_packet(ctx.config(|o| o.max_messages), messages.clone(), size);
clear_chat_messages(ctx.clone(), messages);
if messages.len() >= 1 {
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) => {
if ctx.config(|o| o.debug_logs) {
add_chat_messages(
ctx.clone(),
vec![format!("Read messages error: {}", e.to_string())],
);
}
println!("Read messages error: {}", e.to_string())
}
_ => {}
}
thread::sleep(Duration::from_millis(ctx.config(|o| o.update_time) as u64));
Ok(())
}
#[cfg(feature = "gtk")]
pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn Error>> {
if message.starts_with("/") && ctx.config(|o| o.commands_enabled) {
on_command(ctx.clone(), &message)?;
} else {
let message = prepare_message(
ctx.clone(),
&ctx.config(|o| o.message_format.clone())
.replace("{name}", &ctx.name())
.replace("{text}", &message),
ctx.clone(),
&ctx.config(|o| o.message_format.clone())
.replace("{name}", &ctx.name())
.replace("{text}", &message)
);
if let Some(password) = ctx.registered.read().unwrap().clone() {
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message)?;
send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message, !ctx.config(|o| o.ssl_enabled))?;
} else if ctx.config(|o| o.auth_enabled) {
send_message_spoof_auth(connect_rac!(ctx), &message, !ctx.config(|o| o.ssl_enabled))?;
} else {
send_message(connect_rac!(ctx), &message)?;
}
}
Ok(())
}
}
pub fn sanitize_message(message: String) -> Option<String> {
let message = sanitize_text(&message);
let message = message.trim().to_string();
Some(message)
}
/// 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() {
return None;
return None
}
let date = DATE_REGEX.captures(&message)?;
let (date, message) = (
date.get(1)?.as_str().to_string(),
date.get(2)?.as_str().to_string(),
date.get(1)?.as_str().to_string(),
date.get(2)?.as_str().to_string(),
);
let message = message
@ -278,14 +274,11 @@ pub fn parse_message(
.to_string();
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 {
(None, message)
};
let (message, nick) = match find_username_color(&message) {
Some((name, content, color)) => (content, Some((name, color))),
None => (message, None),
@ -298,12 +291,8 @@ pub fn parse_message(
pub fn find_username_color(message: &str) -> Option<(String, String, String)> {
for (re, color) in COLORED_USERNAMES.iter() {
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
}
}

View File

@ -1,6 +1,3 @@
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(255, 255, 255); }
.message-content { color:rgb(255, 255, 255); }
.message-date { color:rgb(146, 146, 146); }
.message-ip { color:rgb(73, 73, 73); } */
.message-ip { color:rgb(73, 73, 73); }

View File

@ -1,6 +1,3 @@
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(0, 0, 0); }
.message-content { color:rgb(0, 0, 0); }
.message-date { color:rgb(41, 41, 41); }
.message-ip { color:rgb(88, 88, 88); } */
.message-ip { color:rgb(88, 88, 88); }

View File

@ -1,3 +1,5 @@
.send-button, .send-text { border-radius: 0; }
.calendar {
transform: scale(0.6);
@ -14,11 +16,9 @@
font-weight: bold;
}
/* Now made with GTK Pango Markup */
/* .message-name { font-weight: bold; }
.message-name { font-weight: bold; }
.message-name-green { color: #70fa7a; }
.message-name-red { color: #fa7070; }
.message-name-magenta { color: #da70fa; }
.message-name-cyan { color: #70fadc; } */
.message-name-cyan { color: #70fadc; }

View File

@ -1,4 +1,4 @@
#![allow(non_snake_case)]
pub mod chat;
pub mod proto;
pub mod proto;

View File

@ -1,20 +1,15 @@
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 clap::Parser;
use bRAC::chat::{config::{get_config_path, load_config, Args}, ctx::Context, run_main_loop};
use clap::Parser;
fn main() {
#[cfg(feature = "winapi")]
unsafe {
winapi::um::wincon::FreeConsole()
};
unsafe { winapi::um::wincon::FreeConsole() };
let args = Args::parse();
let config_path = get_config_path();
if args.config_path {
@ -24,39 +19,37 @@ fn main() {
let mut config = load_config(config_path);
args.patch_config(&mut config);
if args.read_messages {
let mut stream =
connect(&config.host, config.proxy.clone()).expect("Error reading message");
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error reading message");
print!(
"{}",
read_messages(&mut stream, config.max_messages, 0, false)
.ok()
.flatten()
.expect("Error reading messages")
.0
.join("\n")
print!("{}", read_messages(
&mut stream,
config.max_messages,
0,
!config.ssl_enabled,
false
)
.ok().flatten()
.expect("Error reading messages").0.join("\n")
);
}
if let Some(message) = &args.send_message {
let mut stream =
connect(&config.host, config.proxy.clone()).expect("Error sending message");
let mut stream = connect(&config.host, config.ssl_enabled, config.proxy.clone(), config.wrac_enabled).expect("Error sending message");
send_message(&mut stream, message).expect("Error sending message");
send_message(
&mut stream,
message
).expect("Error sending message");
}
if args.send_message.is_some() || args.read_messages {
return;
}
#[cfg(feature = "gtk")]
{
let ctx = Arc::new(Context::new(&config));
args.patch_config(&mut config);
let ctx = Arc::new(Context::new(&config));
use bRAC::chat::run_main_loop;
run_main_loop(ctx.clone());
}
run_main_loop(ctx.clone());
}

View File

@ -1,14 +1,8 @@
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 socks::Socks5Stream;
use tungstenite::{client::client_with_config, protocol::WebSocketConfig, WebSocket};
use tungstenite::WebSocket;
pub mod rac;
pub mod wrac;
@ -19,44 +13,28 @@ pub trait Stream: Read + Write + Unpin + Send + Sync + Debug {
}
impl Stream for TcpStream {
fn set_read_timeout(&self, timeout: Duration) {
let _ = TcpStream::set_read_timeout(&self, Some(timeout));
}
fn set_write_timeout(&self, timeout: Duration) {
let _ = TcpStream::set_write_timeout(&self, Some(timeout));
}
fn set_read_timeout(&self, timeout: Duration) { 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 {
fn set_read_timeout(&self, timeout: Duration) {
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));
}
fn set_read_timeout(&self, timeout: Duration) { 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> {
fn set_read_timeout(&self, timeout: Duration) {
self.get_ref().set_read_timeout(timeout);
}
fn set_write_timeout(&self, timeout: Duration) {
self.get_ref().set_write_timeout(timeout);
}
fn set_read_timeout(&self, timeout: Duration) { 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>> {
fn set_read_timeout(&self, timeout: Duration) {
self.get_ref().set_read_timeout(timeout);
}
fn set_write_timeout(&self, timeout: Duration) {
self.get_ref().set_write_timeout(timeout);
}
fn set_read_timeout(&self, timeout: Duration) { self.get_ref().set_read_timeout(timeout); }
fn set_write_timeout(&self, timeout: Duration) { self.get_ref().set_write_timeout(timeout); }
}
pub enum RacStream {
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"))` \
@ -86,42 +64,46 @@ pub fn parse_rac_url(url: &str) -> Option<(String, bool, bool)> {
let (scheme, url) = url.split_once("://").unwrap_or(("rac", url));
let (host, _) = url.split_once("/").unwrap_or((url, ""));
match scheme.to_lowercase().as_str() {
"rac" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42666")
},
false,
false,
)),
"racs" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42667")
},
true,
false,
)),
"wrac" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52666")
},
false,
true,
)),
"wracs" => Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52667")
},
true,
true,
)),
"rac" => {
Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42666")
},
false, false
))
},
"racs" => {
Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:42667")
},
true, false
))
},
"wrac" => {
Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52666")
},
false, true
))
},
"wracs" => {
Some((
if host.contains(":") {
host.to_string()
} else {
format!("{host}:52667")
},
true, true
))
},
_ => None,
}
}
@ -132,19 +114,14 @@ 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
/// proxy - socks5 proxy (host, (user, pass))
/// wrac - to use wrac protocol
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())?;
pub fn connect(host: &str, ssl: bool, proxy: Option<String>, wrac: bool) -> Result<RacStream, Box<dyn Error>> {
let (host, ssl_, wrac_) = parse_rac_url(host).ok_or::<Box<dyn Error>>("url parse error".into())?;
let (ssl, wrac) = (ssl_ || ssl, wrac_ || wrac);
let stream: Box<dyn Stream> = if let Some(proxy) = proxy {
if let Some((proxy, auth)) = parse_socks5_url(&proxy) {
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 {
Box::new(Socks5Stream::connect(&proxy, host.as_str())?)
}
@ -152,39 +129,32 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
return Err("proxy parse error".into());
}
} 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)?)
};
let stream = if ssl {
let ip: String = host
.split_once(":")
let ip: String = host.split_once(":")
.map(|o| o.0.to_string())
.unwrap_or(host.clone());
Box::new(
TlsConnector::builder()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()?
.connect(&ip, stream)?,
)
Box::new(TlsConnector::builder()
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()?
.connect(&ip, stream)?)
} else {
stream
};
stream.set_read_timeout(Duration::from_secs(15)); // TODO: softcode this
stream.set_write_timeout(Duration::from_secs(15));
stream.set_read_timeout(Duration::from_secs(3));
stream.set_write_timeout(Duration::from_secs(3));
if wrac {
let (client, _) = client_with_config(
&format!("ws://{host}"),
stream,
Some(WebSocketConfig::default().max_message_size(Some(512 * 1024 * 1024))), // TODO: softcode this
let (client, _) = tungstenite::client(
&format!("ws{}://{host}", if ssl { "s" } else { "" }),
stream
)?;
Ok(RacStream::WRAC(client))
} else {
@ -192,14 +162,42 @@ pub fn connect(host: &str, proxy: Option<String>) -> Result<RacStream, Box<dyn E
}
}
/// Send message with fake auth
///
/// Explaination:
///
/// let (name, message) = message.split("> ") else { return send_message(stream, message) }
/// if send_message_auth(name, name, message) != 0 {
/// let name = "\x1f" + name
/// register_user(stream, name, name)
/// 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>> {
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 f != 0 {
let name = format!("\x1f{name}");
register_user(stream, &name, &name, remove_null)?;
send_message_spoof_auth(stream, &format!("{name}> {message}"), remove_null)?;
}
}
Ok(())
}
/// Send message
///
/// stream - any stream that can be written to
/// message - message text
pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn Error>> {
pub fn send_message(
stream: &mut RacStream,
message: &str
) -> Result<(), Box<dyn Error>> {
match stream {
RacStream::WRAC(websocket) => wrac::send_message(websocket, message),
RacStream::RAC(stream) => rac::send_message(stream, message),
RacStream::RAC(stream) => rac::send_message(stream, message)
}
}
@ -212,13 +210,14 @@ pub fn send_message(stream: &mut RacStream, message: &str) -> Result<(), Box<dyn
///
/// returns whether the user was registered
pub fn register_user(
stream: &mut RacStream,
name: &str,
password: &str,
stream: &mut RacStream,
name: &str,
password: &str,
remove_null: bool
) -> Result<bool, Box<dyn Error>> {
match stream {
RacStream::WRAC(websocket) => wrac::register_user(websocket, name, password),
RacStream::RAC(stream) => rac::register_user(stream, name, password),
RacStream::RAC(stream) => rac::register_user(stream, name, password, remove_null)
}
}
@ -234,14 +233,15 @@ pub fn register_user(
/// returns 1 if the user does not exist
/// returns 2 if the password is incorrect
pub fn send_message_auth(
stream: &mut RacStream,
name: &str,
password: &str,
message: &str,
stream: &mut RacStream,
name: &str,
password: &str,
message: &str,
remove_null: bool
) -> Result<u8, Box<dyn Error>> {
match stream {
RacStream::WRAC(websocket) => wrac::send_message_auth(websocket, name, password, message),
RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message),
RacStream::RAC(stream) => rac::send_message_auth(stream, name, password, message, remove_null)
}
}
@ -254,15 +254,14 @@ pub fn send_message_auth(
///
/// returns (messages, packet size)
pub fn read_messages(
stream: &mut RacStream,
max_messages: usize,
last_size: usize,
chunked: bool,
stream: &mut RacStream,
max_messages: usize,
last_size: usize,
remove_null: bool,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
match stream {
RacStream::WRAC(websocket) => {
wrac::read_messages(websocket, max_messages, last_size, chunked)
}
RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, chunked),
RacStream::WRAC(websocket) => wrac::read_messages(websocket, max_messages, last_size, chunked),
RacStream::RAC(stream) => rac::read_messages(stream, max_messages, last_size, remove_null, chunked)
}
}
}

View File

@ -1,7 +1,4 @@
use std::{
error::Error,
io::{Read, Write},
};
use std::{error::Error, io::{Read, Write}};
/// Send message
///
@ -21,15 +18,25 @@ pub fn send_message(stream: &mut impl Write, message: &str) -> Result<(), Box<dy
///
/// returns whether the user was registered
pub fn register_user(
stream: &mut (impl Write + Read),
name: &str,
password: &str,
stream: &mut (impl Write + Read),
name: &str,
password: &str,
remove_null: bool
) -> Result<bool, Box<dyn Error>> {
stream.write_all(format!("\x03{name}\n{password}").as_bytes())?;
if let Ok(out) = skip_null(stream) {
Ok(out[0] == 0)
if remove_null {
if let Ok(out) = skip_null(stream) {
Ok(out[0] == 0)
} else {
Ok(true)
}
} else {
Ok(true)
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0] == 0)
} else {
Ok(true)
}
}
}
@ -45,16 +52,27 @@ pub fn register_user(
/// returns 1 if the user does not exist
/// returns 2 if the password is incorrect
pub fn send_message_auth(
stream: &mut (impl Write + Read),
name: &str,
password: &str,
message: &str,
stream: &mut (impl Write + Read),
name: &str,
password: &str,
message: &str,
remove_null: bool
) -> Result<u8, Box<dyn Error>> {
stream.write_all(format!("\x02{name}\n{password}\n{message}").as_bytes())?;
if let Ok(out) = skip_null(stream) {
Ok(out[0])
if remove_null {
if let Ok(out) = skip_null(stream) {
Ok(out[0])
} else {
Ok(0)
}
} else {
Ok(0)
let mut buf = vec![0];
if let Ok(1) = stream.read(&mut buf) {
Ok(buf[0])
} else {
Ok(0)
}
}
}
@ -64,7 +82,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
let mut buf = vec![0; 1];
stream.read_exact(&mut buf)?;
if buf[0] != 0 {
break Ok(buf);
break Ok(buf)
}
}
}
@ -72,7 +90,7 @@ pub fn skip_null(stream: &mut impl Read) -> Result<Vec<u8>, Box<dyn Error>> {
/// remove trailing null bytes in vector
pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
while vec.ends_with(&[0]) {
vec.remove(vec.len() - 1);
vec.remove(vec.len()-1);
}
Ok(())
}
@ -86,20 +104,29 @@ pub fn remove_trailing_null(vec: &mut Vec<u8>) -> Result<(), Box<dyn Error>> {
///
/// returns (messages, packet size)
pub fn read_messages(
stream: &mut (impl Read + Write),
max_messages: usize,
last_size: usize,
chunked: bool,
stream: &mut (impl Read + Write),
max_messages: usize,
last_size: usize,
remove_null: bool,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write_all(&[0x00])?;
let packet_size = {
let mut data = skip_null(stream)?;
let mut buf = vec![0; 10];
let len = stream.read(&mut buf)?;
buf.truncate(len);
data.append(&mut buf);
remove_trailing_null(&mut data)?;
let data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; 10];
let len = stream.read(&mut buf)?;
buf.truncate(len);
data.append(&mut buf);
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)?
.trim_matches(char::from(0))
@ -118,24 +145,25 @@ pub fn read_messages(
packet_size - last_size
};
let mut packet_data = skip_null(stream)?;
let mut buf = vec![0; to_read - 1];
stream.read_exact(&mut buf)?;
packet_data.append(&mut buf);
let packet_data = if remove_null {
let mut data = skip_null(stream)?;
let mut buf = vec![0; to_read - 1];
stream.read_exact(&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 lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines
.clone()
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
let lines: Vec<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.map(|o| o.to_string())
.collect();
Ok(Some((lines, packet_size)))
}
}

View File

@ -1,8 +1,6 @@
use std::{
error::Error,
io::{Read, Write},
};
use tungstenite::{Message, WebSocket};
use std::{error::Error, io::{Read, Write}};
use tungstenite::{WebSocket, Message};
/// Send message
///
@ -10,11 +8,9 @@ use tungstenite::{Message, WebSocket};
/// message - message text
pub fn send_message(
stream: &mut WebSocket<impl Write + Read>,
message: &str,
message: &str
) -> 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()?;
Ok(())
}
@ -27,13 +23,11 @@ pub fn send_message(
///
/// returns whether the user was registered
pub fn register_user(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str,
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str
) -> 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()?;
if let Ok(msg) = stream.read() {
Ok(!msg.is_binary() || msg.into_data().get(0).unwrap_or(&0) == &0)
@ -53,17 +47,12 @@ pub fn register_user(
/// returns 1 if the user does not exist
/// returns 2 if the password is incorrect
pub fn send_message_auth(
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str,
message: &str,
stream: &mut WebSocket<impl Write + Read>,
name: &str,
password: &str,
message: &str
) -> 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()?;
if let Ok(msg) = stream.read() {
if msg.is_binary() {
@ -84,10 +73,10 @@ pub fn send_message_auth(
///
/// returns (messages, packet size)
pub fn read_messages(
stream: &mut WebSocket<impl Write + Read>,
max_messages: usize,
last_size: usize,
chunked: bool,
stream: &mut WebSocket<impl Write + Read>,
max_messages: usize,
last_size: usize,
chunked: bool
) -> Result<Option<(Vec<String>, usize)>, Box<dyn Error>> {
stream.write(Message::Binary(vec![0x00].into()))?;
stream.flush()?;
@ -112,9 +101,7 @@ pub fn read_messages(
stream.write(Message::Binary(vec![0x00, 0x01].into()))?;
packet_size
} 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
};
stream.flush()?;
@ -132,16 +119,10 @@ pub fn read_messages(
let packet_data = String::from_utf8_lossy(&packet_data).to_string();
let lines: Vec<&str> = packet_data.split("\n").collect();
let lines: Vec<String> = lines
.clone()
.into_iter()
.skip(if lines.len() >= max_messages {
lines.len() - max_messages
} else {
0
})
let lines: Vec<String> = lines.clone().into_iter()
.skip(if lines.len() >= max_messages { lines.len() - max_messages } else { 0 })
.map(|o| o.to_string())
.collect();
Ok(Some((lines, packet_size)))
}
}