make a node gui

daemon
lza_menace 1 year ago
parent 937240a52f
commit 66b9552cd5

@ -0,0 +1,75 @@
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::time::SystemTime;
use std::io::{prelude::*, BufReader};
use std::net::TcpStream;
use std::str;
use std::thread;
use regex::Regex;
pub fn send_cmd(mut stream: &TcpStream, cmd: &str, msg: String) -> Result<usize, std::io::Error> {
let mut cmd = cmd.to_string();
cmd.push_str(" ");
cmd.push_str(&msg);
println!("sending: {}", cmd.trim());
stream.write(cmd.as_bytes())
}
pub fn write_log(log: &str, first_line: bool) -> Result<(), std::io::Error> {
if std::fs::File::open(crate::IRC_LOG).is_err() {
let _ = std::fs::write(crate::IRC_LOG, "");
}
let mut f = OpenOptions::new()
.append(true)
.open(crate::IRC_LOG)
.unwrap();
if first_line {
let _ = writeln!(f, "-------------writing to file-------------");
}
if let Err(e) = writeln!(f, "{}", log.to_string()) {
eprintln!("[!] Failed to write to file: {}", e);
}
Ok(())
}
fn gen_name() -> String {
let secs = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
return format!("wowboombox{}", secs);
}
pub fn read_irc_log(active_channel: String) -> String {
let mut s: String = String::new();
let f = std::fs::File::open(crate::IRC_LOG); // ("unable to open irc log");
if f.is_err() { return "".to_owned() }
if active_channel.len() > 0 {
let re = Regex::new(format!(r".*PRIVMSG {} :.*", active_channel).as_str()).unwrap();
let reader = BufReader::new(f.unwrap());
for line in reader.lines() {
if line.is_err() { continue }
let msg = line.unwrap();
if re.is_match(msg.as_str()) {
s = s + "\n" + msg.as_str();
}
}
} else {
let _ = f.unwrap().read_to_string(&mut s);
}
return s;
}
pub fn send_tune(&self) -> std::io::Result<()> {
let stream = connect()?;
thread::spawn(move || send_cmd(&stream, "PRIVMSG", "#wownero-music :!tune\r\n".to_owned()));
Ok(())
}
pub fn run(&self) -> Result<TcpStream, std::io::Error> {
let send_stream = connect()?;
let recv_stream = send_stream.try_clone()?;
// https://doc.rust-lang.org/nightly/std/thread/
thread::spawn(move || receive(&recv_stream).expect("error setting up recv_stream"));
Ok(send_stream)
}

