├── src ├── bin │ ├── util │ │ ├── mod.rs │ │ └── event.rs │ ├── ui │ │ ├── mod.rs │ │ ├── opts.rs │ │ └── ui.rs │ ├── suck_it_node.rs │ ├── look_at_me_daddy.rs │ └── test_me_daddy.rs ├── miscom │ ├── mod.rs │ └── thishere.rs ├── bangers │ ├── mod.rs │ ├── consts.rs │ ├── boolizer.rs │ └── bangers.rs ├── twitch │ ├── mod.rs │ ├── prime_listener.rs │ ├── twitch_chat_listener.rs │ ├── twitch_client.rs │ └── runners.rs ├── lib.rs ├── opt.rs ├── encoding │ ├── mod.rs │ ├── base_encoding.rs │ └── squeeze_me_daddy.rs ├── server.rs ├── event_bus.rs ├── main.rs ├── event.rs └── client.rs ├── .gitignore ├── type-me-daddy ├── .twitch.irc.example.toml ├── .env_example ├── README.md ├── Cargo.toml └── Cargo.lock /src/bin/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod event; 2 | 3 | -------------------------------------------------------------------------------- /src/miscom/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod thishere; 2 | 3 | -------------------------------------------------------------------------------- /src/bin/ui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ui; 2 | pub mod opts; 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .twitch.irc.toml 3 | /.env 4 | TODO.md 5 | beats 6 | -------------------------------------------------------------------------------- /src/bin/suck_it_node.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | loop { 3 | } 4 | } 5 | 6 | -------------------------------------------------------------------------------- /type-me-daddy: -------------------------------------------------------------------------------- 1 | sleep 5 2 | xdotool type --delay 95 " I have seen many " 3 | 4 | -------------------------------------------------------------------------------- /src/bangers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod boolizer; 2 | pub mod bangers; 3 | pub mod consts; 4 | 5 | -------------------------------------------------------------------------------- /src/miscom/thishere.rs: -------------------------------------------------------------------------------- 1 | pub fn right_now() { 2 | println!("right here"); 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/twitch/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod prime_listener; 2 | pub mod twitch_chat_listener; 3 | pub mod twitch_client; 4 | mod runners; 5 | 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(slice_group_by)] 2 | 3 | pub mod bangers; 4 | pub mod twitch; 5 | pub mod event; 6 | pub mod event_bus; 7 | pub mod encoding; 8 | -------------------------------------------------------------------------------- /.twitch.irc.example.toml: -------------------------------------------------------------------------------- 1 | nickname = "your-name-lowercase" 2 | password = "oauth:here" 3 | channel = ["#theprimeagen"] 4 | server = "irc://irc.chat.twitch.tv:6667" 5 | 6 | -------------------------------------------------------------------------------- /.env_example: -------------------------------------------------------------------------------- 1 | OAUTH_NAME="you-name-lowercase" 2 | OAUTH_TOKEN="generate yours here: https://twitchapps.com/tmi/ AND REMOVE THE oauth: from the token 3 | generated. Yes, this stupid api doesn't even check for that..." 4 | 5 | -------------------------------------------------------------------------------- /src/bin/ui/opts.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use structopt::StructOpt; 4 | 5 | #[derive(Debug, StructOpt, Clone)] 6 | pub struct UiConfig { 7 | /// Activate debug mode 8 | #[structopt(short = "b", long = "beats", default_value = "./beat.medaddy")] 9 | pub beat: PathBuf 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/bangers/consts.rs: -------------------------------------------------------------------------------- 1 | pub const BIT_LENGTH: usize = 10; 2 | pub const BEAT_COUNT: usize = 128; 3 | 4 | pub const BANG_COMMAND_STARTING_CHAR: char = '♥'; 5 | pub const STARTING_UTF: char = '♥'; 6 | pub const STARTING_UTF_OFFSET: u32 = STARTING_UTF as u32; 7 | 8 | pub const SEPARATOR: &str = "░"; 9 | pub const UNSELECTED: &str = "▒"; 10 | pub const SELECTED: &str = "█"; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## USE NIGHTLY 2 | Once you have rust installed, install nightly 3 | `rustup toolchain install nightly` 4 | 5 | ### look_at_me_daddy 6 | To run the TUI client there are two steps 7 | 8 | 1. copy the .env-example to .env and fill in the proper values. 9 | 2. run `cargo run --release --bin look_at_me_daddy` 10 | You can also run it with `cargo +nightly run --release --bin look_at_me_daddy` for a quick shortcut using nightly. SMILEY FACE 11 | 12 | #### How to use look_at_me_daddy 13 | * use hjkl to move one note at a time 14 | * use w / b to move a quarter note 15 | * use W / B to move a whole note 16 | * space to toggle if the note should be played 17 | * enter to send to twitch 18 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | #[derive(Debug, StructOpt, Clone)] 4 | #[structopt(name = "example", about = "An example of StructOpt usage.")] 5 | pub struct PiOpts { 6 | /// Activate debug mode 7 | #[structopt(short = "d", long = "debug")] 8 | pub debug: bool, 9 | 10 | /// If the program runs in server mode 11 | #[structopt(short = "s", long = "server")] 12 | pub server: bool, 13 | 14 | /// 15 | #[structopt(short = "p", long = "port", default_value = "6969")] 16 | pub port: u16, 17 | 18 | #[structopt(short = "a", long = "addr")] 19 | pub addr: String, 20 | 21 | #[structopt(short = "c", long = "command", default_value = "sonic_pi")] 22 | pub command: String, 23 | 24 | 25 | #[structopt(short = "b", long = "bpm", default_value = "120")] 26 | pub bpm: u16, 27 | } 28 | -------------------------------------------------------------------------------- /src/bin/look_at_me_daddy.rs: -------------------------------------------------------------------------------- 1 | mod util; 2 | mod ui; 3 | 4 | use termion::event::Key; 5 | use ui::opts::UiConfig; 6 | use util::event::{Events, Event}; 7 | use beatmedaddy::twitch::twitch_client::Twitch; 8 | use structopt::StructOpt; 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), Box> { 12 | dotenv::dotenv()?; 13 | let opts = UiConfig::from_args(); 14 | 15 | let events = Events::new(); 16 | let twitch = Twitch::new(); 17 | let mut ui = ui::ui::UI::new(Some(twitch), opts)?; 18 | 19 | loop { 20 | match events.next() { 21 | Ok(Event::Input(Key::Ctrl('c'))) => break, 22 | Ok(Event::Input(c)) => ui.key(c).await, 23 | Ok(Event::Tick) => ui.tick(), 24 | _ => continue 25 | }?; 26 | } 27 | 28 | return Ok(()); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "beatmedaddy" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | async-trait = "0.1.52" 11 | base64 = "0.13.0" 12 | dotenv = "0.15.0" 13 | env_logger = "0.9.0" 14 | futures = "0.3.19" 15 | futures-channel = "0.3.19" 16 | futures-util = "0.3.19" 17 | hex = "0.4.3" 18 | hyper = { version = "0.14.16", features = ["full"] } 19 | itertools = "0.10.3" 20 | log = "0.4.14" 21 | reqwest = { version = "0.11.8", features = ["json"] } 22 | serde = { version = "1.0.132", features = ["derive"] } 23 | serde_json = "1.0.73" 24 | structopt = "0.3.25" 25 | termion = "1.5.6" 26 | tokio = { version = "1.15.0", features = ["full"] } 27 | tui = "0.16.0" 28 | tungstenite = { version = "0.16.0", features = ["native-tls"] } 29 | twitch-irc = "3.0.1" 30 | url = "2.2.2" 31 | -------------------------------------------------------------------------------- /src/encoding/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod base_encoding; 2 | pub mod squeeze_me_daddy; 3 | 4 | pub fn encode(data: &[u8]) -> Result> { 5 | let str = base_encoding::encode(data)?; 6 | return Ok(squeeze_me_daddy::squeeze_me_daddy(&str)); 7 | } 8 | 9 | pub fn decode(data: &String) -> Result, Box> { 10 | // todo: i hate these errors 11 | let data = squeeze_me_daddy::spread_me_daddy(&data)?; 12 | println!("decoded: {:?}", data); 13 | return Ok(base_encoding::decode(&data)?); 14 | } 15 | 16 | #[cfg(test)] 17 | mod test { 18 | use super::*; 19 | 20 | #[test] 21 | fn run_encoding() -> Result<(), Box> { 22 | let encoded = "_6Az0B08CBBDAAECCFDDFAvEEEEFFDAtEEEEFFDAr0"; 23 | print!("{}", encoded); 24 | println!("decoded: {:?}", decode(&encoded.to_string())?); 25 | 26 | return Ok(()); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/twitch/prime_listener.rs: -------------------------------------------------------------------------------- 1 | use futures_channel::mpsc::UnboundedSender; 2 | use super::runners::{Runner, Debug, PlayTheThing, TurnMeDaddy}; 3 | 4 | use crate::event_bus::Listener; 5 | use crate::event::Event; 6 | 7 | pub struct PrimeListener { 8 | runners: Vec>, 9 | } 10 | 11 | impl Listener for PrimeListener { 12 | fn notify(&mut self, event: &Event) { 13 | for runner in &mut self.runners { 14 | if runner.matches(event) { 15 | break; 16 | } 17 | } 18 | } 19 | } 20 | 21 | impl PrimeListener { 22 | pub fn new(tx: UnboundedSender) -> PrimeListener { 23 | let runners: Vec> = vec![ 24 | Box::new(PlayTheThing { tx: tx.clone() }), 25 | Box::new(TurnMeDaddy { tx: tx.clone() }), 26 | Box::new(Debug {}), 27 | ]; 28 | 29 | return PrimeListener { 30 | runners 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/twitch/twitch_chat_listener.rs: -------------------------------------------------------------------------------- 1 | use futures_channel::mpsc::UnboundedSender; 2 | 3 | use crate::{event::Event, event_bus::Listener}; 4 | 5 | pub struct TwitchChatListener { 6 | tx: UnboundedSender, 7 | } 8 | 9 | /* 10 | */ 11 | 12 | pub fn allow(_nick: &String) -> bool { 13 | /* 14 | return [ 15 | "oldmanjudo", 16 | "ThePrimeagen", 17 | "theprimeagen", 18 | ].contains(&nick.as_str()); 19 | */ 20 | return true; 21 | } 22 | 23 | impl Listener for TwitchChatListener { 24 | fn notify(&mut self, event: &Event) { 25 | println!("TwitchChatListener#notify: {:?}", event); 26 | if let Event::TwitchChat(e) = event { 27 | println!("is twitch_chat"); 28 | if allow(&e.name) && crate::bangers::boolizer::is_bang_command(&e.text) { 29 | self.tx.unbounded_send(Event::DrumCommand(e.text.clone())).expect("Successful successing of drum successions"); 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl TwitchChatListener { 36 | pub fn new(tx: UnboundedSender) -> TwitchChatListener { 37 | return TwitchChatListener { 38 | tx 39 | }; 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/twitch/twitch_client.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | use twitch_irc::login::{StaticLoginCredentials}; 6 | use twitch_irc::TwitchIRCClient; 7 | 8 | use twitch_irc::{ClientConfig, SecureTCPTransport}; 9 | 10 | pub struct Twitch { 11 | client: TwitchIRCClient 12 | } 13 | 14 | impl Twitch { 15 | pub async fn send_message(&mut self, s: String) -> Result<(), Box> { 16 | 17 | self.client.privmsg("theprimeagen".to_string(), s).await?; 18 | 19 | return Ok(()); 20 | } 21 | 22 | pub fn new() -> Twitch { 23 | 24 | let login_name = std::env::var("OAUTH_NAME").expect("OAUTH_NAME is required for twitch client"); 25 | let oauth_token = std::env::var("OAUTH_TOKEN").expect("OAUTH_TOKEN is required for twitch client"); 26 | 27 | let config: ClientConfig = ClientConfig::new_simple(StaticLoginCredentials::new( 28 | login_name, 29 | Some(oauth_token), 30 | )); 31 | 32 | let (_, client) = 33 | TwitchIRCClient::::new(config); 34 | 35 | // join a channel 36 | client.join("theprimeagen".to_owned()); 37 | 38 | return Twitch { 39 | client, 40 | }; 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpListener; 2 | use std::thread::spawn; 3 | use tungstenite::accept; 4 | use std::process::Command; 5 | use std::sync::Arc; 6 | 7 | use crate::opt::PiOpts; 8 | 9 | pub fn server(opts: Arc) -> Result<(), Box> { 10 | let server = TcpListener::bind(format!("{}:{}", opts.addr, opts.port))?; 11 | if opts.debug { 12 | println!("Server listening on {}", opts.port); 13 | } 14 | 15 | for stream in server.incoming() { 16 | let opts = opts.clone(); 17 | spawn(move || { 18 | let mut websocket = accept(stream.unwrap()).unwrap(); 19 | println!("Connection accepted"); 20 | loop { 21 | let msg = match websocket.read_message() { 22 | Ok(m) => m, 23 | Err(_) => break 24 | }; 25 | 26 | // We do not want to send back ping/pong messages. 27 | if msg.is_text() { 28 | println!("music: {}", msg); 29 | 30 | Command::new(&opts.command) 31 | .arg(msg.to_text().unwrap()) 32 | .output() 33 | .expect("failed to execute process"); 34 | } 35 | } 36 | }); 37 | } 38 | 39 | return Ok(()); 40 | } 41 | -------------------------------------------------------------------------------- /src/event_bus.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | use futures_channel::mpsc::UnboundedReceiver; 3 | use futures_util::StreamExt; 4 | use tokio::{task::JoinHandle}; 5 | 6 | use crate::event::Event; 7 | 8 | pub trait Listener { 9 | fn notify(&mut self, event: &Event); 10 | } 11 | 12 | pub trait Dispatchable { 13 | fn register_listener(&mut self, listener: Arc>); 14 | } 15 | 16 | pub struct Dispatcher { 17 | /// A list of synchronous weak refs to listeners 18 | listeners: Vec>>, 19 | } 20 | 21 | impl Dispatchable for Dispatcher { 22 | /// Registers a new listener 23 | fn register_listener(&mut self, listener: Arc>) { 24 | self.listeners.push(listener); 25 | } 26 | } 27 | 28 | impl Dispatcher { 29 | pub fn new() -> Dispatcher { 30 | return Dispatcher { 31 | listeners: Vec::new(), 32 | }; 33 | } 34 | } 35 | 36 | pub fn run_dispatcher(mut rx: UnboundedReceiver, mut dispatcher: Dispatcher) -> JoinHandle<()> { 37 | let output_handle = tokio::spawn(async move { 38 | loop { 39 | println!("wating on run_dispatech"); 40 | if let Some(message) = rx.next().await { 41 | println!("Received message! {:?}", message); 42 | for l in dispatcher.listeners.iter_mut() { 43 | l.lock().expect("listener lock shouldn't ever fail").notify(&message.clone()); 44 | } 45 | } 46 | } 47 | }); 48 | 49 | return output_handle; 50 | } 51 | -------------------------------------------------------------------------------- /src/bangers/boolizer.rs: -------------------------------------------------------------------------------- 1 | use crate::encoding::encode; 2 | 3 | use super::consts::STARTING_UTF; 4 | 5 | type PrimeResult = Result>; 6 | 7 | pub fn is_bang_command(str: &String) -> bool { 8 | println!("Is Bang Command: {}", str.starts_with(STARTING_UTF)); 9 | return str.starts_with(STARTING_UTF); 10 | } 11 | 12 | pub struct Boolizer { 13 | pub data: Vec, 14 | offset: usize, 15 | tmp: u8, 16 | } 17 | 18 | impl Boolizer { 19 | pub fn new() -> Boolizer { 20 | return Boolizer { 21 | data: Vec::new(), 22 | offset: 0, 23 | tmp: 0, 24 | }; 25 | } 26 | 27 | pub fn push(&mut self, b: bool) -> Result<(), std::io::Error> { 28 | if b { 29 | let position = 8 - self.offset - 1; 30 | self.tmp |= 0x1 << position; 31 | } 32 | 33 | self.offset += 1; 34 | if self.offset == 8 { 35 | self.finish()?; 36 | } 37 | 38 | return Ok(()); 39 | } 40 | 41 | pub fn finish(&mut self) -> Result<(), std::io::Error> { 42 | if self.offset != 0 { 43 | self.data.push(self.tmp); 44 | 45 | self.offset = 0; 46 | self.tmp = 0; 47 | } 48 | 49 | return Ok(()); 50 | } 51 | 52 | pub fn encode(&self) -> PrimeResult { 53 | return encode(&self.data); 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod test { 59 | use super::*; 60 | #[test] 61 | fn test_boolizer() -> Result<(), Box> { 62 | let mut bools: Vec = vec![false; 23 * 4]; 63 | 64 | // 23 a 65 | for i in 0..23 { 66 | bools[i * 4] = true; 67 | bools[i * 4 + 2] = true; 68 | } 69 | 70 | let mut boolizer = Boolizer::new(); 71 | for b in bools { 72 | boolizer.push(b)?; 73 | } 74 | boolizer.finish()?; 75 | 76 | let expected = "zaa0"; 77 | 78 | assert_eq!(expected, boolizer.encode()?); 79 | 80 | return Ok(()); 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(slice_group_by)] 2 | 3 | use beatmedaddy::event_bus::{Dispatchable, Dispatcher, run_dispatcher}; 4 | use futures_channel::mpsc::unbounded; 5 | 6 | use structopt::StructOpt; 7 | use std::sync::{Arc, Mutex}; 8 | 9 | mod opt; 10 | mod server; 11 | mod client; 12 | 13 | use opt::PiOpts; 14 | use server::server; 15 | use client::Client; 16 | 17 | use beatmedaddy::event::{Event, Eventer}; 18 | use beatmedaddy::twitch::{ 19 | prime_listener::PrimeListener, 20 | twitch_chat_listener::TwitchChatListener, 21 | }; 22 | 23 | pub const STARTING_UTF: char = '♥'; 24 | 25 | #[tokio::main] 26 | async fn main() -> Result<(), Box> { 27 | dotenv::dotenv()?; 28 | env_logger::init(); 29 | 30 | let opt = Arc::new(PiOpts::from_args()); 31 | print!("drummers: "); 32 | 33 | for i in 22..1024 { 34 | print!("{}", char::from_u32(STARTING_UTF as u32 + i).unwrap()); 35 | } 36 | println!(); 37 | 38 | if opt.debug { 39 | println!("{:?}", opt); 40 | } 41 | 42 | if opt.server { 43 | server(opt).expect("The server should never fail"); 44 | } else { 45 | let (tx, rx) = unbounded(); 46 | 47 | let mut events = Dispatcher::new(); 48 | let prime_events = Arc::new(Mutex::new(PrimeListener::new(tx.clone()))); 49 | let twitch_chat_listener = Arc::new(Mutex::new(TwitchChatListener::new(tx.clone()))); 50 | let mut client = Client::new(opt.clone()); 51 | let eventer = Eventer::new(tx.clone()); 52 | 53 | client.connect(opt)?; 54 | 55 | events.register_listener(prime_events); 56 | events.register_listener(twitch_chat_listener); 57 | 58 | let client = Arc::new(Mutex::new(client)); 59 | events.register_listener(client); 60 | 61 | tx.unbounded_send(Event::StartOfProgram)?; 62 | 63 | println!("Running dispatcher"); 64 | run_dispatcher(rx, events).await?; 65 | match eventer.join_handle.join() { 66 | Err(e) => panic!("eventer didn't join {:?}", e), 67 | _ => {} 68 | } 69 | println!("Game Over Asshole"); 70 | } 71 | 72 | 73 | return Ok(()); 74 | } 75 | -------------------------------------------------------------------------------- /src/bin/util/event.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::sync::mpsc; 3 | use std::thread; 4 | use std::time::Duration; 5 | 6 | use termion::event::Key; 7 | use termion::input::TermRead; 8 | 9 | pub enum Event { 10 | Input(I), 11 | Tick, 12 | } 13 | 14 | /// A small event handler that wrap termion input and tick events. Each event 15 | /// type is handled in its own thread and returned to a common `Receiver` 16 | pub struct Events { 17 | rx: mpsc::Receiver>, 18 | input_handle: thread::JoinHandle<()>, 19 | tick_handle: thread::JoinHandle<()>, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy)] 23 | pub struct Config { 24 | pub tick_rate: Duration, 25 | pub input_rate: Duration, 26 | } 27 | 28 | impl Default for Config { 29 | fn default() -> Config { 30 | Config { 31 | tick_rate: Duration::from_millis(15), 32 | input_rate: Duration::from_millis(10), 33 | } 34 | } 35 | } 36 | 37 | impl Events { 38 | pub fn new() -> Events { 39 | Events::with_config(Config::default()) 40 | } 41 | 42 | pub fn with_config(config: Config) -> Events { 43 | let (tx, rx) = mpsc::channel(); 44 | 45 | let input_handle = { 46 | let tx = tx.clone(); 47 | thread::spawn(move || { 48 | let stdin = io::stdin(); 49 | for evt in stdin.keys() { 50 | if let Ok(key) = evt { 51 | if let Err(err) = tx.send(Event::Input(key)) { 52 | eprintln!("{}", err); 53 | return; 54 | } 55 | } 56 | thread::sleep(config.tick_rate); 57 | } 58 | }) 59 | }; 60 | 61 | let tick_handle = { 62 | thread::spawn(move || loop { 63 | if let Err(err) = tx.send(Event::Tick) { 64 | eprintln!("{}", err); 65 | break; 66 | } 67 | thread::sleep(config.tick_rate); 68 | }) 69 | }; 70 | 71 | Events { 72 | rx, 73 | input_handle, 74 | tick_handle, 75 | } 76 | } 77 | 78 | pub fn next(&self) -> Result, mpsc::RecvError> { 79 | self.rx.recv() 80 | } 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/twitch/runners.rs: -------------------------------------------------------------------------------- 1 | use futures_channel::mpsc::UnboundedSender; 2 | use crate::event::{Event, TwitchChat}; 3 | 4 | pub trait Runner { 5 | fn matches(&mut self, event: &Event) -> bool; 6 | } 7 | 8 | fn is_prime(event: &TwitchChat) -> bool { 9 | return event.name.eq_ignore_ascii_case("theprimeagen"); 10 | } 11 | 12 | pub struct TurnMeDaddy { 13 | pub tx: UnboundedSender, 14 | } 15 | 16 | pub struct PlayTheThing { 17 | pub tx: UnboundedSender, 18 | } 19 | 20 | pub struct Debug { } 21 | impl Runner for Debug { 22 | fn matches(&mut self, event: &Event) -> bool { 23 | if let Event::TwitchChat(e) = event { 24 | if e.text.starts_with("!debug") { 25 | let test = '♥'; 26 | /* 27 | print!("DEBUG: "); 28 | e.message_text.chars().filter(|c| (*c as u32 >= test as u32)).for_each(|c| { 29 | print!("{} ", (c as u32 - test as u32)); 30 | }); 31 | */ 32 | for t in 1..1024 { 33 | if t % 500 == 0 { 34 | println!(); 35 | println!("----"); 36 | } 37 | print!("{} ", char::from_u32(test as u32 + t).unwrap()); 38 | } 39 | println!(); 40 | return true; 41 | } 42 | } 43 | return true; 44 | } 45 | } 46 | 47 | impl Runner for PlayTheThing { 48 | fn matches(&mut self, event: &Event) -> bool { 49 | if let Event::TwitchChat(e) = event { 50 | if is_prime(e) && e.text.starts_with("!play") { 51 | 52 | if e.text == "!play stop" { 53 | self.tx.unbounded_send(Event::Stop).expect("Successful tx send"); 54 | } else { 55 | self.tx.unbounded_send(Event::Play(e.text[5..].to_string())).expect("Successful tx send"); 56 | } 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | } 63 | 64 | impl Runner for TurnMeDaddy { 65 | fn matches(&mut self, event: &Event) -> bool { 66 | if let Event::TwitchChat(e) = event { 67 | if is_prime(e) { 68 | match e.text.as_str() { 69 | "turn_me_on_daddy" => { 70 | self.tx.unbounded_send(Event::OnCommand).expect("prime commands shouldn't fail"); 71 | return true; 72 | }, 73 | "turn_me_off_daddy" => { 74 | self.tx.unbounded_send(Event::OffCommand).expect("prime commands shouldn't fail"); 75 | return true; 76 | }, 77 | _ => { } 78 | } 79 | } 80 | } 81 | 82 | return false; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use std::thread::{self, JoinHandle}; 2 | 3 | use log::info; 4 | use serde::{Deserialize}; 5 | use futures_channel::mpsc::UnboundedSender; 6 | use tungstenite::connect; 7 | use url::Url; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum Event { 11 | EventsMessage(String), 12 | TwitchChat(TwitchChat), 13 | TwitchEvent(TwitchEvent), 14 | QuirkMessage(String), 15 | DrumCommand(String), 16 | Stop, 17 | Play(String), 18 | OnCommand, 19 | OffCommand, 20 | StartOfProgram, 21 | } 22 | 23 | pub struct Eventer { 24 | pub join_handle: JoinHandle<()>, 25 | } 26 | 27 | #[derive(Debug, Clone, Deserialize)] 28 | struct PartialDecode { 29 | source: String 30 | } 31 | 32 | #[derive(Debug, Clone, Deserialize)] 33 | pub struct TwitchChat { 34 | pub text: String, 35 | pub name: String, 36 | pub sub: bool, 37 | } 38 | 39 | #[derive(Debug, Clone, Deserialize)] 40 | pub struct TwitchEventDataReward { 41 | pub title: String, 42 | } 43 | #[derive(Debug, Clone, Deserialize)] 44 | pub struct TwitchEventData { 45 | pub user_name: String, 46 | pub reward: TwitchEventDataReward, 47 | } 48 | 49 | #[derive(Debug, Clone, Deserialize)] 50 | pub struct TwitchEvent { 51 | pub data: TwitchEventData 52 | } 53 | 54 | fn process_msg(text: String) -> Result, Box> { 55 | if !text.contains("source\":") { 56 | return Ok(None); 57 | } 58 | let partial = serde_json::from_str::(&text.clone()).expect("if it has source, then it should be fine"); 59 | 60 | return match partial.source.as_str() { 61 | "TWITCH_CHAT" => { 62 | info!("TwitchChat received"); 63 | Ok(Some(Event::TwitchChat(serde_json::from_str::(&text)?))) 64 | } 65 | "TWITCH_EVENTSUB" => { 66 | info!("TwitchEventSub received"); 67 | Ok(Some(Event::TwitchEvent(serde_json::from_str::(&text)?))) 68 | } 69 | _ => Ok(None), 70 | } 71 | } 72 | 73 | impl Eventer { 74 | pub fn new(tx: UnboundedSender) -> Eventer { 75 | println!("eventer::new"); 76 | let (mut socket, _) = 77 | connect(Url::parse("ws://events.theprimeagen.tv:42069").unwrap()) 78 | .expect("Can't connect to the events.theprimeagen.tv"); 79 | println!("eventer::connected"); 80 | 81 | // first thing you should do: start consuming incoming messages, 82 | // otherwise they will back up. 83 | let join_handle: JoinHandle<()> = thread::spawn(move || { 84 | while let Ok(msg) = socket.read_message() { 85 | println!("reading messages for event"); 86 | if msg.is_text() { 87 | let text = msg.into_text().expect("A text messaget should have text"); 88 | println!("TEXT: {}", text); 89 | if let Ok(Some(event)) = process_msg(text) { 90 | 91 | println!("sending from eventer"); 92 | tx.unbounded_send(event).expect("send to be successful?"); 93 | println!("sent from eventer"); 94 | } 95 | } 96 | } 97 | return (); 98 | }); 99 | 100 | return Eventer { 101 | join_handle, 102 | }; 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /src/encoding/base_encoding.rs: -------------------------------------------------------------------------------- 1 | const STARTING_CHAR: char = 'g'; 2 | const STARTING_CHAR_U32: u32 = STARTING_CHAR as u32; 3 | pub const TO_HEX: [char; 16] = [ 4 | '0', 5 | '1', 6 | '2', 7 | '3', 8 | '4', 9 | '5', 10 | '6', 11 | '7', 12 | '8', 13 | '9', 14 | 'a', 15 | 'b', 16 | 'c', 17 | 'd', 18 | 'e', 19 | 'f', 20 | ]; 21 | 22 | fn count_to_char(count: usize) -> char { 23 | let count = count - 3; 24 | return char::from_u32(STARTING_CHAR_U32 + count as u32).unwrap(); 25 | } 26 | 27 | fn char_to_count(c: char) -> u32 { 28 | return (c as u32 - STARTING_CHAR_U32) + 3; 29 | } 30 | 31 | fn inflate(data: &String) -> Result { 32 | return data 33 | .chars() 34 | .try_fold((1, String::new()), |(count, mut buf), c| { 35 | let mut next_count = 1; 36 | if c as u32 >= STARTING_CHAR_U32 { 37 | if count > 1 { 38 | return Err(std::io::Error::new( 39 | std::io::ErrorKind::InvalidInput, 40 | "Invalid encoding of string provided by twitch, those dick heads.", 41 | )); 42 | } else { 43 | next_count = char_to_count(c); 44 | } 45 | } else { 46 | for _ in 0..count { 47 | buf.push(c); 48 | } 49 | } 50 | 51 | return Ok((next_count, buf)); 52 | }) 53 | .map(|(_, buf)| buf); 54 | } 55 | 56 | fn deflate(data: &String) -> String { 57 | return data 58 | .chars() 59 | .collect::>() 60 | .as_slice() 61 | .group_by(|a, b| a == b) 62 | .map(|group| { 63 | return group.chunks(22).fold(String::new(), |mut buf, group| { 64 | if group.len() > 2 { 65 | buf.push(count_to_char(group.len())); 66 | buf.push(group[0]); 67 | } else { 68 | for c in group { 69 | buf.push(*c); 70 | } 71 | } 72 | return buf; 73 | }); 74 | }) 75 | .collect::(); 76 | } 77 | 78 | pub fn encode(data: &[u8]) -> Result> { 79 | let enc = hex::encode(data); 80 | let enc = deflate(&enc); 81 | return Ok(enc); 82 | } 83 | 84 | // TODO: Again, look at error handling... 85 | pub fn decode(data: &String) -> Result, Box> { 86 | let data = inflate(&data)?; 87 | return Ok(hex::decode(data)?); 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_deflate() -> Result<(), Box> { 96 | let s = "aabcccad"; 97 | assert_eq!(deflate(&s.to_string()), "aabgcad"); 98 | 99 | let s = "aabcccaaaaaaaaaaaaaaaaaaaaaaad"; 100 | assert_eq!(deflate(&s.to_string()), "aabgczaad"); 101 | return Ok(()); 102 | } 103 | 104 | #[test] 105 | fn test_inflate() -> Result<(), Box> { 106 | let s = "aabgcad"; 107 | assert_eq!(inflate(&s.to_string())?, "aabcccad"); 108 | 109 | return Ok(()); 110 | } 111 | 112 | #[test] 113 | fn test_both() -> Result<(), Box> { 114 | let s = "aabcccaaaaaaaaaaaaaaaaaaaad"; 115 | assert_eq!(inflate(&deflate(&s.to_string()))?, s); 116 | 117 | return Ok(()); 118 | } 119 | 120 | #[test] 121 | fn test_encode() -> Result<(), Box> { 122 | let s = hex::decode("aabcccaaaaaaaaaaaaaaaaaaaaaaad")?; 123 | 124 | assert_eq!(encode(&s)?, "aabgczaad"); 125 | return Ok(()); 126 | } 127 | } 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use beatmedaddy::{ 2 | event::Event, 3 | event_bus::Listener, 4 | }; 5 | use crate::opt::PiOpts; 6 | 7 | use std::sync::Arc; 8 | use beatmedaddy::bangers::bangers::{WriteNode, BangersSerializer, Direction}; 9 | use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket}; 10 | use url::Url; 11 | 12 | pub struct Client { 13 | on: bool, 14 | socket: Option>>, 15 | banger: beatmedaddy::bangers::bangers::Bangers, 16 | opts: Arc, 17 | } 18 | 19 | impl Listener for Client { 20 | fn notify(&mut self, event: &Event) { 21 | match event { 22 | Event::OnCommand => { 23 | self.on = true; 24 | } 25 | Event::OffCommand => { 26 | self.on = false; 27 | } 28 | Event::Stop => { 29 | self.banger.reset(); 30 | self.send_music(); 31 | } 32 | Event::Play(p) => { 33 | if let Some(socket) = &mut self.socket { 34 | socket.write_message(Message::Text(p.to_string())) 35 | .expect( 36 | "Socket connection cannot fail, or this entire program is doo doo garbage", 37 | ); 38 | } 39 | } 40 | Event::DrumCommand(d) => { 41 | if let Err(_) = self.banger.bang(&d) { 42 | println!("Drum command failed {}", d); 43 | return; 44 | } 45 | self.send_music(); 46 | } 47 | _ => {} 48 | } 49 | } 50 | } 51 | 52 | struct SonicPiSerializer { 53 | msg: Vec, 54 | bpm: u16, 55 | } 56 | 57 | impl BangersSerializer for SonicPiSerializer { 58 | fn direction(&self) -> Direction { 59 | return Direction::Column; 60 | } 61 | 62 | fn write(&mut self, node: WriteNode) { 63 | match node { 64 | WriteNode::ThingFinished => {} 65 | 66 | WriteNode::Thing(drum, _, on) => { 67 | if on { 68 | self.msg.push(format!("sample :{}", drum).to_string()); 69 | } 70 | }, 71 | WriteNode::ThingDone => { 72 | self.msg.push("sleep 0.25".to_string()); 73 | } 74 | } 75 | } 76 | } 77 | 78 | impl SonicPiSerializer { 79 | fn new(opts: Arc) -> SonicPiSerializer { 80 | return SonicPiSerializer { 81 | msg: Vec::new(), 82 | bpm: opts.bpm, 83 | } 84 | } 85 | 86 | fn to_string(&self) -> String { 87 | let mut msg = self.msg.clone(); 88 | 89 | // TODO: Research VecDeque 90 | // What is it? 91 | msg.insert(0, "live_loop :bangers do".to_string()); 92 | msg.insert(1, format!(" use_bpm {}", self.bpm)); 93 | msg.push("end".to_string()); 94 | 95 | return msg.join("\n"); 96 | } 97 | } 98 | 99 | impl Client { 100 | fn send_music(&mut self) { 101 | if let Some(socket) = &mut self.socket { 102 | let mut serializer = SonicPiSerializer::new(self.opts.clone()); 103 | 104 | self.banger.serialize(&mut serializer); 105 | 106 | let music = serializer.to_string(); 107 | 108 | println!("music: {}", music); 109 | socket.write_message(Message::Text(music)) 110 | .expect( 111 | "Socket connection cannot fail, or this entire program is doo doo garbage", 112 | ); 113 | } 114 | } 115 | pub fn new(opts: Arc) -> Client { 116 | return Client { 117 | on: false, 118 | socket: None, 119 | banger: beatmedaddy::bangers::bangers::Bangers::new(), 120 | opts, 121 | }; 122 | } 123 | 124 | pub fn connect(&mut self, opts: Arc) -> Result<(), Box> { 125 | let (socket, _) = 126 | connect(Url::parse(format!("ws://{}:{}", opts.addr, opts.port).as_str()).unwrap()) 127 | .expect("Can't connect"); 128 | 129 | self.socket = Some(socket); 130 | 131 | return Ok(()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/bangers/bangers.rs: -------------------------------------------------------------------------------- 1 | use crate::encoding::{decode, encode}; 2 | 3 | use super::{boolizer::Boolizer, consts::BEAT_COUNT}; 4 | 5 | use std::collections::{HashMap, VecDeque}; 6 | 7 | const DRUM_NAMES: [&str; 22] = [ 8 | "bd_pure", 9 | "bd_boom", 10 | "drum_cowbell", 11 | "drum_roll", 12 | "drum_heavy_kick", 13 | "drum_tom_hi_soft", 14 | "drum_tom_hi_hard", 15 | "drum_tom_mid_soft", 16 | "drum_tom_mid_hard", 17 | "drum_tom_lo_soft", 18 | "drum_tom_lo_hard", 19 | "drum_splash_soft", 20 | "drum_splash_hard", 21 | "drum_snare_soft", 22 | "drum_snare_hard", 23 | "drum_cymbal_soft", 24 | "drum_cymbal_hard", 25 | "drum_cymbal_open", 26 | "drum_cymbal_closed", 27 | "drum_cymbal_pedal", 28 | "drum_bass_soft", 29 | "drum_bass_hard", 30 | ]; 31 | 32 | pub enum WriteNode { 33 | Thing(String, usize, bool), 34 | ThingDone, 35 | ThingFinished, 36 | } 37 | 38 | pub trait BangersSerializer { 39 | fn direction(&self) -> Direction; 40 | fn write(&mut self, node: WriteNode); 41 | } 42 | 43 | pub enum Direction { 44 | Row, 45 | Column, 46 | } 47 | 48 | type DrumLine = HashMap; 49 | 50 | pub struct Bangers { 51 | drums: DrumLine, 52 | } 53 | 54 | type PrimeResult = Result>; 55 | 56 | pub fn serialize(map: &DrumLine) -> PrimeResult { 57 | let mut boolizer = Boolizer::new(); 58 | 59 | for drum in DRUM_NAMES { 60 | match map.get(drum) { 61 | Some(line) => { 62 | line.iter().for_each(|b| { 63 | boolizer 64 | .push(*b) 65 | .expect("This to never fail, like the other 2 of them..."); 66 | }); 67 | } 68 | None => {} 69 | } 70 | } 71 | 72 | boolizer.finish()?; 73 | 74 | return Ok(encode(&boolizer.data)?); 75 | } 76 | 77 | #[cfg(test)] 78 | mod test { 79 | use super::*; 80 | #[test] 81 | fn test_charizer() -> Result<(), Box> { 82 | let out = deserialize(&"88z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0x0".to_string())?; 83 | let bd_boom = out.get(DRUM_NAMES[0]).unwrap(); 84 | 85 | assert_eq!(bd_boom[0], true); 86 | assert_eq!(bd_boom[4], true); 87 | 88 | return Ok(()); 89 | } 90 | } 91 | // The best song: ♥s89z0z0z0z0z0z0z0z0u080808080808080az0z0g02t0adadadadadadadaf08080g80808d8daz0n1 92 | // THrobber by TJ: ♥z0z0z0s0221h020080227g08i0200fj0408h0800854z008z0z0q0808080808080808z0z0x0aeaeaeaeaeaeaeafz0n0t8 93 | 94 | // TODO: AGain... the errors. You should really learn how to do this... 95 | pub fn deserialize(str: &String) -> Result> { 96 | let hex_str = decode(str)?; 97 | let bools = hex_str 98 | .iter() 99 | .flat_map(|byte| { 100 | let mut bools: Vec = vec![]; 101 | 102 | for i in 0..8 { 103 | bools.push((byte >> (7 - i)) & 0x1 == 0x1); 104 | } 105 | 106 | return bools; 107 | }) 108 | .collect::>(); 109 | 110 | let mut acc: Vec> = Vec::new(); 111 | let mut curr: Vec = Vec::new(); 112 | 113 | for b in &bools { 114 | if curr.len() == BEAT_COUNT { 115 | acc.push(curr); 116 | curr = Vec::new(); 117 | } 118 | curr.push(*b); 119 | } 120 | 121 | if curr.len() > 0 { 122 | for _ in curr.len()..BEAT_COUNT { 123 | curr.push(false); 124 | } 125 | acc.push(curr); 126 | } 127 | 128 | if bools.len() != BEAT_COUNT * DRUM_NAMES.len() { 129 | return Err(Box::new(std::io::Error::new( 130 | std::io::ErrorKind::InvalidInput, 131 | "WHY DID YOU TRY TO CHEAT ON ME?", 132 | ))); 133 | } 134 | 135 | let mut drum_line: DrumLine = HashMap::new(); 136 | for (idx, drum) in DRUM_NAMES.iter().enumerate() { 137 | for beat_idx in 0..BEAT_COUNT { 138 | drum_line 139 | .entry(drum.to_string()) 140 | .or_insert([false; BEAT_COUNT])[beat_idx] = 141 | *bools.get(idx * BEAT_COUNT + beat_idx).unwrap(); 142 | } 143 | } 144 | 145 | return Ok(drum_line); 146 | } 147 | 148 | impl Bangers { 149 | pub fn new() -> Bangers { 150 | let mut bangers = Bangers { 151 | drums: HashMap::new(), 152 | }; 153 | bangers.reset(); 154 | return bangers; 155 | } 156 | 157 | pub fn reset(&mut self) { 158 | let mut drums = HashMap::new(); 159 | for name in &DRUM_NAMES { 160 | drums.insert(name.to_string(), [false; BEAT_COUNT]); 161 | } 162 | self.drums = drums; 163 | } 164 | 165 | // For twitch 166 | pub fn bang(&mut self, bang: &String) -> Result<(), Box> { 167 | self.drums = deserialize(&bang.chars().skip(1).collect::())?; 168 | return Ok(()); 169 | } 170 | 171 | // For the cli 172 | pub fn toggle(&mut self, drum_idx: usize, column: usize) { 173 | let drums = self.drums.get_mut(DRUM_NAMES[drum_idx]).unwrap(); 174 | drums[column] = !drums[column]; 175 | } 176 | 177 | pub fn get_keys() -> &'static [&'static str] { 178 | return &DRUM_NAMES; 179 | } 180 | 181 | pub fn get_count() -> usize { 182 | return BEAT_COUNT; 183 | } 184 | 185 | pub fn serialize(&self, writer: &mut T) { 186 | let d: Direction = writer.direction(); 187 | 188 | match d { 189 | Direction::Column => { 190 | for pos in 0..BEAT_COUNT { 191 | for drum_name in DRUM_NAMES { 192 | //for (drum, positions) in &self.drums { 193 | writer.write(WriteNode::Thing( 194 | drum_name.to_string(), 195 | pos, 196 | self.drums.get(drum_name).unwrap()[pos], 197 | )); 198 | //} 199 | } 200 | writer.write(WriteNode::ThingDone); 201 | } 202 | } 203 | 204 | Direction::Row => { 205 | for drum_name in DRUM_NAMES { 206 | for pos in 0..BEAT_COUNT { 207 | writer.write(WriteNode::Thing( 208 | drum_name.to_string(), 209 | pos, 210 | self.drums.get(drum_name).unwrap()[pos], 211 | )); 212 | } 213 | writer.write(WriteNode::ThingDone); 214 | } 215 | } 216 | } 217 | 218 | writer.write(WriteNode::ThingFinished); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/bin/test_me_daddy.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, num::ParseIntError}; 2 | 3 | use itertools::Itertools; 4 | 5 | #[derive(Debug)] 6 | enum DaddyIssues { 7 | ParseInt(ParseIntError), 8 | MalformedString(String), 9 | } 10 | 11 | impl From for DaddyIssues { 12 | fn from(e: ParseIntError) -> Self { 13 | return DaddyIssues::ParseInt(e); 14 | } 15 | } 16 | 17 | fn get_pairs(s: &str, length: usize, offset: usize) -> HashMap { 18 | return s[offset..] 19 | .as_bytes() 20 | .chunks(length) 21 | .fold(HashMap::new(), |mut map, chunk| { 22 | let str = chunk 23 | .iter() 24 | .map(|x| char::from_u32(*x as u32).unwrap()) 25 | .collect::(); 26 | if str.len() == length { 27 | *map.entry(str).or_insert(0) += 1; 28 | } 29 | return map; 30 | }); 31 | } 32 | 33 | fn encode(s: &str, length: usize) -> HashMap { 34 | let map = get_pairs(s, length, 0); 35 | let map2 = get_pairs(s, length, 1); 36 | 37 | return map2.iter().fold(map, |mut map, (k1, v1)| { 38 | if let Some(v) = map.get(k1) { 39 | if v < v1 { 40 | *map.entry(k1.clone()).or_insert(*v1) = *v1; 41 | } 42 | } else { 43 | map.insert(k1.clone(), *v1); 44 | } 45 | 46 | return map; 47 | }); 48 | } 49 | 50 | fn squeeze(s: &str, r: &str, w: &str) -> String { 51 | return s.split(r).join(w); 52 | } 53 | 54 | fn squeeze_once(s: &str, len: usize, r: &str) -> Option<(String, String)> { 55 | let groups = encode(s, len); 56 | let first = groups.iter().sorted_by(|a, b| b.1.cmp(a.1)).nth(0)?; 57 | 58 | if first.1 < &4 { 59 | return None; 60 | } 61 | 62 | return Some((first.0.to_string(), squeeze(s, first.0, r))); 63 | } 64 | 65 | fn squeeze_me_daddy(str: &String) -> String { 66 | let mut str = str.to_string(); 67 | let mut count: u32 = 0; 68 | let mut replacements: Vec = vec![]; 69 | let start = 'A'; 70 | 71 | loop { 72 | let c = char::from_u32(start as u32 + count).unwrap().to_string(); 73 | if let Some(s) = squeeze_once(&str, 2, &c) { 74 | count += 1; 75 | replacements.push(format!("{}{}", c, s.0)); 76 | str = s.1; 77 | } else { 78 | break; 79 | } 80 | } 81 | 82 | if replacements.len() > 0 { 83 | replacements.insert(0, replacements.len().to_string()); 84 | return format!("_{}{}", replacements.iter().join(""), str); 85 | } 86 | return str.to_string(); 87 | } 88 | 89 | fn split_me_daddy(str: String) -> Result<(usize, String, String), DaddyIssues> { 90 | let count_str: String = str 91 | .chars() 92 | .skip(1) 93 | .take_while(|c| c.is_digit(10)) 94 | .collect::(); 95 | 96 | let count: usize = count_str.parse()?; 97 | 98 | return Ok(( 99 | count, 100 | str.chars().skip(1 + count_str.len()).take(count * 3).collect::(), 101 | str.chars().skip(1 + count_str.len() + count * 3).collect::(), 102 | )); 103 | } 104 | fn spread_me_daddy(str: &String) -> Result { 105 | if !str.starts_with('_') { 106 | return Ok(str.to_string()); 107 | } 108 | 109 | let (count, replacements, mut str) = split_me_daddy(str.to_string())?; 110 | 111 | if replacements.len() % 3 != 0 { 112 | return Err(DaddyIssues::MalformedString( 113 | "Expected replacements string to be divisible by 3".to_string(), 114 | )); 115 | } 116 | 117 | for i in (0..count).rev() { 118 | let set = &replacements[i * 3..i * 3 + 3]; 119 | let replacer = &set[0..1]; 120 | let replacee = &set[1..]; 121 | str = str.replace(replacer, replacee); 122 | } 123 | 124 | return Ok(str); 125 | } 126 | 127 | #[cfg(test)] 128 | mod test_squeeze { 129 | use super::*; 130 | #[test] 131 | fn test_simple_no() { 132 | let string: String = "090909".to_string(); 133 | let string2: String = squeeze_me_daddy(&string); 134 | 135 | assert_eq!(string, string2); 136 | } 137 | 138 | #[test] 139 | fn test_simple_yes() { 140 | let string: String = "09090909".to_string(); 141 | let string2: String = squeeze_me_daddy(&string); 142 | 143 | assert_eq!("_1A09AAAA", string2); 144 | } 145 | 146 | #[test] 147 | fn test_simple_yes_longer() { 148 | let string: String = "0909090909099009".to_string(); 149 | let string2: String = squeeze_me_daddy(&string); 150 | 151 | assert_eq!("_1A09AAAAAA90A", string2); 152 | } 153 | 154 | #[test] 155 | fn complex_yes() { 156 | let string: String = "09090909090909099009".to_string(); 157 | let string2: String = squeeze_me_daddy(&string); 158 | 159 | assert_eq!("_2A09BAABBBB90A", string2); 160 | } 161 | 162 | #[test] 163 | fn complex_yes_longer() { 164 | let string: String = "0909090909090909900909090909090909".to_string(); 165 | let string2: String = squeeze_me_daddy(&string); 166 | 167 | assert_eq!("_3A09BAACBBCC90CC", string2); 168 | } 169 | 170 | 171 | } 172 | 173 | #[cfg(test)] 174 | mod test_spread { 175 | use super::*; 176 | #[test] 177 | fn test_spread_noop() -> Result<(), DaddyIssues> { 178 | let string: String = "090909".to_string(); 179 | let string2: String = squeeze_me_daddy(&string); 180 | let string3: String = spread_me_daddy(&string2)?; 181 | 182 | assert_eq!(string3, string); 183 | 184 | return Ok(()); 185 | } 186 | 187 | #[test] 188 | fn test_spread() -> Result<(), DaddyIssues> { 189 | let string: String = "09090909".to_string(); 190 | let string2: String = squeeze_me_daddy(&string); 191 | let string3: String = spread_me_daddy(&string2)?; 192 | 193 | assert_eq!(string3, string); 194 | 195 | return Ok(()); 196 | } 197 | 198 | #[test] 199 | fn test_spread_complex() -> Result<(), DaddyIssues> { 200 | let string: String = "09090909090909099009".to_string(); 201 | let string2: String = squeeze_me_daddy(&string); 202 | let string3: String = spread_me_daddy(&string2)?; 203 | 204 | assert_eq!(string3, string); 205 | return Ok(()); 206 | } 207 | 208 | #[test] 209 | fn test_spread_complex_longer() -> Result<(), DaddyIssues> { 210 | let string: String = "0909090909090909900909090909090909".to_string(); 211 | let string2: String = squeeze_me_daddy(&string); 212 | let string3: String = spread_me_daddy(&string2)?; 213 | 214 | assert_eq!(string3, string); 215 | return Ok(()); 216 | } 217 | 218 | #[test] 219 | fn test_real_failure_1() -> Result<(), DaddyIssues> { 220 | let string: String = "00808080800802a00g808008g080bbz0z0z0z0l0808082a0808082808080ad82808082aaq0104q0g4z0z0002r022az0z0005r0g1z0z0z0z0k010828085808280d080808582a08082d53z0m09z0z0z0p02s012dadadadadadadadadadadadadadadadfg05g05g05g05g05g05g05002dz0z0x".to_string(); 221 | let string2: String = squeeze_me_daddy(&string); 222 | let string3: String = spread_me_daddy(&string2)?; 223 | 224 | assert_eq!(string3, string); 225 | return Ok(()); 226 | } 227 | } 228 | 229 | fn main() -> Result<(), DaddyIssues> { 230 | let string: String = "00808080800802a00g808008g080bbz0z0z0z0l0808082a0808082808080ad82808082aa0044m0104q0g4z0p02an02r022az0p019n05r0g1z0z0z0z0k010828085808280d080808582a08082d53z0m09z0z0z0p02s012dadadadadadadadadadadadadadadadfg05g05g05g05g05g05g05002dz0z0x0".to_string(); 231 | let string2: String = squeeze_me_daddy(&string); 232 | let string3: String = spread_me_daddy(&string2)?; 233 | 234 | assert_eq!(string, string3); 235 | println!("{} vs {}", string2.len(), string3.len()); 236 | println!("{}", string3); 237 | println!("{}", string2); 238 | return Ok(()); 239 | } 240 | -------------------------------------------------------------------------------- /src/encoding/squeeze_me_daddy.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, num::ParseIntError}; 2 | 3 | use itertools::Itertools; 4 | 5 | #[derive(Debug)] 6 | pub enum DaddyIssues { 7 | ParseInt(ParseIntError), 8 | MalformedString(String), 9 | } 10 | 11 | impl std::fmt::Display for DaddyIssues { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | return match self { 14 | DaddyIssues::ParseInt(e) => { 15 | write!(f, "{}", e) 16 | }, 17 | DaddyIssues::MalformedString(e) => { 18 | write!(f, "{}", e) 19 | }, 20 | } 21 | } 22 | } 23 | 24 | impl std::error::Error for DaddyIssues { 25 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 26 | return match self { 27 | DaddyIssues::ParseInt(e) => Some(e), 28 | DaddyIssues::MalformedString(e) => { 29 | None 30 | }, 31 | }; 32 | } 33 | 34 | fn description(&self) -> &str { 35 | "description() is deprecated; use Display" 36 | } 37 | 38 | fn cause(&self) -> Option<&dyn std::error::Error> { 39 | self.source() 40 | } 41 | } 42 | 43 | impl From for DaddyIssues { 44 | fn from(e: ParseIntError) -> Self { 45 | return DaddyIssues::ParseInt(e); 46 | } 47 | } 48 | 49 | fn get_pairs(s: &str, length: usize, offset: usize) -> HashMap { 50 | return s[offset..] 51 | .as_bytes() 52 | .chunks(length) 53 | .fold(HashMap::new(), |mut map, chunk| { 54 | let str = chunk 55 | .iter() 56 | .map(|x| char::from_u32(*x as u32).unwrap()) 57 | .collect::(); 58 | if str.len() == length { 59 | *map.entry(str).or_insert(0) += 1; 60 | } 61 | return map; 62 | }); 63 | } 64 | 65 | fn encode(s: &str, length: usize) -> HashMap { 66 | let map = get_pairs(s, length, 0); 67 | let map2 = get_pairs(s, length, 1); 68 | 69 | return map2.iter().fold(map, |mut map, (k1, v1)| { 70 | if let Some(v) = map.get(k1) { 71 | if v < v1 { 72 | *map.entry(k1.clone()).or_insert(*v1) = *v1; 73 | } 74 | } else { 75 | map.insert(k1.clone(), *v1); 76 | } 77 | 78 | return map; 79 | }); 80 | } 81 | 82 | fn squeeze(s: &str, r: &str, w: &str) -> String { 83 | return s.split(r).join(w); 84 | } 85 | 86 | fn squeeze_once(s: &str, len: usize, r: &str) -> Option<(String, String)> { 87 | let groups = encode(s, len); 88 | let first = groups.iter().sorted_by(|a, b| b.1.cmp(a.1)).nth(0)?; 89 | 90 | if first.1 < &4 { 91 | return None; 92 | } 93 | 94 | return Some((first.0.to_string(), squeeze(s, first.0, r))); 95 | } 96 | 97 | pub fn squeeze_me_daddy(str: &String) -> String { 98 | let mut str = str.to_string(); 99 | let mut count: u32 = 0; 100 | let mut replacements: Vec = vec![]; 101 | let start = 'A'; 102 | 103 | loop { 104 | let c = char::from_u32(start as u32 + count).unwrap().to_string(); 105 | if let Some(s) = squeeze_once(&str, 2, &c) { 106 | count += 1; 107 | replacements.push(format!("{}{}", c, s.0)); 108 | str = s.1; 109 | } else { 110 | break; 111 | } 112 | } 113 | 114 | if replacements.len() > 0 { 115 | replacements.insert(0, replacements.len().to_string()); 116 | return format!("_{}{}", replacements.iter().join(""), str); 117 | } 118 | return str.to_string(); 119 | } 120 | 121 | fn split_me_daddy(str: String) -> Result<(usize, String, String), DaddyIssues> { 122 | let count_str: String = str 123 | .chars() 124 | .skip(1) 125 | .take_while(|c| c.is_digit(10)) 126 | .collect::(); 127 | 128 | let count: usize = count_str.parse()?; 129 | 130 | return Ok(( 131 | count, 132 | str.chars().skip(1 + count_str.len()).take(count * 3).collect::(), 133 | str.chars().skip(1 + count_str.len() + count * 3).collect::(), 134 | )); 135 | } 136 | pub fn spread_me_daddy(str: &String) -> Result { 137 | if !str.starts_with('_') { 138 | return Ok(str.to_string()); 139 | } 140 | 141 | let (count, replacements, mut str) = split_me_daddy(str.to_string())?; 142 | 143 | if replacements.len() % 3 != 0 { 144 | return Err(DaddyIssues::MalformedString( 145 | "Expected replacements string to be divisible by 3".to_string(), 146 | )); 147 | } 148 | 149 | for i in (0..count).rev() { 150 | let set = &replacements[i * 3..i * 3 + 3]; 151 | let replacer = &set[0..1]; 152 | let replacee = &set[1..]; 153 | str = str.replace(replacer, replacee); 154 | } 155 | 156 | return Ok(str); 157 | } 158 | 159 | #[cfg(test)] 160 | mod test_squeeze { 161 | use super::*; 162 | #[test] 163 | fn test_simple_no() { 164 | let string: String = "090909".to_string(); 165 | let string2: String = squeeze_me_daddy(&string); 166 | 167 | assert_eq!(string, string2); 168 | } 169 | 170 | #[test] 171 | fn test_simple_yes() { 172 | let string: String = "09090909".to_string(); 173 | let string2: String = squeeze_me_daddy(&string); 174 | 175 | assert_eq!("_1A09AAAA", string2); 176 | } 177 | 178 | #[test] 179 | fn test_simple_yes_longer() { 180 | let string: String = "0909090909099009".to_string(); 181 | let string2: String = squeeze_me_daddy(&string); 182 | 183 | assert_eq!("_1A09AAAAAA90A", string2); 184 | } 185 | 186 | #[test] 187 | fn complex_yes() { 188 | let string: String = "09090909090909099009".to_string(); 189 | let string2: String = squeeze_me_daddy(&string); 190 | 191 | assert_eq!("_2A09BAABBBB90A", string2); 192 | } 193 | 194 | #[test] 195 | fn complex_yes_longer() { 196 | let string: String = "0909090909090909900909090909090909".to_string(); 197 | let string2: String = squeeze_me_daddy(&string); 198 | 199 | assert_eq!("_3A09BAACBBCC90CC", string2); 200 | } 201 | 202 | 203 | } 204 | 205 | #[cfg(test)] 206 | mod test_spread { 207 | use super::*; 208 | #[test] 209 | fn test_spread_noop() -> Result<(), DaddyIssues> { 210 | let string: String = "090909".to_string(); 211 | let string2: String = squeeze_me_daddy(&string); 212 | let string3: String = spread_me_daddy(&string2)?; 213 | 214 | assert_eq!(string3, string); 215 | 216 | return Ok(()); 217 | } 218 | 219 | #[test] 220 | fn test_spread() -> Result<(), DaddyIssues> { 221 | let string: String = "09090909".to_string(); 222 | let string2: String = squeeze_me_daddy(&string); 223 | let string3: String = spread_me_daddy(&string2)?; 224 | 225 | assert_eq!(string3, string); 226 | 227 | return Ok(()); 228 | } 229 | 230 | #[test] 231 | fn test_spread_complex() -> Result<(), DaddyIssues> { 232 | let string: String = "09090909090909099009".to_string(); 233 | let string2: String = squeeze_me_daddy(&string); 234 | let string3: String = spread_me_daddy(&string2)?; 235 | 236 | assert_eq!(string3, string); 237 | return Ok(()); 238 | } 239 | 240 | #[test] 241 | fn test_spread_complex_longer() -> Result<(), DaddyIssues> { 242 | let string: String = "0909090909090909900909090909090909".to_string(); 243 | let string2: String = squeeze_me_daddy(&string); 244 | let string3: String = spread_me_daddy(&string2)?; 245 | 246 | assert_eq!(string3, string); 247 | return Ok(()); 248 | } 249 | 250 | #[test] 251 | fn test_real_failure_1() -> Result<(), DaddyIssues> { 252 | let string: String = "00808080800802a00g808008g080bbz0z0z0z0l0808082a0808082808080ad82808082aaq0104q0g4z0z0002r022az0z0005r0g1z0z0z0z0k010828085808280d080808582a08082d53z0m09z0z0z0p02s012dadadadadadadadadadadadadadadadfg05g05g05g05g05g05g05002dz0z0x".to_string(); 253 | let string2: String = squeeze_me_daddy(&string); 254 | let string3: String = spread_me_daddy(&string2)?; 255 | 256 | assert_eq!(string3, string); 257 | return Ok(()); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/bin/ui/ui.rs: -------------------------------------------------------------------------------- 1 | use beatmedaddy::bangers::bangers::{self, Bangers, BangersSerializer, WriteNode}; 2 | use beatmedaddy::bangers::boolizer::{is_bang_command, Boolizer}; 3 | use beatmedaddy::bangers::consts::{BEAT_COUNT, STARTING_UTF}; 4 | use beatmedaddy::twitch::twitch_client::Twitch; 5 | use std::collections::HashMap; 6 | use std::fs; 7 | use std::io::{self, Stdout}; 8 | use termion::event::Key; 9 | use termion::raw::{IntoRawMode, RawTerminal}; 10 | use tui::backend::TermionBackend; 11 | use tui::layout::{Alignment, Constraint, Direction, Layout}; 12 | use tui::style::{Color, Style}; 13 | use tui::text::{Span, Spans}; 14 | use tui::widgets::{Block, Paragraph, Wrap}; 15 | use tui::Terminal; 16 | 17 | use super::opts::UiConfig; 18 | 19 | const SEPARATOR: &str = "░"; 20 | const UNSELECTED: &str = "▒"; 21 | const SELECTED: &str = "█"; 22 | 23 | pub type PrimeResult = Result>; 24 | 25 | pub struct UI { 26 | twitch: Option, 27 | bangers: Bangers, 28 | terminal: Terminal>>, 29 | cursor: Cursor, 30 | opts: UiConfig, 31 | title: String, 32 | } 33 | 34 | struct UIBangerSerializer { 35 | drums: HashMap, 36 | } 37 | 38 | // TODO: Do I like the cursor moving itself? Or just have it dumb? 39 | struct Cursor { 40 | drum_idx: usize, 41 | column: usize, 42 | } 43 | 44 | impl Cursor { 45 | fn new() -> Cursor { 46 | return Cursor { 47 | drum_idx: 0, 48 | column: 0, 49 | }; 50 | } 51 | 52 | fn j(&mut self) { 53 | self.drum_idx = (self.drum_idx + 1) % Bangers::get_keys().len(); 54 | } 55 | 56 | fn k(&mut self) { 57 | if self.drum_idx == 0 { 58 | self.drum_idx = Bangers::get_keys().len() - 1; 59 | } else { 60 | self.drum_idx = self.drum_idx - 1; 61 | } 62 | } 63 | 64 | #[allow(non_snake_case)] 65 | fn A(&mut self) { 66 | self.column = BEAT_COUNT - 1; 67 | } 68 | 69 | #[allow(non_snake_case)] 70 | fn I(&mut self) { 71 | self.column = 0; 72 | } 73 | 74 | fn l(&mut self) { 75 | self.column = (self.column + 1).min(BEAT_COUNT - 1); 76 | } 77 | 78 | fn h(&mut self) { 79 | self.column = self.column.saturating_sub(1); 80 | } 81 | 82 | fn w(&mut self) { 83 | self.column = ((self.column + 4) & (!3)).min(BEAT_COUNT - 1); 84 | } 85 | 86 | #[allow(non_snake_case)] 87 | fn W(&mut self) { 88 | self.column = ((self.column + 16) & (!15)).min(BEAT_COUNT - 1); 89 | } 90 | 91 | fn b(&mut self) { 92 | self.column = self.column.saturating_sub(1) & (!3); 93 | } 94 | 95 | #[allow(non_snake_case)] 96 | fn B(&mut self) { 97 | self.column = self.column.saturating_sub(1) & (!15); 98 | } 99 | 100 | fn at_drum(&self, drum_idx: usize) -> bool { 101 | return self.drum_idx == drum_idx; 102 | } 103 | 104 | fn at(&self, drum_idx: usize, column: usize) -> bool { 105 | return self.drum_idx == drum_idx && self.column == column; 106 | } 107 | } 108 | 109 | impl UIBangerSerializer { 110 | fn new() -> UIBangerSerializer { 111 | return UIBangerSerializer { 112 | drums: HashMap::new(), 113 | }; 114 | } 115 | 116 | fn drums_to_spans<'a>(&mut self, order: &'static [&'static str], cursor: &Cursor) -> Vec> { 117 | let mut out: Vec> = Vec::new(); 118 | 119 | for (idx, drum) in order.iter().enumerate() { 120 | if cursor.at_drum(idx) { 121 | // TODO: Fix this and make it pretty 122 | out.push(Spans::from(drum.to_string())); 123 | } else { 124 | out.push(Spans::from(drum.to_string())); 125 | } 126 | } 127 | 128 | return out; 129 | } 130 | 131 | // TODO: the bounds of the ui 132 | // TODO: ^ 133 | fn to_spans<'a>(&mut self, order: &'static [&'static str], cursor: &Cursor) -> Vec> { 134 | let mut out: Vec> = Vec::new(); 135 | 136 | // How do i place cursor? 137 | for (idx, drum) in order.iter().enumerate() { 138 | let span_list = self.drums 139 | .entry(drum.to_string()) 140 | .or_insert([false; BEAT_COUNT]) 141 | .iter() 142 | .enumerate() 143 | .map(|(col, x)| { 144 | let note = if *x { SELECTED } else { UNSELECTED }; 145 | if cursor.at(idx, col) { 146 | return Span::styled(note, Style::default().fg(Color::Red)) 147 | } else if col % 4 == 0 { 148 | return Span::styled(note, Style::default().fg(Color::DarkGray)) 149 | } 150 | return Span::from(note); 151 | }) 152 | .collect::>(); 153 | 154 | out.push(Spans::from(span_list)); 155 | } 156 | 157 | return out; 158 | } 159 | } 160 | 161 | struct TwitchSerializer { 162 | data: Boolizer, 163 | } 164 | 165 | impl TwitchSerializer { 166 | fn new() -> TwitchSerializer { 167 | return TwitchSerializer { 168 | data: Boolizer::new() 169 | } 170 | } 171 | 172 | fn to_twitch_string(&self) -> PrimeResult { 173 | return Ok(STARTING_UTF.to_string() + &self.data.encode()?); 174 | } 175 | 176 | } 177 | 178 | impl BangersSerializer for TwitchSerializer { 179 | fn direction(&self) -> bangers::Direction { 180 | return bangers::Direction::Row; 181 | } 182 | 183 | fn write(&mut self, node: WriteNode) { 184 | match node { 185 | WriteNode::Thing(.., on) => { 186 | self.data.push(on).expect("This should never fail, said me once before."); 187 | } 188 | 189 | WriteNode::ThingDone => {} 190 | WriteNode::ThingFinished => { } 191 | } 192 | } 193 | } 194 | 195 | impl BangersSerializer for UIBangerSerializer { 196 | 197 | fn direction(&self) -> bangers::Direction { 198 | return bangers::Direction::Row; 199 | } 200 | 201 | fn write(&mut self, node: WriteNode) { 202 | match node { 203 | WriteNode::Thing(drum, pos, on) => { 204 | self.drums.entry(drum).or_insert([false; BEAT_COUNT])[pos] = on; 205 | } 206 | WriteNode::ThingDone => {} 207 | WriteNode::ThingFinished => {} 208 | } 209 | } 210 | } 211 | 212 | macro_rules! call_cursor { 213 | ($self:expr, $x:ident) => { 214 | { 215 | $self.cursor.$x(); 216 | $self.render()?; 217 | } 218 | }; 219 | } 220 | 221 | impl UI { 222 | pub fn new(twitch: Option, opts: UiConfig) -> Result> { 223 | let stdout = io::stdout().into_raw_mode()?; 224 | let backend = TermionBackend::new(stdout); 225 | let beat: String = fs::read_to_string(&opts.beat).unwrap_or("".to_string()).trim().to_string(); 226 | let mut terminal = Terminal::new(backend)?; 227 | terminal.clear()?; 228 | 229 | let mut bangers = Bangers::new(); 230 | let mut title = "Sugma".to_string(); 231 | 232 | if is_bang_command(&beat) { 233 | bangers.bang(&beat)?; 234 | 235 | let mut serializer = TwitchSerializer::new(); 236 | bangers.serialize(&mut serializer); 237 | title = serializer.to_twitch_string()?; 238 | } 239 | 240 | return Ok(UI { 241 | bangers, 242 | terminal, 243 | opts, 244 | twitch, 245 | cursor: Cursor::new(), 246 | title, 247 | }); 248 | } 249 | 250 | // TODO: Look at the anyhow crate 251 | pub async fn key(&mut self, key: Key) -> Result<(), Box> { 252 | match key { 253 | Key::Char('_') => call_cursor!(self, A), 254 | Key::Char('$') => call_cursor!(self, I), 255 | Key::Char('I') => call_cursor!(self, I), 256 | Key::Char('l') => call_cursor!(self, l), 257 | Key::Char('h') => call_cursor!(self, h), 258 | Key::Char('B') => call_cursor!(self, B), 259 | Key::Char('b') => call_cursor!(self, b), 260 | Key::Char('W') => call_cursor!(self, W), 261 | Key::Char('w') => call_cursor!(self, w), 262 | Key::Char('j') => call_cursor!(self, j), 263 | Key::Char('k') => call_cursor!(self, k), 264 | Key::Char(' ') => { 265 | self.bangers.toggle(self.cursor.drum_idx, self.cursor.column); 266 | self.render()?; 267 | } 268 | Key::Char('\n') => { 269 | let mut serializer = TwitchSerializer::new(); 270 | self.bangers.serialize(&mut serializer); 271 | self.title = serializer.to_twitch_string()?; 272 | 273 | self.save_file()?; 274 | 275 | if let Some(twitch) = &mut self.twitch { 276 | twitch.send_message(self.title.clone()).await?; 277 | } 278 | } 279 | _ => {} 280 | } 281 | return Ok(()); 282 | } 283 | 284 | fn save_file(&mut self) -> Result<(), Box> { 285 | if self.opts.beat.exists() { 286 | fs::write(&self.opts.beat, &self.title)?; 287 | } 288 | 289 | return Ok(()); 290 | } 291 | 292 | pub fn tick(&mut self) -> Result<(), Box> { 293 | return self.render(); 294 | } 295 | 296 | fn render(&mut self) -> Result<(), Box> { 297 | self.terminal.draw(|f| { 298 | let size = f.size(); 299 | 300 | let top = Layout::default() 301 | .direction(Direction::Vertical) 302 | .constraints([Constraint::Length(2), Constraint::Min(12)].as_ref()) 303 | .split(size); 304 | 305 | let title = Paragraph::new( 306 | Span::styled(self.title.clone(), Style::default().fg(Color::Red)) 307 | ) 308 | .block(Block::default()) 309 | .alignment(Alignment::Left) 310 | .wrap(Wrap { trim: true }); 311 | 312 | f.render_widget(title, top[0]); 313 | 314 | let chunks = Layout::default() 315 | .direction(Direction::Horizontal) 316 | .constraints([Constraint::Length(18), Constraint::Min(12)].as_ref()) 317 | .split(top[1]); 318 | 319 | let mut serializer = UIBangerSerializer::new(); 320 | self.bangers 321 | .serialize(&mut serializer); 322 | let drum_lines = serializer.to_spans(Bangers::get_keys(), &self.cursor); 323 | let drums = serializer.drums_to_spans(Bangers::get_keys(), &self.cursor); 324 | 325 | let drum_names = Paragraph::new(drums) 326 | .block(Block::default().title("Drums")) 327 | .alignment(Alignment::Left) 328 | .wrap(Wrap { trim: true }); 329 | f.render_widget(drum_names, chunks[0]); 330 | 331 | let paragraph = Paragraph::new(drum_lines) 332 | .block(Block::default().title("Tracks")) 333 | .alignment(Alignment::Left) 334 | .wrap(Wrap { trim: true }); 335 | f.render_widget(paragraph, chunks[1]); 336 | })?; 337 | 338 | return Ok(()); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "async-trait" 25 | version = "0.1.52" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" 28 | dependencies = [ 29 | "proc-macro2", 30 | "quote", 31 | "syn", 32 | ] 33 | 34 | [[package]] 35 | name = "atty" 36 | version = "0.2.14" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 39 | dependencies = [ 40 | "hermit-abi", 41 | "libc", 42 | "winapi", 43 | ] 44 | 45 | [[package]] 46 | name = "autocfg" 47 | version = "1.0.1" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 50 | 51 | [[package]] 52 | name = "base64" 53 | version = "0.13.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 56 | 57 | [[package]] 58 | name = "beatmedaddy" 59 | version = "0.1.0" 60 | dependencies = [ 61 | "async-trait", 62 | "base64", 63 | "dotenv", 64 | "env_logger", 65 | "futures", 66 | "futures-channel", 67 | "futures-util", 68 | "hex", 69 | "hyper", 70 | "itertools", 71 | "log", 72 | "reqwest", 73 | "serde", 74 | "serde_json", 75 | "structopt", 76 | "termion", 77 | "tokio", 78 | "tui", 79 | "tungstenite", 80 | "twitch-irc", 81 | "url", 82 | ] 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.3.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 89 | 90 | [[package]] 91 | name = "block-buffer" 92 | version = "0.9.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 95 | dependencies = [ 96 | "generic-array", 97 | ] 98 | 99 | [[package]] 100 | name = "bumpalo" 101 | version = "3.8.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 104 | 105 | [[package]] 106 | name = "byteorder" 107 | version = "1.4.3" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 110 | 111 | [[package]] 112 | name = "bytes" 113 | version = "1.1.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 116 | 117 | [[package]] 118 | name = "cassowary" 119 | version = "0.3.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 122 | 123 | [[package]] 124 | name = "cc" 125 | version = "1.0.72" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 128 | 129 | [[package]] 130 | name = "cfg-if" 131 | version = "1.0.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 134 | 135 | [[package]] 136 | name = "chrono" 137 | version = "0.4.19" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 140 | dependencies = [ 141 | "libc", 142 | "num-integer", 143 | "num-traits", 144 | "time", 145 | "winapi", 146 | ] 147 | 148 | [[package]] 149 | name = "clap" 150 | version = "2.34.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 153 | dependencies = [ 154 | "ansi_term", 155 | "atty", 156 | "bitflags", 157 | "strsim", 158 | "textwrap", 159 | "unicode-width", 160 | "vec_map", 161 | ] 162 | 163 | [[package]] 164 | name = "core-foundation" 165 | version = "0.9.2" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" 168 | dependencies = [ 169 | "core-foundation-sys", 170 | "libc", 171 | ] 172 | 173 | [[package]] 174 | name = "core-foundation-sys" 175 | version = "0.8.3" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 178 | 179 | [[package]] 180 | name = "cpufeatures" 181 | version = "0.2.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 184 | dependencies = [ 185 | "libc", 186 | ] 187 | 188 | [[package]] 189 | name = "digest" 190 | version = "0.9.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 193 | dependencies = [ 194 | "generic-array", 195 | ] 196 | 197 | [[package]] 198 | name = "dotenv" 199 | version = "0.15.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 202 | 203 | [[package]] 204 | name = "either" 205 | version = "1.6.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 208 | 209 | [[package]] 210 | name = "encoding_rs" 211 | version = "0.8.30" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" 214 | dependencies = [ 215 | "cfg-if", 216 | ] 217 | 218 | [[package]] 219 | name = "enum_dispatch" 220 | version = "0.3.7" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "bd53b3fde38a39a06b2e66dc282f3e86191e53bd04cc499929c15742beae3df8" 223 | dependencies = [ 224 | "once_cell", 225 | "proc-macro2", 226 | "quote", 227 | "syn", 228 | ] 229 | 230 | [[package]] 231 | name = "env_logger" 232 | version = "0.9.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 235 | dependencies = [ 236 | "atty", 237 | "humantime", 238 | "log", 239 | "regex", 240 | "termcolor", 241 | ] 242 | 243 | [[package]] 244 | name = "fnv" 245 | version = "1.0.7" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 248 | 249 | [[package]] 250 | name = "foreign-types" 251 | version = "0.3.2" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 254 | dependencies = [ 255 | "foreign-types-shared", 256 | ] 257 | 258 | [[package]] 259 | name = "foreign-types-shared" 260 | version = "0.1.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 263 | 264 | [[package]] 265 | name = "form_urlencoded" 266 | version = "1.0.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 269 | dependencies = [ 270 | "matches", 271 | "percent-encoding", 272 | ] 273 | 274 | [[package]] 275 | name = "futures" 276 | version = "0.3.19" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" 279 | dependencies = [ 280 | "futures-channel", 281 | "futures-core", 282 | "futures-executor", 283 | "futures-io", 284 | "futures-sink", 285 | "futures-task", 286 | "futures-util", 287 | ] 288 | 289 | [[package]] 290 | name = "futures-channel" 291 | version = "0.3.19" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" 294 | dependencies = [ 295 | "futures-core", 296 | "futures-sink", 297 | ] 298 | 299 | [[package]] 300 | name = "futures-core" 301 | version = "0.3.19" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" 304 | 305 | [[package]] 306 | name = "futures-executor" 307 | version = "0.3.19" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" 310 | dependencies = [ 311 | "futures-core", 312 | "futures-task", 313 | "futures-util", 314 | ] 315 | 316 | [[package]] 317 | name = "futures-io" 318 | version = "0.3.19" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" 321 | 322 | [[package]] 323 | name = "futures-macro" 324 | version = "0.3.19" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" 327 | dependencies = [ 328 | "proc-macro2", 329 | "quote", 330 | "syn", 331 | ] 332 | 333 | [[package]] 334 | name = "futures-sink" 335 | version = "0.3.19" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" 338 | 339 | [[package]] 340 | name = "futures-task" 341 | version = "0.3.19" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" 344 | 345 | [[package]] 346 | name = "futures-util" 347 | version = "0.3.19" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" 350 | dependencies = [ 351 | "futures-channel", 352 | "futures-core", 353 | "futures-io", 354 | "futures-macro", 355 | "futures-sink", 356 | "futures-task", 357 | "memchr", 358 | "pin-project-lite", 359 | "pin-utils", 360 | "slab", 361 | ] 362 | 363 | [[package]] 364 | name = "generic-array" 365 | version = "0.14.4" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 368 | dependencies = [ 369 | "typenum", 370 | "version_check", 371 | ] 372 | 373 | [[package]] 374 | name = "getrandom" 375 | version = "0.2.3" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 378 | dependencies = [ 379 | "cfg-if", 380 | "libc", 381 | "wasi", 382 | ] 383 | 384 | [[package]] 385 | name = "h2" 386 | version = "0.3.9" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" 389 | dependencies = [ 390 | "bytes", 391 | "fnv", 392 | "futures-core", 393 | "futures-sink", 394 | "futures-util", 395 | "http", 396 | "indexmap", 397 | "slab", 398 | "tokio", 399 | "tokio-util", 400 | "tracing", 401 | ] 402 | 403 | [[package]] 404 | name = "hashbrown" 405 | version = "0.11.2" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 408 | 409 | [[package]] 410 | name = "heck" 411 | version = "0.3.3" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 414 | dependencies = [ 415 | "unicode-segmentation", 416 | ] 417 | 418 | [[package]] 419 | name = "hermit-abi" 420 | version = "0.1.19" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 423 | dependencies = [ 424 | "libc", 425 | ] 426 | 427 | [[package]] 428 | name = "hex" 429 | version = "0.4.3" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 432 | 433 | [[package]] 434 | name = "http" 435 | version = "0.2.5" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" 438 | dependencies = [ 439 | "bytes", 440 | "fnv", 441 | "itoa 0.4.8", 442 | ] 443 | 444 | [[package]] 445 | name = "http-body" 446 | version = "0.4.4" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" 449 | dependencies = [ 450 | "bytes", 451 | "http", 452 | "pin-project-lite", 453 | ] 454 | 455 | [[package]] 456 | name = "httparse" 457 | version = "1.5.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 460 | 461 | [[package]] 462 | name = "httpdate" 463 | version = "1.0.2" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 466 | 467 | [[package]] 468 | name = "humantime" 469 | version = "2.1.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 472 | 473 | [[package]] 474 | name = "hyper" 475 | version = "0.14.16" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" 478 | dependencies = [ 479 | "bytes", 480 | "futures-channel", 481 | "futures-core", 482 | "futures-util", 483 | "h2", 484 | "http", 485 | "http-body", 486 | "httparse", 487 | "httpdate", 488 | "itoa 0.4.8", 489 | "pin-project-lite", 490 | "socket2", 491 | "tokio", 492 | "tower-service", 493 | "tracing", 494 | "want", 495 | ] 496 | 497 | [[package]] 498 | name = "hyper-tls" 499 | version = "0.5.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 502 | dependencies = [ 503 | "bytes", 504 | "hyper", 505 | "native-tls", 506 | "tokio", 507 | "tokio-native-tls", 508 | ] 509 | 510 | [[package]] 511 | name = "idna" 512 | version = "0.2.3" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 515 | dependencies = [ 516 | "matches", 517 | "unicode-bidi", 518 | "unicode-normalization", 519 | ] 520 | 521 | [[package]] 522 | name = "indexmap" 523 | version = "1.7.0" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 526 | dependencies = [ 527 | "autocfg", 528 | "hashbrown", 529 | ] 530 | 531 | [[package]] 532 | name = "instant" 533 | version = "0.1.12" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 536 | dependencies = [ 537 | "cfg-if", 538 | ] 539 | 540 | [[package]] 541 | name = "ipnet" 542 | version = "2.3.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" 545 | 546 | [[package]] 547 | name = "itertools" 548 | version = "0.10.3" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 551 | dependencies = [ 552 | "either", 553 | ] 554 | 555 | [[package]] 556 | name = "itoa" 557 | version = "0.4.8" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 560 | 561 | [[package]] 562 | name = "itoa" 563 | version = "1.0.1" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 566 | 567 | [[package]] 568 | name = "js-sys" 569 | version = "0.3.55" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 572 | dependencies = [ 573 | "wasm-bindgen", 574 | ] 575 | 576 | [[package]] 577 | name = "lazy_static" 578 | version = "1.4.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 581 | 582 | [[package]] 583 | name = "libc" 584 | version = "0.2.112" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 587 | 588 | [[package]] 589 | name = "lock_api" 590 | version = "0.4.5" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 593 | dependencies = [ 594 | "scopeguard", 595 | ] 596 | 597 | [[package]] 598 | name = "log" 599 | version = "0.4.14" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 602 | dependencies = [ 603 | "cfg-if", 604 | ] 605 | 606 | [[package]] 607 | name = "matches" 608 | version = "0.1.9" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 611 | 612 | [[package]] 613 | name = "memchr" 614 | version = "2.4.1" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 617 | 618 | [[package]] 619 | name = "mime" 620 | version = "0.3.16" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 623 | 624 | [[package]] 625 | name = "mio" 626 | version = "0.7.14" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 629 | dependencies = [ 630 | "libc", 631 | "log", 632 | "miow", 633 | "ntapi", 634 | "winapi", 635 | ] 636 | 637 | [[package]] 638 | name = "miow" 639 | version = "0.3.7" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 642 | dependencies = [ 643 | "winapi", 644 | ] 645 | 646 | [[package]] 647 | name = "native-tls" 648 | version = "0.2.8" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" 651 | dependencies = [ 652 | "lazy_static", 653 | "libc", 654 | "log", 655 | "openssl", 656 | "openssl-probe", 657 | "openssl-sys", 658 | "schannel", 659 | "security-framework", 660 | "security-framework-sys", 661 | "tempfile", 662 | ] 663 | 664 | [[package]] 665 | name = "ntapi" 666 | version = "0.3.6" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 669 | dependencies = [ 670 | "winapi", 671 | ] 672 | 673 | [[package]] 674 | name = "num-integer" 675 | version = "0.1.44" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 678 | dependencies = [ 679 | "autocfg", 680 | "num-traits", 681 | ] 682 | 683 | [[package]] 684 | name = "num-traits" 685 | version = "0.2.14" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 688 | dependencies = [ 689 | "autocfg", 690 | ] 691 | 692 | [[package]] 693 | name = "num_cpus" 694 | version = "1.13.1" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 697 | dependencies = [ 698 | "hermit-abi", 699 | "libc", 700 | ] 701 | 702 | [[package]] 703 | name = "numtoa" 704 | version = "0.1.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 707 | 708 | [[package]] 709 | name = "once_cell" 710 | version = "1.9.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 713 | 714 | [[package]] 715 | name = "opaque-debug" 716 | version = "0.3.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 719 | 720 | [[package]] 721 | name = "openssl" 722 | version = "0.10.38" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" 725 | dependencies = [ 726 | "bitflags", 727 | "cfg-if", 728 | "foreign-types", 729 | "libc", 730 | "once_cell", 731 | "openssl-sys", 732 | ] 733 | 734 | [[package]] 735 | name = "openssl-probe" 736 | version = "0.1.4" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" 739 | 740 | [[package]] 741 | name = "openssl-sys" 742 | version = "0.9.72" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" 745 | dependencies = [ 746 | "autocfg", 747 | "cc", 748 | "libc", 749 | "pkg-config", 750 | "vcpkg", 751 | ] 752 | 753 | [[package]] 754 | name = "parking_lot" 755 | version = "0.11.2" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 758 | dependencies = [ 759 | "instant", 760 | "lock_api", 761 | "parking_lot_core", 762 | ] 763 | 764 | [[package]] 765 | name = "parking_lot_core" 766 | version = "0.8.5" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 769 | dependencies = [ 770 | "cfg-if", 771 | "instant", 772 | "libc", 773 | "redox_syscall", 774 | "smallvec", 775 | "winapi", 776 | ] 777 | 778 | [[package]] 779 | name = "percent-encoding" 780 | version = "2.1.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 783 | 784 | [[package]] 785 | name = "pin-project-lite" 786 | version = "0.2.7" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 789 | 790 | [[package]] 791 | name = "pin-utils" 792 | version = "0.1.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 795 | 796 | [[package]] 797 | name = "pkg-config" 798 | version = "0.3.24" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 801 | 802 | [[package]] 803 | name = "ppv-lite86" 804 | version = "0.2.15" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 807 | 808 | [[package]] 809 | name = "proc-macro-error" 810 | version = "1.0.4" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 813 | dependencies = [ 814 | "proc-macro-error-attr", 815 | "proc-macro2", 816 | "quote", 817 | "syn", 818 | "version_check", 819 | ] 820 | 821 | [[package]] 822 | name = "proc-macro-error-attr" 823 | version = "1.0.4" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 826 | dependencies = [ 827 | "proc-macro2", 828 | "quote", 829 | "version_check", 830 | ] 831 | 832 | [[package]] 833 | name = "proc-macro2" 834 | version = "1.0.34" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" 837 | dependencies = [ 838 | "unicode-xid", 839 | ] 840 | 841 | [[package]] 842 | name = "quote" 843 | version = "1.0.10" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 846 | dependencies = [ 847 | "proc-macro2", 848 | ] 849 | 850 | [[package]] 851 | name = "rand" 852 | version = "0.8.4" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 855 | dependencies = [ 856 | "libc", 857 | "rand_chacha", 858 | "rand_core", 859 | "rand_hc", 860 | ] 861 | 862 | [[package]] 863 | name = "rand_chacha" 864 | version = "0.3.1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 867 | dependencies = [ 868 | "ppv-lite86", 869 | "rand_core", 870 | ] 871 | 872 | [[package]] 873 | name = "rand_core" 874 | version = "0.6.3" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 877 | dependencies = [ 878 | "getrandom", 879 | ] 880 | 881 | [[package]] 882 | name = "rand_hc" 883 | version = "0.3.1" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 886 | dependencies = [ 887 | "rand_core", 888 | ] 889 | 890 | [[package]] 891 | name = "redox_syscall" 892 | version = "0.2.10" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 895 | dependencies = [ 896 | "bitflags", 897 | ] 898 | 899 | [[package]] 900 | name = "redox_termios" 901 | version = "0.1.2" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" 904 | dependencies = [ 905 | "redox_syscall", 906 | ] 907 | 908 | [[package]] 909 | name = "regex" 910 | version = "1.5.4" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 913 | dependencies = [ 914 | "aho-corasick", 915 | "memchr", 916 | "regex-syntax", 917 | ] 918 | 919 | [[package]] 920 | name = "regex-syntax" 921 | version = "0.6.25" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 924 | 925 | [[package]] 926 | name = "remove_dir_all" 927 | version = "0.5.3" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 930 | dependencies = [ 931 | "winapi", 932 | ] 933 | 934 | [[package]] 935 | name = "reqwest" 936 | version = "0.11.8" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" 939 | dependencies = [ 940 | "base64", 941 | "bytes", 942 | "encoding_rs", 943 | "futures-core", 944 | "futures-util", 945 | "http", 946 | "http-body", 947 | "hyper", 948 | "hyper-tls", 949 | "ipnet", 950 | "js-sys", 951 | "lazy_static", 952 | "log", 953 | "mime", 954 | "native-tls", 955 | "percent-encoding", 956 | "pin-project-lite", 957 | "serde", 958 | "serde_json", 959 | "serde_urlencoded", 960 | "tokio", 961 | "tokio-native-tls", 962 | "url", 963 | "wasm-bindgen", 964 | "wasm-bindgen-futures", 965 | "web-sys", 966 | "winreg", 967 | ] 968 | 969 | [[package]] 970 | name = "ryu" 971 | version = "1.0.9" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 974 | 975 | [[package]] 976 | name = "schannel" 977 | version = "0.1.19" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 980 | dependencies = [ 981 | "lazy_static", 982 | "winapi", 983 | ] 984 | 985 | [[package]] 986 | name = "scopeguard" 987 | version = "1.1.0" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 990 | 991 | [[package]] 992 | name = "security-framework" 993 | version = "2.4.2" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" 996 | dependencies = [ 997 | "bitflags", 998 | "core-foundation", 999 | "core-foundation-sys", 1000 | "libc", 1001 | "security-framework-sys", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "security-framework-sys" 1006 | version = "2.4.2" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" 1009 | dependencies = [ 1010 | "core-foundation-sys", 1011 | "libc", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "serde" 1016 | version = "1.0.132" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" 1019 | dependencies = [ 1020 | "serde_derive", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "serde_derive" 1025 | version = "1.0.132" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" 1028 | dependencies = [ 1029 | "proc-macro2", 1030 | "quote", 1031 | "syn", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "serde_json" 1036 | version = "1.0.73" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" 1039 | dependencies = [ 1040 | "itoa 1.0.1", 1041 | "ryu", 1042 | "serde", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "serde_urlencoded" 1047 | version = "0.7.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1050 | dependencies = [ 1051 | "form_urlencoded", 1052 | "itoa 0.4.8", 1053 | "ryu", 1054 | "serde", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "sha-1" 1059 | version = "0.9.8" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" 1062 | dependencies = [ 1063 | "block-buffer", 1064 | "cfg-if", 1065 | "cpufeatures", 1066 | "digest", 1067 | "opaque-debug", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "signal-hook-registry" 1072 | version = "1.4.0" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1075 | dependencies = [ 1076 | "libc", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "slab" 1081 | version = "0.4.5" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1084 | 1085 | [[package]] 1086 | name = "smallvec" 1087 | version = "1.7.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 1090 | 1091 | [[package]] 1092 | name = "socket2" 1093 | version = "0.4.2" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" 1096 | dependencies = [ 1097 | "libc", 1098 | "winapi", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "strsim" 1103 | version = "0.8.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1106 | 1107 | [[package]] 1108 | name = "structopt" 1109 | version = "0.3.25" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 1112 | dependencies = [ 1113 | "clap", 1114 | "lazy_static", 1115 | "structopt-derive", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "structopt-derive" 1120 | version = "0.4.18" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 1123 | dependencies = [ 1124 | "heck", 1125 | "proc-macro-error", 1126 | "proc-macro2", 1127 | "quote", 1128 | "syn", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "syn" 1133 | version = "1.0.82" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 1136 | dependencies = [ 1137 | "proc-macro2", 1138 | "quote", 1139 | "unicode-xid", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "tempfile" 1144 | version = "3.2.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 1147 | dependencies = [ 1148 | "cfg-if", 1149 | "libc", 1150 | "rand", 1151 | "redox_syscall", 1152 | "remove_dir_all", 1153 | "winapi", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "termcolor" 1158 | version = "1.1.2" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1161 | dependencies = [ 1162 | "winapi-util", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "termion" 1167 | version = "1.5.6" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" 1170 | dependencies = [ 1171 | "libc", 1172 | "numtoa", 1173 | "redox_syscall", 1174 | "redox_termios", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "textwrap" 1179 | version = "0.11.0" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1182 | dependencies = [ 1183 | "unicode-width", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "thiserror" 1188 | version = "1.0.30" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1191 | dependencies = [ 1192 | "thiserror-impl", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "thiserror-impl" 1197 | version = "1.0.30" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1200 | dependencies = [ 1201 | "proc-macro2", 1202 | "quote", 1203 | "syn", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "time" 1208 | version = "0.1.44" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1211 | dependencies = [ 1212 | "libc", 1213 | "wasi", 1214 | "winapi", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "tinyvec" 1219 | version = "1.5.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 1222 | dependencies = [ 1223 | "tinyvec_macros", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "tinyvec_macros" 1228 | version = "0.1.0" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1231 | 1232 | [[package]] 1233 | name = "tokio" 1234 | version = "1.15.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" 1237 | dependencies = [ 1238 | "bytes", 1239 | "libc", 1240 | "memchr", 1241 | "mio", 1242 | "num_cpus", 1243 | "once_cell", 1244 | "parking_lot", 1245 | "pin-project-lite", 1246 | "signal-hook-registry", 1247 | "tokio-macros", 1248 | "winapi", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "tokio-macros" 1253 | version = "1.7.0" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 1256 | dependencies = [ 1257 | "proc-macro2", 1258 | "quote", 1259 | "syn", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "tokio-native-tls" 1264 | version = "0.3.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1267 | dependencies = [ 1268 | "native-tls", 1269 | "tokio", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "tokio-stream" 1274 | version = "0.1.8" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" 1277 | dependencies = [ 1278 | "futures-core", 1279 | "pin-project-lite", 1280 | "tokio", 1281 | ] 1282 | 1283 | [[package]] 1284 | name = "tokio-util" 1285 | version = "0.6.9" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 1288 | dependencies = [ 1289 | "bytes", 1290 | "futures-core", 1291 | "futures-sink", 1292 | "log", 1293 | "pin-project-lite", 1294 | "tokio", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "tower-service" 1299 | version = "0.3.1" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1302 | 1303 | [[package]] 1304 | name = "tracing" 1305 | version = "0.1.29" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" 1308 | dependencies = [ 1309 | "cfg-if", 1310 | "pin-project-lite", 1311 | "tracing-core", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "tracing-core" 1316 | version = "0.1.21" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" 1319 | dependencies = [ 1320 | "lazy_static", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "try-lock" 1325 | version = "0.2.3" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1328 | 1329 | [[package]] 1330 | name = "tui" 1331 | version = "0.16.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" 1334 | dependencies = [ 1335 | "bitflags", 1336 | "cassowary", 1337 | "termion", 1338 | "unicode-segmentation", 1339 | "unicode-width", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "tungstenite" 1344 | version = "0.16.0" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" 1347 | dependencies = [ 1348 | "base64", 1349 | "byteorder", 1350 | "bytes", 1351 | "http", 1352 | "httparse", 1353 | "log", 1354 | "native-tls", 1355 | "rand", 1356 | "sha-1", 1357 | "thiserror", 1358 | "url", 1359 | "utf-8", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "twitch-irc" 1364 | version = "3.0.1" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "e54cf6932e6dbe8f0af0ece6eeaed9a57af1bff66fceedf42cd78dec834b16d6" 1367 | dependencies = [ 1368 | "async-trait", 1369 | "bytes", 1370 | "chrono", 1371 | "enum_dispatch", 1372 | "futures-util", 1373 | "itertools", 1374 | "log", 1375 | "smallvec", 1376 | "thiserror", 1377 | "tokio", 1378 | "tokio-native-tls", 1379 | "tokio-stream", 1380 | "tokio-util", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "typenum" 1385 | version = "1.14.0" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 1388 | 1389 | [[package]] 1390 | name = "unicode-bidi" 1391 | version = "0.3.7" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 1394 | 1395 | [[package]] 1396 | name = "unicode-normalization" 1397 | version = "0.1.19" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1400 | dependencies = [ 1401 | "tinyvec", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "unicode-segmentation" 1406 | version = "1.8.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 1409 | 1410 | [[package]] 1411 | name = "unicode-width" 1412 | version = "0.1.9" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1415 | 1416 | [[package]] 1417 | name = "unicode-xid" 1418 | version = "0.2.2" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1421 | 1422 | [[package]] 1423 | name = "url" 1424 | version = "2.2.2" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1427 | dependencies = [ 1428 | "form_urlencoded", 1429 | "idna", 1430 | "matches", 1431 | "percent-encoding", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "utf-8" 1436 | version = "0.7.6" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1439 | 1440 | [[package]] 1441 | name = "vcpkg" 1442 | version = "0.2.15" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1445 | 1446 | [[package]] 1447 | name = "vec_map" 1448 | version = "0.8.2" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1451 | 1452 | [[package]] 1453 | name = "version_check" 1454 | version = "0.9.3" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1457 | 1458 | [[package]] 1459 | name = "want" 1460 | version = "0.3.0" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1463 | dependencies = [ 1464 | "log", 1465 | "try-lock", 1466 | ] 1467 | 1468 | [[package]] 1469 | name = "wasi" 1470 | version = "0.10.0+wasi-snapshot-preview1" 1471 | source = "registry+https://github.com/rust-lang/crates.io-index" 1472 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1473 | 1474 | [[package]] 1475 | name = "wasm-bindgen" 1476 | version = "0.2.78" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 1479 | dependencies = [ 1480 | "cfg-if", 1481 | "wasm-bindgen-macro", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "wasm-bindgen-backend" 1486 | version = "0.2.78" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 1489 | dependencies = [ 1490 | "bumpalo", 1491 | "lazy_static", 1492 | "log", 1493 | "proc-macro2", 1494 | "quote", 1495 | "syn", 1496 | "wasm-bindgen-shared", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "wasm-bindgen-futures" 1501 | version = "0.4.28" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" 1504 | dependencies = [ 1505 | "cfg-if", 1506 | "js-sys", 1507 | "wasm-bindgen", 1508 | "web-sys", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "wasm-bindgen-macro" 1513 | version = "0.2.78" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 1516 | dependencies = [ 1517 | "quote", 1518 | "wasm-bindgen-macro-support", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "wasm-bindgen-macro-support" 1523 | version = "0.2.78" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 1526 | dependencies = [ 1527 | "proc-macro2", 1528 | "quote", 1529 | "syn", 1530 | "wasm-bindgen-backend", 1531 | "wasm-bindgen-shared", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "wasm-bindgen-shared" 1536 | version = "0.2.78" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 1539 | 1540 | [[package]] 1541 | name = "web-sys" 1542 | version = "0.3.55" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 1545 | dependencies = [ 1546 | "js-sys", 1547 | "wasm-bindgen", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "winapi" 1552 | version = "0.3.9" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1555 | dependencies = [ 1556 | "winapi-i686-pc-windows-gnu", 1557 | "winapi-x86_64-pc-windows-gnu", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "winapi-i686-pc-windows-gnu" 1562 | version = "0.4.0" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1565 | 1566 | [[package]] 1567 | name = "winapi-util" 1568 | version = "0.1.5" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1571 | dependencies = [ 1572 | "winapi", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "winapi-x86_64-pc-windows-gnu" 1577 | version = "0.4.0" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1580 | 1581 | [[package]] 1582 | name = "winreg" 1583 | version = "0.7.0" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1586 | dependencies = [ 1587 | "winapi", 1588 | ] 1589 | --------------------------------------------------------------------------------