Skip to content

Commit

Permalink
Cranberry TUI - init
Browse files Browse the repository at this point in the history
  • Loading branch information
Paddyk45 committed Dec 11, 2023
1 parent 3c45cba commit b4f8f46
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 182 deletions.
10 changes: 4 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ strip = true
opt-level = 'z'

[dependencies]
ansi-to-tui = "3.1.0"
better-panic = "0.3.0"
clap = { version = "4.4.11", features = ["derive"] }
color-eyre = "0.6.2"
fastrand = "2.0.1"
crossterm = "0.27.0"
owo-colors = "3.5.0"
rayon = "1.8.0"
rustyline = "13.0.0"
ratatui = "0.24.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
tokio = { version = "1.35.0", features = ["full"] }

Binary file added cranberry.zip
Binary file not shown.
52 changes: 52 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::cli::Args;
use crate::net_handler::*;
use crate::tui::ui;
use clap::Parser;
use crossterm::event;
use crossterm::event::{Event, KeyCode};
use ratatui::backend::Backend;
use ratatui::Terminal;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use std::thread::spawn;
use std::time::Duration;

#[derive(Default)]
pub struct App {
pub input: String,
pub cursor_pos: usize,
pub messages: Vec<String>,
pub message_queue: Vec<String>,
pub input_history: Vec<String>,
pub debug_messages: Vec<String>,
}

impl App {
pub fn run(self, term: &mut Terminal<impl Backend>) {
let app = Arc::new(RwLock::new(self));
let cli_args = Args::parse();
let stream =
TcpStream::connect((cli_args.addr, cli_args.port)).expect("Failed to open stream");
let stream_r = stream.try_clone().unwrap();
let stream_w = stream.try_clone().unwrap();
let c1 = app.clone();
let c2 = c1.clone();
spawn(|| handler_c2s(c1, stream_r));
spawn(|| handler_s2c(c2, stream_w));
loop {
term.draw(|f| ui(f, app.clone())).expect("Error drawing");
if !event::poll(Duration::from_millis(100)).expect("Failed to poll event") {
continue;
}
if let Event::Key(key) = event::read().expect("Failed to read event") {
match key.code {
KeyCode::Enter => app.write().unwrap().send(),
KeyCode::Esc => return,
KeyCode::Char(c) => app.write().unwrap().enter(c),
KeyCode::Backspace => app.write().unwrap().delete(),
_ => {}
}
}
}
}
}
4 changes: 0 additions & 4 deletions src/command.rs

This file was deleted.

206 changes: 34 additions & 172 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,179 +1,41 @@
#![allow(deprecated)]
#![warn(
clippy::all,
clippy::nursery,
clippy::pedantic,
)]

