├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── poc.mp4 └── src ├── main.rs └── vibrator.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | /target 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-buttplug" 3 | version = "0.1.0" 4 | edition = "2021" 5 | repository = "https://github.com/vmfunc/cargo-buttplug" 6 | description = "A cargo subcommand for controlling buttplug devices" 7 | license = "MIT" 8 | exclude = [ 9 | "assets/*", 10 | ] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | anyhow = "1.0.83" 16 | async-std = "1.12.0" 17 | buttplug = "7.1.16" 18 | futures = "0.3.30" 19 | tokio = "1.37.0" 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 vmfunc 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cargo-buttplug 2 | ![Crates.io Version](https://img.shields.io/crates/v/cargo-buttplug) 3 | 4 | ensuring positive reinforcement during long, tiring code sessions~ 5 | 6 | ## Preview 7 | 8 | https://github.com/vmfunc/cargo-buttplug/assets/59031302/78939b9c-ad3c-4226-9ffd-6982c5a19ab8 9 | 10 | ## Installation 11 | 12 | You will need a local buttplug.io server set up. The defaults for cargo-buttplug are websocket addresses from [Intiface Central](https://intiface.com/central/). We recommend setting this up. 13 | 14 | To install cargo-buttplug, just run 15 | 16 | ```zsh 17 | cargo install cargo-buttplug 18 | ``` 19 | 20 | ## Usage 21 | 22 | Simply add buttplug after each cargo command. 23 | 24 | ```bash 25 | cargo buttplug build 26 | ``` 27 | 28 | ## Credits 29 | 30 | Written by [vmfunc](https://github.com/vmfunc) and [kenneth](https://github.com/kennethnym) because it was a boring saturday 31 | -------------------------------------------------------------------------------- /assets/poc.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmfunc/cargo-buttplug/03653eb3810a4e4cdde1b5b30851574cd6c13b1f/assets/poc.mp4 -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod vibrator; 2 | 3 | #[tokio::main(flavor = "current_thread")] 4 | async fn main() { 5 | // get all args 6 | let args: Vec = std::env::args().collect(); 7 | println!("args: {:?}", args); 8 | 9 | // run cargo with args and interate over the exit code 10 | let code = std::process::Command::new("cargo") 11 | .args(&args[2..]) 12 | .status() 13 | .expect("failed to execute process") //yeah ik 14 | .code() 15 | .unwrap(); 16 | 17 | 18 | if code == 0 { 19 | println!("you deserve a reward~ 💖"); 20 | vibrator::vibrate().await.unwrap(); 21 | } else { 22 | println!("do better next time~~"); 23 | } 24 | 25 | // exit 26 | std::process::exit(code); 27 | } -------------------------------------------------------------------------------- /src/vibrator.rs: -------------------------------------------------------------------------------- 1 | use async_std::io; 2 | use std::sync::{Arc, Mutex}; 3 | use std::{thread, time::Duration}; 4 | use buttplug::{client::{device::ScalarValueCommand, ButtplugClientEvent, ButtplugClient}, util::in_process_client, core::connector::new_json_ws_client_connector}; 5 | use futures::StreamExt; 6 | use tokio::io::{AsyncBufReadExt, BufReader}; 7 | 8 | async fn wait_for_input() { 9 | let stdin = io::stdin(); 10 | let mut line = String::new(); 11 | stdin.read_line(&mut line).await; 12 | } 13 | 14 | pub async fn vibrate() -> anyhow::Result<()> { 15 | let connector = new_json_ws_client_connector("ws://127.0.0.1:12345/buttplug"); 16 | let client = ButtplugClient::new("cargo-buttplug"); 17 | client.connect(connector).await?; 18 | let mut events = client.event_stream(); 19 | 20 | tokio::spawn(async move { 21 | while let Some(event) = events.next().await { 22 | match event { 23 | ButtplugClientEvent::DeviceAdded(device) => { 24 | println!("device {} connected", device.name()); 25 | } 26 | ButtplugClientEvent::DeviceRemoved(info) => { 27 | println!("device {} removed", info.name()); 28 | } 29 | ButtplugClientEvent::ScanningFinished => { 30 | println!("device scanning is finished!"); 31 | } 32 | _ => {} 33 | } 34 | } 35 | }); 36 | 37 | client.start_scanning().await?; 38 | 39 | client.stop_scanning().await?; 40 | thread::sleep(Duration::from_secs(3)); 41 | // maybe make it wait a bit to scan or smth 42 | 43 | // if no devices are connected, we can't do anything gg 44 | if client.devices().is_empty() { 45 | println!("No devices connected, exiting"); 46 | return Ok(()); 47 | } 48 | 49 | println!("Vibrating:"); 50 | for device in client.devices() { 51 | println!("- {}", device.name()); 52 | } 53 | 54 | let stop = Arc::new(Mutex::new(false)); 55 | let clone: Arc> = Arc::clone(&stop); 56 | 57 | thread::spawn(move || { 58 | thread::sleep(Duration::from_secs(10)); 59 | let mut s = clone.lock().unwrap(); 60 | *s = true; 61 | }); 62 | 63 | // we will have flying cars in 2024 64 | // 2024: 65 | // HHAHHAAH MULTITHREADED BUTTPLUGGING 66 | 67 | let test_device = &client.devices()[0]; 68 | loop { // this needs to be 10s only 69 | test_device 70 | .vibrate(&ScalarValueCommand::ScalarValue(1.0)) 71 | .await?; 72 | let should_stop = stop.lock().unwrap(); 73 | if *should_stop { 74 | break; 75 | } 76 | } 77 | 78 | client.disconnect().await?; 79 | 80 | Ok(()) 81 | } --------------------------------------------------------------------------------