Skip to content

Instantly share code, notes, and snippets.

@NuroDev
Created January 18, 2020 21:13
Show Gist options
  • Save NuroDev/bfaa560bd53ce5164de3818f853966f6 to your computer and use it in GitHub Desktop.
Save NuroDev/bfaa560bd53ce5164de3818f853966f6 to your computer and use it in GitHub Desktop.
๐Ÿ–ผ Cosmo - A simple twitter bot to upload/download an image repeatedly. Used to analyze Twitter image compression results
[package]
name = "cosmo"
version = "1.1.1"
authors = ["N U R O โ„ข"]
edition = "2018"
[dependencies]
egg-mode = "0.13.0"
failure = "0.1.6"
reqwest = "0.9.22"
tokio = "0.1.22"
use egg_mode::{
media::{media_types, MediaHandle, UploadBuilder},
tweet::{delete, DraftTweet, Tweet},
KeyPair, Response, Token,
};
use failure::{err_msg, Error};
use std::{
ffi::OsStr,
fs::{copy as FsCopy, create_dir_all, File},
io::{copy as IoCopy, Read},
path::{Path, PathBuf},
thread::sleep,
time::{Duration, Instant},
};
use structopt::StructOpt;
use tokio::runtime::current_thread::block_on_all;
const CONSUMER_KEY: &str = "";
const CONSUMER_SECRET: &str = "";
const ACCESS_KEY: &str = "";
const ACCESS_SECRET: &str = "";
const OUTPUT_DIR: &str = "./dist";
#[derive(Debug, StructOpt)]
#[structopt(
name = "cosmo",
about = "A simple twitter bot to upload/download an image repeatedly. Used to analyze Twitter image compression results"
)]
struct CLI {
/// The path to the entry image
#[structopt(name = "image_path", parse(from_os_str))]
image_path: PathBuf,
/// Number of recursions (Times to upload/download the image)
#[structopt(name = "recursions")]
recursions: u64,
}
fn upload_media(path: &Path, token: &Token) -> Result<MediaHandle, Error> {
// Get the image file extension
let file_extension = match path
.extension()
.and_then(OsStr::to_str)
.expect("โ›” Error parsing file extension to string")
{
"jpg" => media_types::image_jpg(),
"png" => media_types::image_png(),
"gif" => media_types::image_gif(),
"webp" => media_types::image_webp(),
_ => panic!("โ›” Invalid file type submitted"),
};
// Load image into buffer
let mut buffer: Vec<u8> = Vec::new();
let mut _file: usize = File::open(path)?.read_to_end(&mut buffer)?;
// Create new UploadBuilder instance
let builder: UploadBuilder = UploadBuilder::new(buffer, file_extension);
// Upload the image
match block_on_all(builder.call(&token)) {
Ok(media) => {
println!("โฌ†๏ธ Uploaded media");
Ok(media)
}
Err(err) => panic!("โ›” Error uploading media: {:#?}", err),
}
}
fn send_tweet(
media_id: u64,
token: &Token,
file_name: &str,
i: u64,
) -> Result<Response<Tweet>, Error> {
let draft: DraftTweet =
DraftTweet::new(format!("๐Ÿ“ธ Image: {}\nโžฟ Loop: {}", file_name, i)).media_ids(&[media_id]);
return match block_on_all(draft.send(&token)) {
Ok(tweet) => {
println!("โœˆ๏ธ Tweet sent");
Ok(tweet)
}
Err(e) => {
println!("โ›” Error tweeting iter {}: {:#?}", i, e);
// Check if it is because we hit the daily API limit (Code 185)
// If so, wait 24 hours and retry, if not, return an error
match &e {
egg_mode::error::Error::TwitterError(err) => {
if err.errors[0].code == 185 {
println!("โš ๏ธ API Limit reached\n ๐Ÿ›๏ธ Sleeping for 24 hours");
sleep(Duration::new(60 * 60 * 24, 0));
Ok(send_tweet(media_id, token, file_name, i)?)
} else {
Err(err_msg(e))
}
}
_ => Err(err_msg(e)),
}
}
};
}
fn download_media(
tweet: &Response<Tweet>,
file_name: &str,
file_extension: &str,
i: u64,
) -> Result<(), Error> {
let mut response = reqwest::get(
&tweet
.response
.entities
.media
.clone()
.expect("โ›” No media url found")[0]
.media_url_https,
)?;
let mut output_image = File::create(&format!(
"{}/{}_{}.{}",
OUTPUT_DIR,
file_name,
i + 1,
file_extension
))?;
match IoCopy(&mut response, &mut output_image) {
Ok(_) => Ok(println!("โฌ‡๏ธ Downloaded media")),
Err(e) => Err(err_msg(format!("โ›” Error downloading image: {:#?}", e))),
}
}
fn main() -> Result<(), Error> {
// Get CLI arguments
let cli = CLI::from_args();
println!("============================================");
// Generate twitter access token using credentials provided above
let token: Token = Token::Access {
consumer: KeyPair::new(CONSUMER_KEY, CONSUMER_SECRET),
access: KeyPair::new(ACCESS_KEY, ACCESS_SECRET),
};
let file_name: &str = &cli
.image_path
.file_stem()
.and_then(OsStr::to_str)
.expect("โ›” Error unwrapping image name");
let file_extension: &str = &cli
.image_path
.extension()
.and_then(OsStr::to_str)
.expect("โ›” Error unwrapping image extension");
// Create the output directory
create_dir_all(OUTPUT_DIR).expect("โ›”๏ธ Error creating output directory");
println!("โœจ Created output directory");
// Copy the entry image to the output directory
let entry_image_path = format!("{}/{}_0.{}", OUTPUT_DIR, file_name, file_extension);
match FsCopy(&cli.image_path, entry_image_path) {
Ok(_) => println!("โžก๏ธ Copied entry image"),
Err(err) => panic!("โ›” Error copying entry image: {:#?}", err),
};
// Start the stopwatch
let timer = Instant::now();
println!("============================================");
for i in 0..cli.recursions {
println!("โžฟ Loop: {}", i);
// Upload the image and get back the media id
let media: MediaHandle = upload_media(
Path::new(&format!(
"{}/{}_{}.{}",
OUTPUT_DIR, file_name, i, file_extension
)),
&token,
)?;
// Send the tweet
let tweet: Response<Tweet> = send_tweet(media.id, &token, &file_name, i)?;
// Download the uploaded image
download_media(&tweet, &file_name, &file_extension, i)?;
// Delete tweet after the media has been downloaded
match block_on_all(delete(tweet.id, &token)) {
Ok(_) => println!("๐Ÿ—‘๏ธ Deleted tweet"),
Err(err) => panic!("โ›” Error deleting tweet {}: {:#?}", i, err),
};
println!("โœ… Done!");
println!("============================================");
}
println!("โฐ Time: {} seconds", timer.elapsed().as_secs());
println!("============================================");
Ok(())
}
@NuroDev
Copy link
Author

NuroDev commented Jan 18, 2020

๐Ÿ–ผ Cosmo

A simple twitter bot to upload/download an image repeatedly. Used to analyze Twitter image compression results

๐Ÿš€ Usage

Create a new Rust project named whatever you like. Then copy the code from both the Cargo.toml and main.rs file above and paste into your newly generated projects Cargo.toml and main.rs.
You will then need to enter your Twitter developer API credentials. To do this, open the main.rs file and at the top, add your credentials to the following chunk:

const CONSUMER_KEY: &str = "";
const CONSUMER_SECRET: &str = "";
const ACCESS_KEY: &str = "";
const ACCESS_SECRET: &str = "";

Once you have added your credentials, run the following command with your chosen parameters to run Comso:

cargo run [IMAGE PATH] [RECURSIONS]

For example:

cargo run test.jpg 10

After the application has finished, all screenshots will be available in your project directory in a directory named dist

๐Ÿ”ง Parameters:

  • IMAGE PATH: Path to the entry image that will be uploaded first
  • RECURSIONS: Number of recursions / times to repeatedly upload/download the image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment