├── .gitignore ├── img └── logo.png ├── snapcraft.yaml ├── Cargo.toml ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ ├── rust.yml │ └── publish.yml ├── LICENSE ├── src ├── handler.rs ├── port.rs ├── input.rs ├── visual │ └── chicken.txt ├── main.rs └── output.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpacehuhnTech/Huhnitor/HEAD/img/logo.png -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: huhnitor 2 | version: '2.0.0' 3 | summary: An intergalactic serial monitor for the ESP8266 Deauther v3 4 | description: | 5 | The Huhnitor is designed to be as easy to use as possible: 6 | - Open huhnitor 7 | - Plug in your deauther 8 | - Have fun using the command line interface of the ESP8266 Deauther 9 | 10 | confinement: devmode 11 | base: core18 12 | 13 | parts: 14 | huhnitor: 15 | plugin: rust 16 | source: https://github.com/SpacehuhnTech/Huhnitor.git 17 | build-packages: 18 | - libudev-dev 19 | - pkg-config 20 | 21 | apps: 22 | huhnitor: 23 | command: bin/huhnitor 24 | 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "huhnitor" 3 | version = "2.0.0" 4 | authors = ["James M "] 5 | description = "Intergalactic serial monitor" 6 | readme = "README.md" 7 | documentation = "https://github.com/SpacehuhnTech/Huhnitor" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | tokio = { version = "0.2.21", features = [ "full" ] } 12 | tokio-util = { version = "0.3.1", features = [ "codec" ] } 13 | tokio-serial = "4.3.3" 14 | 15 | serialport = "3.3.0" 16 | futures = "0.3.5" 17 | bytes = "0.5.4" 18 | webbrowser = "0.5.2" 19 | lazy_static = "1.4.0" 20 | structopt = "0.3.15" 21 | 22 | regex = "1.3.9" 23 | termcolor = "1.1" 24 | rustyline = "6.3.0" 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: spacehuhntech 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: spacehuhn 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - bug 10 | - feature request 11 | - ready for review 12 | # Label to use when marking an issue as stale 13 | staleLabel: stale 14 | # Comment to post when marking an issue as stale. Set to `false` to disable 15 | markComment: > 16 | This issue has been automatically marked as stale because it has not had 17 | recent activity. It will be closed if no further activity occurs. Thank you 18 | for your contributions. 19 | # Comment to post when closing a stale issue. Set to `false` to disable 20 | closeComment: false 21 | only: issues 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jamz in cooperation with Spacehuhn Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Rust 4 | 5 | jobs: 6 | 7 | check: 8 | name: Check 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions-rs/toolchain@v1 13 | with: 14 | profile: minimal 15 | toolchain: stable 16 | override: true 17 | - name: Update APT 18 | run: sudo apt-get update 19 | - name: Install libdev 20 | run: sudo apt-get install -y libudev-dev 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | clippy: 26 | name: Clippy 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: stable 34 | override: true 35 | - name: Update APT 36 | run: sudo apt-get update 37 | - name: Install libdev 38 | run: sudo apt-get install -y libudev-dev 39 | - run: rustup component add clippy 40 | - uses: actions-rs/cargo@v1 41 | with: 42 | command: clippy 43 | #args: -- -D warnings -------------------------------------------------------------------------------- /src/handler.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::prelude::*; 3 | 4 | use crate::error; 5 | 6 | pub fn handle(command: String) -> String { 7 | let words = command.split(' ').collect::>(); 8 | let len = words.len(); 9 | if let Some(key) = words.get(1) { 10 | match key.to_uppercase().trim().as_ref() { 11 | "READ" => { 12 | if len > 2 { 13 | let mut out = String::new(); 14 | if let Ok(mut file) = File::open(words[2].trim()) { 15 | if file.read_to_string(&mut out).is_err() { 16 | error!(format!("Coudldn't read file: '{}'", words[1])); 17 | } 18 | } else { 19 | error!(format!("Couldn't open file: '{}'", words[2].trim())); 20 | } 21 | 22 | if !out.ends_with('\n') { out += "\n" } 23 | return out; 24 | } else { 25 | println!("Insufficient arguments"); 26 | println!("Command format: read [filename]"); 27 | } 28 | } 29 | 30 | _ => error!("Command not found"), 31 | } 32 | } 33 | 34 | String::new() 35 | } 36 | -------------------------------------------------------------------------------- /src/port.rs: -------------------------------------------------------------------------------- 1 | use serialport::{available_ports, SerialPortInfo}; 2 | use tokio::sync::mpsc::UnboundedReceiver; 3 | 4 | use crate::input; 5 | use crate::output; 6 | 7 | async fn detect_port(ports: &mut Vec) -> Option { 8 | loop { 9 | tokio::time::delay_for(std::time::Duration::from_millis(500)).await; 10 | 11 | if let Ok(new_ports) = available_ports() { 12 | for path in &new_ports { 13 | if !ports.contains(&path) { 14 | return Some(path.port_name.clone()); 15 | } 16 | } 17 | 18 | *ports = new_ports; 19 | } 20 | } 21 | } 22 | 23 | fn manual_port(port: String, ports: &mut Vec) -> Option { 24 | if port.to_lowercase().contains("dev/") || port.to_lowercase().contains("com") { 25 | Some(port) 26 | } else { 27 | let index = port.parse().ok()?; 28 | 29 | if index < ports.len() { 30 | Some(ports.remove(index).port_name) 31 | } else { 32 | None 33 | } 34 | } 35 | } 36 | 37 | pub async fn manual( 38 | receiver: &mut UnboundedReceiver, 39 | out: &output::Preferences, 40 | ) -> Option { 41 | let mut ports = available_ports().ok()?; 42 | 43 | out.ports(&ports); 44 | 45 | let port = input::read_line(receiver).await?; 46 | 47 | manual_port(port, &mut ports) 48 | } 49 | 50 | pub async fn auto( 51 | receiver: &mut UnboundedReceiver, 52 | out: &output::Preferences, 53 | ) -> Option { 54 | let mut ports = available_ports().ok()?; 55 | 56 | out.ports(&ports); 57 | out.println("> Plug your deauther in, or type the port ID or name"); 58 | 59 | tokio::select! { 60 | port = detect_port(&mut ports) => port, 61 | 62 | Some(port) = input::read_line(receiver) => { 63 | manual_port(port, &mut ports) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; 2 | use std::collections::VecDeque; 3 | use std::time::{Instant, Duration}; 4 | 5 | use crate::error; 6 | 7 | pub fn receiver(sender: UnboundedSender) { 8 | let mut exitspam: VecDeque = VecDeque::with_capacity(3); 9 | 10 | let mut rl = rustyline::Editor::<()>::new(); 11 | rl.bind_sequence(rustyline::KeyPress::Up, rustyline::Cmd::LineUpOrPreviousHistory(1)); 12 | rl.bind_sequence(rustyline::KeyPress::Down, rustyline::Cmd::LineDownOrNextHistory(1)); 13 | 14 | loop { 15 | match rl.readline(">> ") { 16 | Ok(line) => { 17 | rl.add_history_entry(&line); 18 | if sender.send(format!("{}\r\n", line.clone())).is_err() { 19 | error!("Couldn't report input to main thread!"); 20 | } 21 | 22 | if line.trim().to_uppercase() == "EXIT" { 23 | break; 24 | } 25 | }, 26 | Err(rustyline::error::ReadlineError::Interrupted) => { 27 | sender.send("stop\n".to_string()).expect("Couldn't stop!"); 28 | 29 | if exitspam.len() == 3 { 30 | if let Some(time) = exitspam.pop_back() { 31 | if Instant::now() - time <= Duration::new(3, 0) { 32 | sender.send("EXIT".to_string()).expect("Couldn't exit!"); 33 | break; 34 | } else { 35 | exitspam.push_front(Instant::now()); 36 | } 37 | } 38 | } else { 39 | exitspam.push_front(Instant::now()); 40 | } 41 | } 42 | Err(e) => error!(e) 43 | 44 | } 45 | } 46 | } 47 | 48 | pub async fn read_line(receiver: &mut UnboundedReceiver) -> Option { 49 | Some(receiver.recv().await?.trim().to_string()) 50 | } 51 | -------------------------------------------------------------------------------- /src/visual/chicken.txt: -------------------------------------------------------------------------------- 1 | @@@@@@@ 2 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3 | @@@@@@@@@ @@@@@@@@@ 4 | @@@@@@& @@@@@@@ 5 | @@@@@/ @@@@@@@@@@ *@@@ @@@@@@ 6 | @@@@@ .@@@@@@@# @@@@@@@@@@@ @@@ /@@@@@ 7 | @@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@, @@@@@ 8 | @@@@# .@@@@@@@@@@@@@@@@@@@@@ *@@@ @@@@@ 9 | @@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@ 10 | @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@# @@ #@@@% 11 | @@@@ @@@@@@@@@@@@@@@ @@@ @@@ @@@@ 12 | @@@@ @@@@@@@@@ @@@@@ @@& @@ @@@@ 13 | *@@@ @@@@@ @@@@ @@@@ @@@@@@/ @@ @@@ 14 | @@@@ @@ @@ @# *@@ @@ @@ @ @@@@ 15 | @@@ @@ @@ @@@@@@ @@ @@@@@ *@& @ @@@ 16 | @@@@ @@ @@ @@@@ @@ @@@ @@@ @@ @ @@@@ 17 | @@@@ @@ @@ %@/ @@ @@@@@, @@ ,@@ @@@@ 18 | @@@@ @@ @@@ @@@.@@ @@@@@ @@@@ 19 | @@@ @& @@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@ 20 | @@@@ @ (@ @@ (@@@& @@@@ 21 | @@@ @@ @@ @@ @@@@ @@ @@@ 22 | @@@@ @@# @@ @@@@@@@ @@ @@@@ 23 | @@@@ @@@ @@ @@@@@@@@@ @@ @@@@ 24 | @@@@ @@/ @@ @@@@@@@@@@@ @@ @@@% 25 | @@@@ @& @@@@@@@@@@@ @@ @@@@ 26 | @@@@@ @@ @@@@@@@@@ @@ @@@@@ 27 | @@@@@ @, *@ @@@@@ 28 | @@@@@@@@@@@@@@ @ /@@@@@ 29 | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @& @@@@@@ 30 | .@ @@@ @@ @, @@@@@@@@@@@@@@@@@@@@@@@ 31 | @@ @@@ @@@@ @@@& @@@@@@ 32 | @@@@@@@@@@@@@@@, @@ *@ @@@ @@ 33 | *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @@ 34 | @@@@@@@@@@@@@@@@@@@ 35 | @@@@@@ -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use handler::handle; 2 | use serialport::prelude::*; 3 | use std::env; 4 | use std::time::Duration; 5 | use structopt::StructOpt; 6 | use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; 7 | 8 | #[macro_use] 9 | mod handler; 10 | mod input; 11 | mod output; 12 | mod port; 13 | 14 | async fn monitor(cmd_port: Option, auto: bool, no_welcome: bool, out: &output::Preferences) { 15 | let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); 16 | 17 | std::thread::spawn(|| input::receiver(sender)); 18 | 19 | let settings = tokio_serial::SerialPortSettings { 20 | baud_rate: 115200, 21 | data_bits: DataBits::Eight, 22 | flow_control: FlowControl::None, 23 | parity: Parity::None, 24 | stop_bits: StopBits::One, 25 | timeout: Duration::from_secs(10), 26 | }; 27 | 28 | let tty_path = if cmd_port.is_some() { 29 | cmd_port 30 | } else if auto { 31 | port::auto(&mut receiver, out).await 32 | } else { 33 | port::manual(&mut receiver, out).await 34 | }; 35 | 36 | if let Some(inner_tty_path) = tty_path { 37 | #[allow(unused_mut)] // Ignore warning from windows compilers 38 | if let Ok(mut port) = tokio_serial::Serial::from_path(&inner_tty_path, &settings) { 39 | #[cfg(unix)] 40 | port.set_exclusive(false) 41 | .expect("Unable to set serial port exclusive to false"); 42 | 43 | let mut port = BufReader::new(port); 44 | 45 | out.connected(&inner_tty_path); 46 | 47 | if !no_welcome { 48 | if let Err(_) = port.write("welcome\r\n".as_bytes()).await { 49 | out.print("Couldn't send welcome command!"); 50 | } 51 | } 52 | 53 | let mut buf = Vec::new(); 54 | loop { 55 | tokio::select! { 56 | len = port.read_until(b'\n', &mut buf) => match len { 57 | Ok(0) => { // EOF 58 | break; 59 | }, 60 | Ok(_) => { 61 | let input = String::from_utf8_lossy(&buf).to_string(); 62 | out.print(&input); 63 | buf = Vec::new(); 64 | }, 65 | Err(e) => { 66 | error!(e); 67 | break; 68 | } 69 | }, 70 | 71 | Some(text) = receiver.recv() => { 72 | if text.trim().to_uppercase() == "EXIT" { 73 | break; 74 | } else if text.trim().to_uppercase() == "CLEAR" { 75 | output::clear(); 76 | } else if text.to_uppercase().starts_with("HUHN") { 77 | if port.write(handle(text).as_bytes()).await.is_err() { 78 | error!("Command failed"); 79 | } 80 | } else if port.write(text.as_bytes()).await.is_err() { 81 | error!("Couldn't send message"); 82 | } 83 | } 84 | } 85 | } 86 | } else { 87 | // Port creation handler 88 | error!("Couldn't create port object!"); 89 | } 90 | } else { 91 | // Path handler 92 | out.hint(); 93 | } 94 | } 95 | 96 | #[derive(StructOpt)] 97 | #[structopt(name = "Huhnitor", about = env!("CARGO_PKG_DESCRIPTION"))] 98 | struct Opt { 99 | /// Open driver page 100 | #[structopt(short, long)] 101 | driver: bool, 102 | 103 | /// Disable automatic port connection 104 | #[structopt(short = "a", long = "no-auto")] 105 | auto: bool, 106 | 107 | /// Disable colored output 108 | #[structopt(short = "c", long = "no-color")] 109 | color: bool, 110 | 111 | /// Select port 112 | #[structopt(short, long)] 113 | port: Option, 114 | 115 | /// Disable welcome command 116 | #[structopt(short = "w", long = "no-welcome")] 117 | no_welcome: bool, 118 | } 119 | 120 | #[tokio::main] 121 | async fn main() { 122 | let args = Opt::from_args(); 123 | 124 | let out = output::Preferences { 125 | color_enabled: !args.color, 126 | }; 127 | 128 | out.logo(); 129 | out.version(); 130 | 131 | if args.driver { 132 | out.driver(); 133 | } else { 134 | monitor(args.port, !args.auto, args.no_welcome, &out).await; 135 | } 136 | 137 | out.goodbye(); 138 | } 139 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish binaries to release page 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | env: 8 | NAME: huhnitor 9 | FILE: huhnitor 10 | HOMEBREW: spacehuhntech/homebrew-huhnitor 11 | REPOSITORY: spacehuhntech/Huhnitor 12 | 13 | jobs: 14 | 15 | version-check: 16 | name: "Check version number" 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - name: Read version numbers 22 | run: | 23 | echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 24 | echo "SNAP_VERSION=$(grep -E "version: '[0-9]?.[0-9]?.[0-9]?'$" snapcraft.yaml | grep -oE "[0-9]?\.[0-9]?\.[0-9]?")" >> $GITHUB_ENV 25 | echo "APP_VERSION=$(grep -E "version = \"[0-9]?.[0-9]?.[0-9]?\"$" Cargo.toml | grep -oE "[0-9]?\.[0-9]?\.[0-9]?")" >> $GITHUB_ENV 26 | 27 | - name: Cancel build 28 | if: env.RELEASE_VERSION != env.APP_VERSION || env.RELEASE_VERSION != env.SNAP_VERSION 29 | uses: andymckay/cancel-action@0.2 30 | 31 | - name: Fail job 32 | if: env.RELEASE_VERSION != env.APP_VERSION || env.RELEASE_VERSION != env.SNAP_VERSION 33 | run: | 34 | echo "Release tag: ${{ env.RELEASE_VERSION }}" 35 | echo "Cargo.toml: ${{ env.APP_VERSION }}" 36 | echo "snapcraft.yaml: ${{ env.SNAP_VERSION }}" 37 | exit 1 38 | 39 | publish-linux: 40 | name: "Publish Linux binary" 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v1 44 | 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: stable 49 | 50 | - name: Read version number 51 | run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 52 | 53 | - name: Update APT 54 | run: sudo apt-get update 55 | - name: Install libdev 56 | run: sudo apt-get install -y libudev-dev 57 | 58 | - name: Build 59 | run: cargo build --release 60 | 61 | - name: Upload binaries to release 62 | uses: svenstaro/upload-release-action@v1-release 63 | with: 64 | repo_token: ${{ secrets.GITHUB_TOKEN }} 65 | file: target/release/${{ env.NAME }} 66 | asset_name: ${{ env.FILE }}_${{ env.VERSION }}_linux 67 | tag: ${{ github.ref }} 68 | 69 | publish-mac: 70 | name: "Publish MacOS binary" 71 | runs-on: macos-latest 72 | steps: 73 | - uses: actions/checkout@v1 74 | 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | profile: minimal 78 | toolchain: stable 79 | 80 | - name: Build 81 | run: cargo build --release 82 | 83 | - name: Read version number 84 | run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 85 | 86 | - name: Archive 87 | run: | 88 | cd target/release 89 | tar -czf ${{ env.NAME }}.tar.gz ${{ env.NAME }} 90 | 91 | - name: Get the hash 92 | run: echo "CHECKSUM=$(shasum -a 256 target/release/${{ env.NAME }}.tar.gz | cut -d ' ' -f 1)" >> $GITHUB_ENV 93 | 94 | - name: Upload binaries to release 95 | uses: svenstaro/upload-release-action@v1-release 96 | with: 97 | repo_token: ${{ secrets.GITHUB_TOKEN }} 98 | file: target/release/${{ env.NAME }}.tar.gz 99 | asset_name: ${{ env.FILE }}_${{ env.VERSION }}_mac.tar.gz 100 | tag: ${{ github.ref }} 101 | 102 | - name: Notify homebrew 103 | run: | 104 | curl -X POST https://api.github.com/repos/${{ env.HOMEBREW }}/dispatches \ 105 | -H 'Accept: application/vnd.github.everest-preview+json' \ 106 | -u ${{ secrets.ACCESS_TOKEN }} \ 107 | --data '{"event_type": "release", "client_payload": { "version": "${{ env.VERSION }}", "hash": "${{ env.CHECKSUM }}", "url": "https://github.com/${{ env.REPOSITORY }}/releases/download/${{ env.VERSION }}/${{ env.FILE }}_${{ env.VERSION }}_mac.tar.gz"}}' 108 | 109 | publish-win: 110 | name: "Publish Windows binary" 111 | runs-on: windows-latest 112 | steps: 113 | - uses: actions/checkout@v1 114 | 115 | - uses: actions-rs/toolchain@v1 116 | with: 117 | profile: minimal 118 | toolchain: stable 119 | 120 | - name: Read version number 121 | uses: olegtarasov/get-tag@v2.1 122 | - name: Build 123 | run: cargo build --release 124 | 125 | - name: Upload binaries to release 126 | uses: svenstaro/upload-release-action@v1-release 127 | with: 128 | repo_token: ${{ secrets.GITHUB_TOKEN }} 129 | file: target/release/${{ env.NAME }}.exe 130 | asset_name: ${{ env.FILE }}_${{ env.GIT_TAG_NAME }}_windows.exe 131 | tag: ${{ github.ref }} 132 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | use regex::RegexSet; 2 | use std::io::{self, Write}; 3 | use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; 4 | 5 | #[macro_export] 6 | macro_rules! error { 7 | ($expression:expr) => { 8 | eprintln!("[Error] {}", $expression) 9 | }; 10 | } 11 | 12 | // Statically compile regex to avoid repetetive compiling 13 | // Rust Regex can be tested here: https://rustexp.lpil.uk/ 14 | lazy_static::lazy_static! { 15 | static ref REGSET: RegexSet = RegexSet::new(&[ 16 | r"^(\x60|\.|:|/|-|\+|o|s|h|d|y| ){50,}", // ASCII Chicken 17 | r"^# ", // # command 18 | r"(?m)^\s*(-|=|#)+\s*$", // ================ 19 | r"^\[ =+ ?.* ?=+ \]", // [ ===== Headline ====== ] 20 | r"^> \w+", // > Finished job 21 | r"^(ERROR)|(WARNING): ", // ERROR: something went wrong :( 22 | r"^.*: +.*", // -arg: value 23 | r"^\[.*\]", // [default=something] 24 | r"(?m)^\S+( \[?-\S*( <\S*>)?\]?)*\s*$", // command [-arg ] [-flag] 25 | ]).unwrap(); 26 | 27 | static ref COLORSET: Vec<(Color, bool)> = vec![ 28 | (Color::White, false), // # command 29 | (Color::White, true), // # command 30 | (Color::Blue, false), // ================ 31 | (Color::Yellow, true), // [ ===== Headline ====== ] 32 | (Color::Cyan, false), // > Finished job 33 | (Color::Red, false), // ERROR: something went wrong :( 34 | (Color::Green, false), // -arg value 35 | (Color::Green, true), // [default=something] 36 | (Color::Yellow, false), // command [-arg ] [-flag] 37 | ]; 38 | } 39 | 40 | fn parse(s: &str) { 41 | let matches: Vec<_> = REGSET.matches(s).into_iter().collect(); 42 | 43 | let (color, bold) = if !matches.is_empty() { 44 | COLORSET[matches[0]] 45 | } else { 46 | (Color::White, false) 47 | }; 48 | 49 | fn print_color(input: &str, color: Color, bold: bool) -> io::Result<()> { 50 | let mut stdout = StandardStream::stdout(ColorChoice::Always); 51 | 52 | stdout.set_color( 53 | ColorSpec::new() 54 | .set_fg(Some(color)) 55 | //.set_bg(Some(Color::Black)) 56 | .set_bold(bold), 57 | )?; 58 | 59 | write!(&mut stdout, "{}", input)?; 60 | stdout.reset() 61 | } 62 | 63 | if let Err(e) = print_color(s, color, bold) { 64 | error!(e); 65 | } 66 | } 67 | 68 | pub fn clear() { 69 | print!("{esc}[2J{esc}[1;1H", esc = 27 as char); 70 | println!(" "); 71 | } 72 | 73 | pub struct Preferences { 74 | pub color_enabled: bool, 75 | } 76 | 77 | impl Preferences { 78 | pub fn print(&self, s: &str) { 79 | if self.color_enabled { 80 | parse(&s); 81 | } else { 82 | print!("{}", s); 83 | } 84 | } 85 | 86 | pub fn println(&self, s: &str) { 87 | self.print(s); 88 | println!(); 89 | } 90 | 91 | pub fn logo(&self) { 92 | let c_bytes = include_bytes!("visual/chicken.txt"); 93 | let logo_str = String::from_utf8_lossy(c_bytes).to_string(); 94 | println!("{}", logo_str); 95 | } 96 | 97 | pub fn version(&self) { 98 | let version = format!(" Huhnitor Version {} ", env!("CARGO_PKG_VERSION")); 99 | let headline = format!("[ {:=^76} ]", version); 100 | self.println(&headline); 101 | } 102 | 103 | pub fn divider(&self) { 104 | let divider = format!("[ {:=^76} ]", '='); 105 | self.println(÷r); 106 | } 107 | 108 | pub fn ports(&self, ports: &[serialport::SerialPortInfo]) { 109 | if ports.is_empty() { 110 | self.hint(); 111 | } else { 112 | self.println("Available serial ports:"); 113 | for (id, port) in ports.iter().enumerate() { 114 | let port = format!("[{}] {}", id, port.port_name); 115 | self.println(&port); 116 | } 117 | } 118 | } 119 | 120 | pub fn hint(&self) { 121 | self.println("> No serial port found"); 122 | self.println("Make sure the USB connection works and necessary drivers are installed:"); 123 | self.println("https://github.com/SpacehuhnTech/Huhnitor#drivers"); 124 | } 125 | 126 | pub fn connected(&self, port: &str) { 127 | let msg = format!("Connected to {} \\o/", port); 128 | 129 | self.println(&msg); 130 | self.divider(); 131 | } 132 | 133 | pub fn driver(&self) { 134 | self.print("Opening \"https://github.com/spacehuhntech/huhnitor#drivers\"..."); 135 | 136 | if webbrowser::open("https://github.com/spacehuhntech/huhnitor#drivers").is_err() { 137 | self.println("Couldn't open URL :("); 138 | } else { 139 | self.println("OK") 140 | } 141 | } 142 | 143 | pub fn goodbye(&self) { 144 | let bye = format!("[ {:=^76} ]", " Thanks for using Huhnitor "); 145 | self.println(&bye); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Huhnitor 2 | 3 |