@ -1,51 +1,23 @@
use std::time::{Duration, SystemTime};
use std::net::TcpStream;
use eframe::egui;
use egui::FontFamily::Proportional;
use egui::FontId;
use egui::TextStyle::*;
use crate::player::Player;
use crate::tor::GuiTor;
use crate::stats::Market;
use crate::irc::Client as IRCClient;
use libtor::Error as libtorError;
use crate::node::Node;
pub struct App {
pub player: Player,
pub market: Market,
pub irc: IRCClient,
pub tor_required: bool,
pub tor_started: bool,
pub tor_connected: bool,
pub to_data: String,
pub show_irc: bool,
pub irc_message: String,
pub irc_connected: bool,
pub show_market_data: bool,
pub show_radio: bool,
pub irc_stream: Option<TcpStream>,
pub irc_last_ping: SystemTime,
pub irc_active_channel: String
pub node: Node,
pub node_running: bool,
pub show_node_logs: bool,
pub node_log: String
}
impl Default for App {
fn default() -> Self {
Self {
player: Player::default(),
market: Market::new(),
irc: IRCClient::new(),
tor_started: false,
tor_required: true,
tor_connected: false,
show_market_data: false,
show_irc: false,
irc_connected: false,
to_data: "".to_owned(),
irc_message: "".to_owned(),
show_radio: true,
irc_stream: None,
irc_last_ping: SystemTime::now(),
irc_active_channel: "#wownero-music".to_owned()
node: Node::default(),
node_running: false,
show_node_logs: false,
node_log: "".to_owned()
}
}
}
@ -70,221 +42,229 @@ impl eframe::App for App {
ctx.set_visuals(egui::Visuals::dark());
egui::TopBottomPanel::top("top").show(ctx, |ui| {
ui.heading("Wownero Operations Center");
ui.heading("Wownero Node Center");
ui.label("Made by ya boi, lza_menace");
ui.hyperlink("https://lzahq.tech");
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.checkbox(&mut self.tor_required, "Require Tor");
if (self.tor_connected && self.tor_required) || (! self.tor_required) {
ui.checkbox(&mut self.show_market_data, "Show Market Data");
ui.checkbox(&mut self.show_irc, "Show IRC");
}
ui.checkbox(&mut self.show_radio, "Show Radio");
});
// Tor
if self.tor_required {
if self.tor_started {
ui.label(
egui::RichText::new(
format!(
"Tor Started: {} Tor Connected: {} Proxy Up: {}",
show_boolmoji(self.tor_started),
show_boolmoji(self.tor_connected),
show_boolmoji(self.tor_connected)
)
// if self.tor_required {
// if self.node_running {
// ui.label(
// egui::RichText::new(
// format!(
// "Tor Started: {} Tor Connected: {} Proxy Up: {}",
// show_boolmoji(self.node_running),
// show_boolmoji(self.tor_connected),
// show_boolmoji(self.tor_connected)
// )
// )
// );
// } else {
// }
// }
// node
if self.node_running {
ui.label(
egui::RichText::new(
format!(
"Node Started: {}",
show_boolmoji(self.node_running)
)
);
} else {
if ui.button("Connect to the Tor network").clicked() {
let _t: std::thread::JoinHandle<Result<u8, libtorError>> = GuiTor::start_tor();
self.tor_started = true;
self.tor_connected = true;
}
)
);
ui.horizontal(|ui| {
ui.checkbox(&mut self.show_node_logs, "Show Node Logs");
});
if self.show_node_logs {
egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| {
// ui.label(self.irc.read_irc_log(self.irc_active_channel.clone()));
ui.horizontal(|ui| {
ui.text_edit_singleline(&mut self.node_log);
});
// if ui.button("Clear IRC Log").clicked() {
// let _ = std::fs::File::create(crate::IRC_LOG);
// }
// if self.irc_last_ping.elapsed().unwrap() > Duration::from_secs(60) {
// let _ = crate::nodev2::send_cmd(&self.irc_stream.as_ref().unwrap(), "PING", format!(""));
// self.irc_last_ping = SystemTime::now();
// }
});
}
} else {
if ui.button("Start the node").clicked() {
self.node.start();
self.node_running = true;
}
ui.separator();
}
ui.separator();
// WOW!Radio
if self.show_radio {
ui.heading(egui::RichText::new("WOW!Radio").color(egui::Color32::WHITE));
ui.label(egui::RichText::new("Your home for the most diabolical playlist of the century, made by the skeevers, scallywags, chupacabras, snails, and whores of the Wownero community. Join da chat to peep da scoop.\n").color(egui::Color32::WHITE));
ui.horizontal(|ui| {
if self.player.playing {
if ui.button("⏸").clicked() {
let _ = self.player.sink.pause();
self.player.playing = false;
}
// if ui.button("⏹").clicked() {
// let _ = self.player.sink.stop();
// self.player.playing = false;
// let _ = std::fs::remove_file(crate::RADIO_STREAM);
// }
ui.add(egui::Slider::new(&mut self.player.volume, 0.0..=100.0));
self.player.sink.set_volume(&self.player.volume / 100.0);
if self.player.sink.len() != 1 {
let f = std::fs::File::open(crate::RADIO_STREAM);
if let Ok(fo) = f {
let file = std::io::BufReader::new(fo);
let source = rodio::Decoder::new(file);
if source.is_err() {
return ()
}
let _ = self.player.sink.append(source.unwrap());
let _ = self.player.sink.play();
} else {
return ()
}
}
} else {
if ! self.tor_connected && self.tor_required {
ui.label("Connect to the Tor network first.");
} else {
if ui.button("▶").clicked() {
if ! self.tor_connected && self.tor_required {
return ();
}
// If stream thread is done, start again
if self.player.stream_thread.is_finished() {
self.player.stream_thread = self.player.start_radio_stream(self.tor_required);
}
let _ = self.player.sink.play();
self.player.playing = true;
}
}
// if self.show_radio {
// ui.heading(egui::RichText::new("WOW!Radio").color(egui::Color32::WHITE));
// ui.label(egui::RichText::new("Your home for the most diabolical playlist of the century, made by the skeevers, scallywags, chupacabras, snails, and whores of the Wownero community. Join da chat to peep da scoop.\n").color(egui::Color32::WHITE));
// ui.horizontal(|ui| {
// if self.player.playing {
// if ui.button("⏸").clicked() {
// let _ = self.player.sink.pause();
// self.player.playing = false;
// }
// // if ui.button("⏹").clicked() {
// // let _ = self.player.sink.stop();
// // self.player.playing = false;
// // let _ = std::fs::remove_file(crate::RADIO_STREAM);
// // }
// } else {
// if ! self.tor_connected && self.tor_required {
// ui.label("Connect to the Tor network first.");
// } else {
// if ui.button("▶").clicked() {
// if ! self.tor_connected && self.tor_required {
// return ();
// }
// // If stream thread is done, start again
// if self.player.stream_thread.is_finished() {
// self.player.stream_thread = self.player.start_radio_stream(self.tor_required);
// }
// let _ = self.player.sink.play();
// self.player.playing = true;
// }
// }
}
});
// }
// });
// Show spinner when downloading, along with file size
if ! self.player.stream_thread.is_finished() {
ui.horizontal(|ui| {
ui.spinner();
let size: u64 = self.player.get_radio_size();
ui.label(format!(
"{:?} -> {} ({} bytes)",
self.player.stream_source,
crate::RADIO_STREAM,
size
));
});
}
// // Show spinner when downloading, along with file size
// // if ! self.player.stream_thread.is_finished() {
// // ui.horizontal(|ui| {
// // ui.spinner();
// // let size: u64 = self.player.get_radio_size();
// // ui.label(format!(
// // "{:?} -> {} ({} bytes)",
// // self.player.stream_source,
// // crate::RADIO_STREAM,
// // size
// // ));
// // });
// // }
// Show exif metadata when radio file available to read
if self.player.playing && self.player.get_radio_size() > 0 {
let rt = egui::RichText::new(
format!("\n{:?}", self.player.stream_exif))
.color(egui::Color32::WHITE)
.size(18.0);
ui.label(rt);
let dur = self.player.exif_date.elapsed().unwrap();
if dur > Duration::from_secs(15) {
self.player.exif_date = SystemTime::now();
self.player.stream_exif = self.player.get_song_meta().unwrap();
}
if ui.button(" +1 ").clicked() {
let _ = self.irc.send_tune();
}
}
ui.separator();
}
// // Show exif metadata when radio file available to read
// // if self.player.playing && self.player.get_radio_size() > 0 {
// // let rt = egui::RichText::new(
// // format!("\n{:?}", self.player.stream_exif))
// // .color(egui::Color32::WHITE)
// // .size(18.0);
// // ui.label(rt);
// // let dur = self.player.exif_date.elapsed().unwrap();
// // if dur > Duration::from_secs(15) {
// // self.player.exif_date = SystemTime::now();
// // self.player.stream_exif = self.player.get_song_meta().unwrap();
// // }
// // if ui.button(" +1 ").clicked() {
// // let _ = self.irc.send_tune();
// // }
// // }
// ui.separator();
// }
// IRC
if self.show_irc && ! self.irc_connected {
let i = self.irc.run();
if i.is_ok() {
self.irc_stream = Some(i.unwrap());
self.irc_connected = true;
}
// // IRC
// if self.show_node_logs && ! self.irc_connected {
// let i = self.irc.run();
// if i.is_ok() {
// self.irc_stream = Some(i.unwrap());
// self.irc_connected = true;
// }
} else if self.show_irc {
egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| {
ui.label(self.irc.read_irc_log(self.irc_active_channel.clone()));
ui.horizontal(|ui| {
egui::ComboBox::from_label("")
.selected_text(format!("{}", self.irc_active_channel))
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.irc_active_channel, "".to_owned(), "system");
ui.selectable_value(&mut self.irc_active_channel, "#wownero-music".to_owned(), "#wownero-music");
ui.selectable_value(&mut self.irc_active_channel, "#wownero".to_owned(), "#wownero");
}
);
ui.text_edit_singleline(&mut self.irc_message);
if ui.button("> Send <").clicked() {
let res = crate::irc::send_cmd(&self.irc_stream.as_ref().unwrap(), "PRIVMSG", format!("{} :{}\r\n", self.irc_active_channel, self.irc_message));
if res.is_ok() {
println!("wrote {} bytes to IRC: {}", res.unwrap(), self.irc_message);
let _ = crate::irc::write_log(format!("PRIVMSG {} :{}\r\n", self.irc_active_channel, self.irc_message).as_str(), false);
} else {
eprintln!("error: {:?}", res.err());
}
self.irc_message = "".to_owned();
}
});
if ui.button("Clear IRC Log").clicked() {
let _ = std::fs::File::create(crate::IRC_LOG);
}
if self.irc_last_ping.elapsed().unwrap() > Duration::from_secs(60) {
let _ = crate::irc::send_cmd(&self.irc_stream.as_ref().unwrap(), "PING", format!(""));
self.irc_last_ping = SystemTime::now();
}
});
}
// } else if self.show_node_logs {
// egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| {
// ui.label(self.irc.read_irc_log(self.irc_active_channel.clone()));
// ui.horizontal(|ui| {
// egui::ComboBox::from_label("")
// .selected_text(format!("{}", self.irc_active_channel))
// .show_ui(ui, |ui| {
// ui.selectable_value(&mut self.irc_active_channel, "".to_owned(), "system");
// ui.selectable_value(&mut self.irc_active_channel, "#wownero-music".to_owned(), "#wownero-music");
// ui.selectable_value(&mut self.irc_active_channel, "#wownero".to_owned(), "#wownero");
// }
// );
// ui.text_edit_singleline(&mut self.node_log);
// if ui.button("> Send <").clicked() {
// let res = crate::nodev2::send_cmd(&self.irc_stream.as_ref().unwrap(), "PRIVMSG", format!("{} :{}\r\n", self.irc_active_channel, self.node_log));
// if res.is_ok() {
// println!("wrote {} bytes to IRC: {}", res.unwrap(), self.node_log);
// let _ = crate::nodev2::write_log(format!("PRIVMSG {} :{}\r\n", self.irc_active_channel, self.node_log).as_str(), false);
// } else {
// eprintln!("error: {:?}", res.err());
// }
// self.node_log = "".to_owned();
// }
// });
// if ui.button("Clear IRC Log").clicked() {
// let _ = std::fs::File::create(crate::IRC_LOG);
// }
// if self.irc_last_ping.elapsed().unwrap() > Duration::from_secs(60) {
// let _ = crate::nodev2::send_cmd(&self.irc_stream.as_ref().unwrap(), "PING", format!(""));
// self.irc_last_ping = SystemTime::now();
// }
// });
// }
// Market
if self.show_market_data {
egui::ComboBox::from_label("Pick currency base.")
.selected_text(format!("{}", self.market.denomination))
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.market.denomination, "sats".to_owned(), "sats");
ui.selectable_value(&mut self.market.denomination, "usd".to_owned(), "usd");
ui.selectable_value(&mut self.market.denomination, "eth".to_owned(), "eth");
}
);
if self.market.last_check_time.elapsed().unwrap() > Duration::from_secs(120) {
println!("[+] Refreshing WOW market data.");
self.market.store_market_data(self.tor_required);
self.market.last_check_time = SystemTime::now();
}
if self.market.last_check_time.elapsed().unwrap() < Duration::from_secs(30) && self.market.read_json_from_file().len() == 0 {
ui.horizontal(|ui| {
ui.spinner();
ui.label("Fetching market data...");
});
}
self.market.last_cg_data = self.market.read_json_from_file();
let m = &self.market.last_cg_data;
let md = &m["market_data"];
ui.horizontal_wrapped(|ui| {
ui.vertical(|ui| {
ui.label("Current Price");
ui.heading(egui::RichText::new(md["current_price"][&self.market.denomination].to_string()).strong());
ui.label("All-Time High");
ui.heading(egui::RichText::new(md["ath"][&self.market.denomination].to_string()).strong());
ui.label("All-Time Low");
ui.heading(egui::RichText::new(md["atl"][&self.market.denomination].to_string()).strong());
ui.label("Total Volume");
ui.heading(egui::RichText::new(md["total_volume"][&self.market.denomination].to_string()).strong());
ui.label("Market Cap");
ui.heading(egui::RichText::new(md["market_cap"][&self.market.denomination].to_string()).strong());
});
ui.vertical(|ui| {
ui.label("PriceChg% (~24hrs)");
ui.heading(egui::RichText::new(md["price_change_percentage_24h_in_currency"][&self.market.denomination].to_string()).strong());
ui.label("PriceChg% (~7d)");
ui.heading(egui::RichText::new(md["price_change_percentage_7d_in_currency"][&self.market.denomination].to_string()).strong());
ui.label("PriceChg% (~14d)");
ui.heading(egui::RichText::new(md["price_change_percentage_14d_in_currency"][&self.market.denomination].to_string()).strong());
ui.label("PriceChg% (~30d)");
ui.heading(egui::RichText::new(md["price_change_percentage_30d_in_currency"][&self.market.denomination].to_string()).strong());
});
});
}
// // Market
// if self.show_market_data {
// egui::ComboBox::from_label("Pick currency base.")
// .selected_text(format!("{}", self.market.denomination))
// .show_ui(ui, |ui| {
// ui.selectable_value(&mut self.market.denomination, "sats".to_owned(), "sats");
// ui.selectable_value(&mut self.market.denomination, "usd".to_owned(), "usd");
// ui.selectable_value(&mut self.market.denomination, "eth".to_owned(), "eth");
// }
// );
// if self.market.last_check_time.elapsed().unwrap() > Duration::from_secs(120) {
// println!("[+] Refreshing WOW market data.");
// self.market.store_market_data(self.tor_required);
// self.market.last_check_time = SystemTime::now();
// }
// if self.market.last_check_time.elapsed().unwrap() < Duration::from_secs(30) && self.market.read_json_from_file().len() == 0 {
// ui.horizontal(|ui| {
// ui.spinner();
// ui.label("Fetching market data...");
// });
// }
// self.market.last_cg_data = self.market.read_json_from_file();
// let m = &self.market.last_cg_data;
// let md = &m["market_data"];
// ui.horizontal_wrapped(|ui| {
// ui.vertical(|ui| {
// ui.label("Current Price");
// ui.heading(egui::RichText::new(md["current_price"][&self.market.denomination].to_string()).strong());
// ui.label("All-Time High");
// ui.heading(egui::RichText::new(md["ath"][&self.market.denomination].to_string()).strong());
// ui.label("All-Time Low");
// ui.heading(egui::RichText::new(md["atl"][&self.market.denomination].to_string()).strong());
// ui.label("Total Volume");
// ui.heading(egui::RichText::new(md["total_volume"][&self.market.denomination].to_string()).strong());
// ui.label("Market Cap");
// ui.heading(egui::RichText::new(md["market_cap"][&self.market.denomination].to_string()).strong());
// });
// ui.vertical(|ui| {
// ui.label("PriceChg% (~24hrs)");
// ui.heading(egui::RichText::new(md["price_change_percentage_24h_in_currency"][&self.market.denomination].to_string()).strong());
// ui.label("PriceChg% (~7d)");
// ui.heading(egui::RichText::new(md["price_change_percentage_7d_in_currency"][&self.market.denomination].to_string()).strong());
// ui.label("PriceChg% (~14d)");
// ui.heading(egui::RichText::new(md["price_change_percentage_14d_in_currency"][&self.market.denomination].to_string()).strong());
// ui.label("PriceChg% (~30d)");
// ui.heading(egui::RichText::new(md["price_change_percentage_30d_in_currency"][&self.market.denomination].to_string()).strong());
// });
// });
// }
});
}

@ -1,368 +0,0 @@
// Copyright © 2019 Matthew Geary
// [This program is licensed under the "MIT License"]
// Please see the file LICENSE in the source
// distribution of this software for license terms.
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::time::SystemTime;
use std::io::{prelude::*, BufReader};
use std::collections::HashMap;
use std::net::TcpStream;
use std::str;
use std::thread;
use regex::Regex;
///! A simple IRC client written in Rust.
/// Returns a TcpStream connected to the desired server, with the given nickname
///
/// # Arguments
///
/// * `nick` - A string that holds the desired user nickname.
///
/// # Example
///
/// `let stream = connect(nick.to_owned()).unwrap();`
///
fn connect() -> std::io::Result<TcpStream> {
let nick = gen_name();
// https://doc.rust-lang.org/std/net/struct.TcpStream.html
let send_stream = TcpStream::connect("irc.oftc.net:6667")?;
// let send_stream = connector.connect("irc.oftc.net", send_stream).expect("could not connect via tls");
// https://tools.ietf.org/html/rfc1459#section-4.1.1
// https://github.com/hoodie/concatenation_benchmarks-rs
let nick_string = format!("{}\r\n", &nick);
let user_string = format!("{} * * {}\n\r", &nick, &nick);
send_cmd(&send_stream, "USER", user_string)?;
send_cmd(&send_stream, "NICK", nick_string)?;
send_cmd(&send_stream, "JOIN", format!("#wownero\r\n"))?;
send_cmd(&send_stream, "JOIN", format!("#wownero-music\r\n"))?;
Ok(send_stream)
}
/// Writes a command to a given TcpStream
///
/// # Arguments
///
/// * `stream` - A mutable reference to a TcpStream
/// # `server` - A string that holds the desired message
///
/// # Example
///
/// `send_cmd(&send_stream, "QUIT", "\r\n".to_string())?;`
///
pub fn send_cmd(mut stream: &TcpStream, cmd: &str, msg: String) -> Result<usize, std::io::Error> {
let mut cmd = cmd.to_string();
cmd.push_str(" ");
cmd.push_str(&msg);
println!("sending: {}", cmd.trim());
stream.write(cmd.as_bytes())
}
/// Loops to recieve data from a TcpStream
///
/// # Arguments
///
/// * `stream` - A mutable reference to a TcpStream
///
/// # Example
///
/// The following example demonstrates how to set up a threaded TcpStream with one
/// stream reference listening and one receiving.
/// ```
/// let send_stream = connect()?;
/// let recv_stream = send_stream.try_clone()?;
/// thread::spawn(move || receive(&recv_stream).expect("error setting up recv_stream"));
/// ```
///
fn receive(mut stream: &TcpStream) -> std::io::Result<()> {
let mut first_line: bool = true;
loop {
let mut buffer = Vec::new();
let mut temp = [1];
for _ in 0..512 {
stream.read_exact(&mut temp)?;
match temp[0] {
0x1 => continue, // start of heading
0xD => continue, // carriage return
0xA => break, // line feed
_ => buffer.push(temp[0]),
}
}
let res_string = str::from_utf8(&buffer[..]);
match res_string {
Ok(r) => {
if !res_string.unwrap().is_empty() {
// if std::fs::File::open(crate::IRC_LOG).is_err() {
// let _ = std::fs::write(crate::IRC_LOG, "");
// }
// let mut f = OpenOptions::new()
// .append(true)
// .open(crate::IRC_LOG)
// .unwrap();
// if ! first_line {
// let _ = writeln!(f, "-------------writing to file-------------");
// first_line = true;
// }
// if let Err(e) = writeln!(f, "{}", r.to_string()) {
// eprintln!("[!] Failed to write to file: {}", e);
// }
let _ = write_log(r, first_line);
first_line = false;
}
}
Err(error) => eprintln!("error while reading from tcp stream: {}", error),
}
}
}
pub fn write_log(log: &str, first_line: bool) -> Result<(), std::io::Error> {
if std::fs::File::open(crate::IRC_LOG).is_err() {
let _ = std::fs::write(crate::IRC_LOG, "");
}
let mut f = OpenOptions::new()
.append(true)
.open(crate::IRC_LOG)
.unwrap();
if first_line {
let _ = writeln!(f, "-------------writing to file-------------");
}
if let Err(e) = writeln!(f, "{}", log.to_string()) {
eprintln!("[!] Failed to write to file: {}", e);
}
Ok(())
}
/// Generates a friendly IRC nick string
///
/// # Example
///
/// ```
/// let nick = gen_name(); // wowboombox1232348765
/// ```
///
fn gen_name() -> String {
let secs = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
return format!("wowboombox{}", secs);
}
/// A client struct holds nickname, server, and command information as well as
/// implementing functions to connect to a server and issue commands.
pub struct Client {
/// Storing command data in a hashmap will supply the
/// user with accurate feedback
commands: HashMap<String, String>,
}
impl Client {
/// Returns a Client with a hashmap built with command data
///
/// # Example
///
/// `let client = Client::new();`
///
pub fn new() -> Client {
let mut commands = HashMap::new();
commands.insert("/quit".to_string(), "Command: /quit".to_string());
commands.insert(
"/join".to_string(),
"Command: /join Parameters: <channel>".to_string(),
);
commands.insert(
"/part".to_string(),
"Command: /part Parameters: <channel>".to_string(),
);
commands.insert(
"/nick".to_string(),
"Command: /nick Parameters: <nickname>".to_string(),
);
commands.insert(
"/msg".to_string(),
"Command: /msg Parameters: <receiver>".to_string(),
);
commands.insert(
"/topic".to_string(),
"Command: /topic Parameters: <channel> [<topic>]".to_string(),
);
commands.insert(
"/list".to_string(),
"Command: /list Parameters: <channel>".to_string(),
);
commands.insert(
"/names".to_string(),
"Command: /names Parameters: <channel>".to_string(),
);
Client {
commands
}
}
/// Returns an option specifying whether the given
/// command/message is valid. If the message is not valid,
/// the function prints information about the command to
/// the user and returns None.
///
/// # Arguments
///
/// * `params` - The minimum number of params needed for the command
/// * `msg` - The msg to verify
///
/// # Example
///
/// if let None = self.verify(2, &msg) {
/// continue
/// }
///
pub fn verify(&self, params: usize, msg: &[&str]) -> Option<()> {
if msg.len() < params {
let msg = self.commands.get(msg[0].trim()).unwrap();
println!("{}", msg);
return None;
}
Some(())
}
pub fn read_irc_log(&self, active_channel: String) -> String {
let mut s: String = String::new();
let f = std::fs::File::open(crate::IRC_LOG); // ("unable to open irc log");
if f.is_err() { return "".to_owned() }
if active_channel.len() > 0 {
let re = Regex::new(format!(r".*PRIVMSG {} :.*", active_channel).as_str()).unwrap();
let reader = BufReader::new(f.unwrap());
for line in reader.lines() {
if line.is_err() { continue }
let msg = line.unwrap();
if re.is_match(msg.as_str()) {
s = s + "\n" + msg.as_str();
}
}
} else {
let _ = f.unwrap().read_to_string(&mut s);
}
return s;
}
pub fn send_tune(&self) -> std::io::Result<()> {
let stream = connect()?;
thread::spawn(move || send_cmd(&stream, "PRIVMSG", "#wownero-music :!tune\r\n".to_owned()));
Ok(())
}
/// Creates a send and recv TcpStream (with the same socket)
/// spins recv to its own thread
/// main thread takes user input and matches it to commands
/// after commands and processed and messages verified,
/// the send stream is used to send command/message combinations.
pub fn run(&self) -> Result<TcpStream, std::io::Error> {
let send_stream = connect()?;
let recv_stream = send_stream.try_clone()?;
// https://doc.rust-lang.org/nightly/std/thread/
thread::spawn(move || receive(&recv_stream).expect("error setting up recv_stream"));
Ok(send_stream)
// Read the input.
// loop {
// let mut msg = String::new();
// match io::stdin().read_line(&mut msg) {
// Ok(_) => {
// // https://users.rust-lang.org/t/how-to-split-a-string-by-and-then-print-first-or-last-component/23042
// let mut msg: Vec<&str> = msg.trim().split(' ').collect();
// let cmd: &str = msg[0].trim();
// match cmd {
// "help" => {
// self.commands
// .iter()
// .for_each(|(_, val)| println!("{}", val));
// }
// "/quit" => {
// send_cmd(&send_stream, "QUIT", "\r\n".to_string())?;
// println!("Quitting...");
// return Ok(());
// }
// "/join" => {
// if self.verify(2, &msg).is_none() {
// continue;
// }
// let msg = format!("{}\r\n", msg[1].trim());
// send_cmd(&send_stream, "JOIN", msg)?;
// }
// "/part" => {
// if self.verify(2, &msg).is_none() {
// continue;
// }
// let msg = format!("{}\r\n", msg[1].trim());
// send_cmd(&send_stream, "PART", msg)?;
// }
// "/nick" => {
// if self.verify(2, &msg).is_none() {
// continue;
// }
// let msg = format!("{}\r\n", msg[1].trim());
// send_cmd(&send_stream, "NICK", msg)?;
// }
// "/msg" => {
// if self.verify(2, &msg).is_none() {
// continue;
// }
// let receiver = msg[1].trim();
// msg.remove(0);
// msg.remove(0);
// let mut text = String::new();
// msg.iter().for_each(|word| {
// text.push_str(word);
// text.push_str(" ")
// });
// let text = text.trim();
// let msg = format!("{} :{:?}\r\n", receiver, text);
// send_cmd(&send_stream, "PRIVMSG", msg)?;
// }
// "/list" => {
// let target = if msg.len() > 1 { msg[1].trim() } else { "" };
// let msg = format!("{}\r\n", target);
// send_cmd(&send_stream, "LIST", msg)?;
// }
// "/names" => {
// let target = if msg.len() > 1 { msg[1].trim() } else { "" };
// let msg = format!("{}\r\n", target);
// send_cmd(&send_stream, "NAMES", msg)?;
// }
// "/topic" => {
// if self.verify(3, &msg).is_none() {
// continue;
// }
// let msg = format!("{} {}\r\n", msg[1].trim(), msg[2].trim());
// send_cmd(&send_stream, "NAMES", msg)?;
// }
// _ => {
// println!("Unrecognized command: {}", msg[0]);
// }
// }
// }
// Err(error) => eprintln!("error while reading user input: {}", error),
// }
// }
}
}
// Parse command line arguments and use them create and run a client
// fn main() {
// // Process the arguments.
// let args: Vec<String> = std::env::args().collect();
// let mut nick: String;
// let mut server: String;
// match args.len() {
// 3 => server = args[2].to_owned(),
// 2 => server = "irc.freenode.net".to_string(),
// _ => usage(),
// }
// nick = args[1].to_owned();
// let client = Client::new(nick, server);
// client.run().expect("Client Error");
// }