use std::io::Write;
use crate::command::Command;
use clap::Parser;
use owo_colors::OwoColorize;
use serde_json::Value;
use std::process::exit;
use std::thread::sleep_ms;
use std::time::Duration;
use rayon::prelude::*;
use rustyline::DefaultEditor;
use rustyline::error::ReadlineError;
use tokio::io::{split, AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
use tokio::net::TcpStream;
use tokio::time::sleep;

mod app;
mod cli;
mod command;
mod net_handler;
mod tui;

async fn s2c_t(mut r_server: ReadHalf<TcpStream>) {
loop {
let mut buff = [0u8; 1];
let mut str_buf = String::new();
let mut wraps = 0;
loop {
let n_bytes = r_server.read(&mut buff).await.expect("Failed to read from stream");
if n_bytes == 0 {
println!("Server closed connection, exiting");
exit(0);
}
match buff[0] as char {
'{' => {
wraps += 1;
str_buf.push('{');
}
'}' => {
wraps -= 1;
str_buf.push('}');
}
c => str_buf.push(c)
}
if wraps == 0 {
break
}
//dbg!(wraps, &str_buf);
}
let msg: Value = match serde_json::from_str(&str_buf) {
Ok(ok) => ok,
Err(e) => {
println!("{} error desering packet ({str_buf}): {e}", "[err]".red());
continue;
}
};
match msg["message_type"].as_str() {
Some("system_message") => println!(
"{} {}",
"[sys]".bright_green(),
msg["message"]["content"].as_str().unwrap()
),
Some("stbchat_backend") => {}
Some("user_message") => println!(
"{} {} ({}) -> {}",
"[msg]".bright_blue(),
msg["username"].as_str().unwrap(),
msg["nickname"].as_str().unwrap(),
msg["message"]["content"].as_str().unwrap()
),
None => unreachable!(),
m => println!(
"{} Unimplemented packet {} - full packet: {}",
"[uimp]".red(),
m.unwrap(),
str_buf
),
}
}
}


async fn c2s_t(mut w_server: WriteHalf<TcpStream>) {
let cmds = vec![
Command {
name: "randchars",
handler: |a| {
let num_chars = a.first().map_or(600, |s| s.parse().unwrap_or(600));
let num_messages = a.get(1).map_or(1, |s| s.parse().unwrap_or(1));
let mut res = vec![];
for _ in 0..num_messages {
let mut bytes = String::new();
for _ in 0..num_chars {
bytes.push(fastrand::char(..));
}
res.push(bytes);
}
use crate::cli::Args;
use better_panic::Settings;
use clap::Parser;
use crossterm::execute;
use crossterm::terminal::*;
use ratatui::prelude::*;
use std::io;
use std::io::stdout;
use crate::app::App;

res
},
},
Command {
name: "loginspam",
handler: |a| {
if a.len() < 2 {
println!("Not enough args - 2 required");
return vec![];
}
let args = cli::Args::parse();
let user = a.first().unwrap();
let pass = a.get(1).unwrap();
let logins: u64 = a.get(2).unwrap_or(&"10".to_string()).parse().unwrap_or(10);
(0..logins).into_par_iter().for_each(|_| {
let mut stream = std::net::TcpStream::connect((args.addr.clone(), args.port)).unwrap();
sleep_ms(230);
stream.write_all(user.as_bytes()).unwrap();
sleep_ms(230);
stream.write_all(pass.as_bytes()).unwrap();
sleep_ms(300);
});
vec![]
},
},
];
let mut rl = DefaultEditor::new().unwrap();
loop {
let line = match rl.readline("") {
Ok(l) => {
rl.add_history_entry(&l).unwrap();
l
},
Err(ReadlineError::Eof) => exit(0),
Err(ReadlineError::Interrupted) => {
w_server.write_all("/exit".as_bytes()).await.expect("Failed to write to stream");
sleep(Duration::from_millis(400)).await;
exit(0);
},
Err(why) => panic!("{why}")
};
let f = line.split(' ').next().unwrap();
let cmd_filt = cmds.iter().filter(|c| c.name == f);
for cmd in cmd_filt.clone() {
let args: Vec<String> = line.split(' ').map(String::from).skip(1).collect();
for mut m in (cmd.handler)(args) {
while m.len() > 4096 {
m.pop();
}
w_server
.write_all(m.as_bytes())
.await
.expect("Failed to write to stream");
sleep(Duration::from_millis(50)).await;
}
}
if cmd_filt.count() > 0 {
continue;
}
let read_str = line.trim_end_matches('\n');
w_server
.write_all(read_str.as_bytes())
.await
.expect("Failed to write to stream");
}
pub fn initialize_panic_handler() {
std::panic::set_hook(Box::new(|panic_info| {
execute!(std::io::stderr(), crossterm::terminal::LeaveAlternateScreen).unwrap();
disable_raw_mode().unwrap();
Settings::auto()
.most_recent_first(false)
.lineno_suffix(true)
.create_panic_handler()(panic_info);
}));
}

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
color_eyre::install().unwrap();
rayon::ThreadPoolBuilder::new().num_threads(5).build_global().unwrap();
let args = cli::Args::parse();
let stream = TcpStream::connect((args.addr, args.port)).await?;
let halves = split(stream);
tokio::spawn(s2c_t(halves.0));
tokio::spawn(c2s_t(halves.1)).await.expect("Join error");
fn main() -> io::Result<()> {
initialize_panic_handler();
enable_raw_mode()?;
Args::parse();
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.clear()?;
let app = App::default();
app.run(&mut terminal);
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
disable_raw_mode()?;
Ok(())
}
63 changes: 63 additions & 0 deletions src/net_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crossterm::style::Stylize;
use owo_colors::OwoColorize;
use serde_json::Value;
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use crate::app::App;

pub fn handler_s2c(app: Arc<RwLock<App>>, stream: TcpStream) {
let deser = serde_json::Deserializer::from_reader(stream);
let messages_iter = deser.into_iter::<Value>();
for message in messages_iter {
match message {
Err(e) => app.write().unwrap().messages.push(format!(
"{} Error deserializing packet - {}",
"[err]".red(),
e
)),
Ok(msg) => {
let to_push = match msg["message_type"].as_str() {
Some("system_message") => Some(format!(
"{} {}",
"[sys]".bright_green(),
msg["message"]["content"].as_str().unwrap()
)),
Some("stbchat_backend") => None,
Some("user_message") => Some(format!(
"{} {} ({}) -> {}",
"[msg]".bright_blue(),
msg["username"].as_str().unwrap(),
msg["nickname"].as_str().unwrap(),
msg["message"]["content"].as_str().unwrap()
)),
None => None,
m => {
println!("{} Unimplemented packet {}", "[uimp]".red(), m.unwrap());
None
}
};
if let Some(text) = to_push {
app.write().unwrap().messages.push(text);
}
}
}
}
}

pub fn handler_c2s(app: Arc<RwLock<App>>, mut stream: TcpStream) {
loop {
let state = app.read().unwrap();
let msgs = state.message_queue.clone();
drop(state);
match msgs.last() {
Some(msg) => {
stream
.write_all(msg.as_bytes())
.expect("Failed to write to stream");
app.write().unwrap().message_queue.pop();
}
None => {}
}
}
}
Loading

0 comments on commit b4f8f46

Please sign in to comment.