4 | Huhnitor Logo 5 |
6 | An intergalactic serial monitor for the ESP8266 Deauther v3 7 |
8 | Rust badge 9 |

10 | 11 | ## New New New ✨ 12 | Check out [serial.huhn.me](https://serial.huhn.me) for a web based serial monitor. 13 | A great alternative that works with all Arduino projects and doesn't need to be installed. 14 | You just need a compatible browser, like Chrome for desktop. 15 | 16 | But no worries, it doesn't replace this project. Huhnitor remains to be our terminal based serial monitor! 17 | 18 | ## Disclaimer 19 | 20 | **Please note** that while this software can be used for other serial devices and projects, it is designed to be used with the 21 | [ESP8266 Deauther Version 3](https://github.com/SpacehuhnTech/esp8266_deauther/tree/v3). 22 | 23 | ## Installation 24 | 25 | **Youtube Tutorial:** 26 | [![Huhnitor Installation Video Tutorial](https://img.youtube.com/vi/tSH_wjFreHQ/0.jpg)](https://www.youtube.com/watch?v=tSH_wjFreHQ&t=95s) 27 | 28 | ### Using released binary (Recommended for Windows) 29 | 30 | 1. Go to the [release page](https://github.com/SpacehuhnTech/Huhnitor/releases) and download a binary for your OS from the latest release. 31 | 2. Run it by simply double clicking it or via terminal `./huhnitor` or `sudo ./huhnitor` 32 | **Linux & Mac** users will have to make the binary executable first by running `sudo chmod +x huhnitor` 33 | 3. [Optional] Add it to the `PATH` variable for easy use in the terminal 34 | 35 | ### Using Snap (Recommended for Linux) 36 | 37 | 1. [Install snap](https://snapcraft.io/docs/installing-snapd) if it doesn't already come with your Linux distribution. 38 | 2. Open a terminal and type 39 | `sudo snap install huhnitor --edge --devmode` 40 | 3. To start simply run `sudo huhnitor` in a terminal 41 | 42 | If you get a `huhnitor not found` message, try adding snap to the PATH by running `export PATH="$PATH:/snap/bin"`. 43 | 44 | ### Using AUR on Arch Linux 45 | 46 | 1. Open a terminal and type 47 | `yay -S huhnitor` 48 | 2. To start simply run `sudo huhnitor` in a terminal 49 | 50 | ### Using Homebrew (Recommended for macOS) 51 | 52 | 1. Make sure [Homebrew](https://brew.sh/) is installed 53 | 2. Open a terminal and type 54 | `brew tap spacehuhntech/huhnitor` 55 | `brew install huhnitor` 56 | or as a one-liner: `brew tap spacehuhntech/huhnitor && brew install huhnitor` 57 | 3. To start simply run `huhnitor` in a terminal 58 | 59 | **Pro tip**: Homebrew can also be installed on Linux, and the Windows Subsystem for Linux. 60 | 61 | ### Compiling it yourself 62 | 63 | Precompiled binaries can be found at [releases](https://github.com/SpacehuhnTech/Huhnitor/releases). 64 | But if you want, you can compile the Huhnitor yourself: 65 | 66 | 1. Install Rust using [rustup](https://www.rust-lang.org/tools/install) 67 | **Linux users** will also need to run `sudo apt install libudev-dev pkg-config` 68 | **Windows users** have to install [Visual C++ Build Tools 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) 69 | 2. [Download and unzip](https://github.com/SpacehuhnTech/Huhnitor/archive/master.zip) or `git clone https://github.com/SpacehuhnTech/Huhnitor.git` this repository 70 | 3. In the root directory of the repo run `cargo build --release`, your binary will be located in `target/release/` (you can use `cargo run` to start Huhnitor without compiling a binary) 71 | 72 | ## Usage 73 | 74 | The Huhnitor is designed to be as easy to use as possible: 75 | 76 | 1. Open huhnitor 77 | 2. Plug in your deauther 78 | 3. Have fun using the command line interface of the [ESP8266 Deauther](https://github.com/SpacehuhnTech/esp8266_deauther) :slightly_smiling_face: 79 | 80 | If the huhnitor has issues connecting to your deauther, try running it as administrator or via `sudo huhnitor`. 81 | You can also give a user permission to access the serial ports by running `sudo usermod -a -G dialout `. 82 | 83 | The Huhnitor can run scripts (a series of pre-written commands) if you enter `huhn read [filename]` once you are connected to a deauther. The file paths are relative to your current command line location (not the executable's) and are essentially a series of newline separated deauther commands. 84 | 85 | To stop running a command on the deauther, you can hit ctrl + c, which is in theory more convenient and should help to prevent accidental disconnects. This does, however, also mean that you cannot exit the Huhnitor with ctrl + c, therefore once a serial connection has been opened, entering `exit` must be used to exit the Huhnitor. 86 | 87 | ### Arguments 88 | 89 | | Argument | Description | 90 | | ----------------------- | ------------------------------ | 91 | | `--help` or `-h` | print this help screen | 92 | | `--port` or `-p` | enter port as argument | 93 | | `--driver` or `-d` | open driver page | 94 | | `--no-auto` or `-a` | disable automatic port connect | 95 | | `--no-color` or `-c` | disable colored output | 96 | | `--no-welcome` or `-w` | disable welcome message | 97 | 98 | ## Drivers 99 | 100 | Your deauther is not detected when plugged in? 101 | **Make sure the USB connection is working. Some cables can only charge but not transmit data.** 102 | Depending on the serial chip that is used on the ESP8266 development board you have, you might need to install the right driver: 103 | 104 | * [CP210x](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers) 105 | * [CH341](http://www.wch-ic.com/search?q=cH341&t=downloads) 106 | * [FTDI](https://www.ftdichip.com/FTDrivers.htm) 107 | 108 | Not sure which one to install? A lot of ESP8266 based development boards use a chip from the CP210x family, try starting there. 109 | 110 | ## Credits 111 | 112 | Made with :heart: by [Jamz](https://github.com/the-Jamz) with help from [Selicre](https://selic.re)
113 | in cooperation with [Spacehuhn Technologies](https://github.com/SpacehuhnTech/) 114 | 115 | ## License 116 | 117 | This software is licensed under the MIT License. See the [license file](LICENSE) for details. 118 | --------------------------------------------------------------------------------