@ -1,26 +1,16 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
// hide console window on Windows in release
pub use crate::player::Player;
pub use crate::node::Node;
pub use crate::app::App;
mod app;
mod player;
mod tor;
mod stats;
mod irc;
// "http://wowradiod6mhb4ignjqy5ghf5l42yh2yeumgeq3yi7gn7yqy3efhe5ad.onion"
// "http://wowradiof5wx62w4avdk5fwbvcoea2z4ul2q3awffn5vmfaa3vhlgiid.onion"
// "http://radio.poysibicsj73uhw7sjrv3fyopoyulrns4nlr5amyqdtafkqzlocd4qad.onion"
// "http://anonyradixhkgh5myfrkarggfnmdzzhhcgoy2v66uf7sml27to5n2tid.onion"
// "http://u2frppcgwqmoca7h3hu5l72upkwhcf2n6umrkqsnvnpahjynkre6sqyd.onion:8300/radio"
// "https://radio.wownero.com/wow.ogg"
mod node;
pub const TOR_DATA: &str = &"/tmp/tor-rust";
pub const TOR_LOG: &str = &"/tmp/tor-rust/tor.log";
pub const RADIO_STREAM: &str = &"radio.ogg";
pub const IRC_LOG: &str = &"irc.log";
pub const NODE_LOG: &str = &"node.log";
fn cleanup() {
let r: std::io::Result<()> = std::fs::remove_file(&RADIO_STREAM);
@ -30,14 +20,10 @@ fn cleanup() {
}
fn main() {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let sink: rodio::Sink = rodio::Sink::try_new(&stream_handle).unwrap();
let player: player::Player = player::Player::new(sink, stream_handle);
let mut app: app::App = app::App::default();
app.player = player;
let app: app::App = app::App::default();
let options: eframe::NativeOptions = eframe::NativeOptions::default();
eframe::run_native(
"lza gui",
"node gui",
options,
Box::new(|_cc| Box::new(app)),
);

@ -0,0 +1,122 @@
use std::{io::{Read, BufReader}, process::{Command, Child}};
pub struct Node {
pub thread: std::thread::JoinHandle<()>,
pub running: bool
}
impl Node {
pub fn new() -> Self {
Self {
thread: std::thread::spawn(|| {}),
running: false
}
}
pub fn default() -> Self {
Self::new()
}
pub fn start(&self) -> Child {
let cmd = Command::new("wownerod")
// .arg("--detach")
// .arg("--non-interactive")
.arg("--log-file")
.arg(crate::NODE_LOG)
.spawn()
.expect("failed");
return cmd;
}
// --detach
// --pidfile
// --non-interactive
// --log-file
// --log-level
// --max-log-file-size
pub fn read_log() -> String {
let mut s: String = String::new();
let f = std::fs::File::open(crate::NODE_LOG); // ("unable to open irc log");
if f.is_err() { return "".to_owned() }
// let re = Regex::new(format!(r".*PRIVMSG {} :.*", active_channel).as_str()).unwrap();
let reader = BufReader::new(f.unwrap());
for line in reader.lines() {
if line.is_err() { continue }
let msg = line.unwrap();
// if re.is_match(msg.as_str()) {
// s = s + "\n" + msg.as_str();
// }
s = s + "\n" + msg.as_str()
}
return s;
}
// pub fn start_radio_stream(&self, tor_required: bool) -> std::thread::JoinHandle<()> {
// let url = self.stream_source.clone();
// let t: Result<std::thread::JoinHandle<()>, std::io::Error> = std::thread::Builder::new().name("radio_stream".to_string()).spawn(move || {
// let mut client_builder = reqwest::blocking::Client::builder()
// .user_agent("WOC GUI + BoomBox");
// if tor_required {
// let proxy = reqwest::Proxy::all("socks5://127.0.0.1:19050").unwrap();
// client_builder = client_builder.proxy(proxy);
// }
// let mut res = client_builder.build()
// .unwrap()
// .get(url)
// .send()
// .unwrap();
// let mut out: std::fs::File = std::fs::File::create(&crate::RADIO_STREAM).expect("failed to create file");
// std::io::copy(&mut res, &mut out).expect("failed to copy content");
// });
// return t.unwrap()
// }
// pub fn wait_for_source(&self) {
// loop {
// let r: Result<std::fs::File, std::io::Error> = std::fs::File::open(&crate::RADIO_STREAM);
// if r.is_ok() {
// std::thread::sleep(std::time::Duration::from_secs(1));
// return ()
// }
// std::thread::sleep(std::time::Duration::from_secs(2));
// }
// }
// pub fn get_song_meta(&self) -> Option<String> {
// let file = std::fs::File::open(&crate::RADIO_STREAM);
// if file.is_ok() {
// let mut buffer = vec![];
// let file_size = file.unwrap().metadata().unwrap().len();
// let chunk_size = 100000;
// let mut start_pos = if file_size < chunk_size { 0 } else { file_size - chunk_size };
// let tries = file_size / chunk_size;
// for _i in 0..tries + 1 {
// // println!("Scanning radio stream: {} ({} bytes)\nTry: {}", &crate::RADIO_STREAM, file_size, _i);
// let mut f = std::fs::File::open(&crate::RADIO_STREAM).unwrap();
// f.seek(std::io::SeekFrom::Start(start_pos)).unwrap();
// f.take(chunk_size).read_to_end(&mut buffer).unwrap();
// let s = std::string::String::from_utf8_lossy(&buffer);
// let re = regex::Regex::new(r"title=(.*).{4}server=").unwrap();
// let caps = re.captures(&s);
// if caps.is_some() {
// // eat whatever the fuck you want - food moderation and other philosophies
// // im 63, i walk everyday, everyone can do that, if they want
// // if you're gonna drink, you gotta balance it out
// return Some(caps.unwrap().get(1).map_or("", |m| m.as_str()).to_owned());
// }
// if start_pos < chunk_size {
// start_pos = 0;
// } else {
// start_pos -= chunk_size;
// }
// }
// }
// return Some("".to_owned());
// }
}

@ -1,128 +0,0 @@
use std::{io::{Read, Seek}, time::SystemTime};
pub struct Player {
pub sink: rodio::Sink,
pub stream_thread: std::thread::JoinHandle<()>,
pub stream_handle: rodio::OutputStreamHandle,
pub stream_source: String,
pub stream_exif: String,
pub exif_date: SystemTime,
pub decode_err_date: SystemTime,
pub volume: f32,
pub playing: bool
}
impl Player {
pub fn new(sink: rodio::Sink, stream_handle: rodio::OutputStreamHandle) -> Self {
Self {
sink,
stream_handle,
stream_thread: std::thread::spawn(|| {}),
stream_source: "https://radio.wownero.com/wow.ogg".to_owned(),
stream_exif: "".to_owned(),
exif_date: SystemTime::now(),
decode_err_date: SystemTime::now(),
volume: 100.0,
playing: false
}
}
pub fn default() -> Self {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&stream_handle).unwrap();
Self::new(sink, stream_handle)
}
pub fn check_proxy(&self) -> bool {
let stream = std::net::TcpStream::connect("127.0.0.1:19050");
return stream.is_ok()
}
pub fn get_radio_size(&self) -> u64 {
let file = std::fs::File::open(crate::RADIO_STREAM);
if file.is_ok() {
return file.unwrap().metadata().unwrap().len();
} else {
return 0;
}
}
pub fn start_radio_stream(&self, tor_required: bool) -> std::thread::JoinHandle<()> {
let url = self.stream_source.clone();
let t: Result<std::thread::JoinHandle<()>, std::io::Error> = std::thread::Builder::new().name("radio_stream".to_string()).spawn(move || {
let mut client_builder = reqwest::blocking::Client::builder()
.user_agent("WOC GUI + BoomBox");
if tor_required {
let proxy = reqwest::Proxy::all("socks5://127.0.0.1:19050").unwrap();
client_builder = client_builder.proxy(proxy);
}
let mut res = client_builder.build()
.unwrap()
.get(url)
.send()
.unwrap();
let mut out: std::fs::File = std::fs::File::create(&crate::RADIO_STREAM).expect("failed to create file");
std::io::copy(&mut res, &mut out).expect("failed to copy content");
});
return t.unwrap()
}
pub fn wait_for_source(&self) {
loop {
let r: Result<std::fs::File, std::io::Error> = std::fs::File::open(&crate::RADIO_STREAM);
if r.is_ok() {
std::thread::sleep(std::time::Duration::from_secs(1));
return ()
}
std::thread::sleep(std::time::Duration::from_secs(2));
}
}
pub fn get_song_meta(&self) -> Option<String> {
let file = std::fs::File::open(&crate::RADIO_STREAM);
if file.is_ok() {
let mut buffer = vec![];
let file_size = file.unwrap().metadata().unwrap().len();
let chunk_size = 100000;
let mut start_pos = if file_size < chunk_size { 0 } else { file_size - chunk_size };
let tries = file_size / chunk_size;
for _i in 0..tries + 1 {
// println!("Scanning radio stream: {} ({} bytes)\nTry: {}", &crate::RADIO_STREAM, file_size, _i);
let mut f = std::fs::File::open(&crate::RADIO_STREAM).unwrap();
f.seek(std::io::SeekFrom::Start(start_pos)).unwrap();
f.take(chunk_size).read_to_end(&mut buffer).unwrap();
let s = std::string::String::from_utf8_lossy(&buffer);
let re = regex::Regex::new(r"title=(.*).{4}server=").unwrap();
let caps = re.captures(&s);
if caps.is_some() {
// eat whatever the fuck you want - food moderation and other philosophies
// im 63, i walk everyday, everyone can do that, if they want
// if you're gonna drink, you gotta balance it out
return Some(caps.unwrap().get(1).map_or("", |m| m.as_str()).to_owned());
}
if start_pos < chunk_size {
start_pos = 0;
} else {
start_pos -= chunk_size;
}
}
}
return Some("".to_owned());
}
pub fn get_radio_source(&self) -> Result<rodio::Decoder<std::io::BufReader<std::fs::File>>, ()> {
// Self::wait_for_source();
let f = std::fs::File::open(&crate::RADIO_STREAM);
if let Ok(fo) = f {
let file = std::io::BufReader::new(fo);
let source = rodio::Decoder::new(file).unwrap();
return Ok(source)
}
Err(())
}
}