diff --git a/src/lib.rs b/src/lib.rs index ec9be53..6923346 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,11 @@ // std use std::{ + borrow::Cow, + io, fs::{read_to_string, write}, error::Error, }; -// clap -use clap::{App, Arg}; - // toml use serde::Deserialize; @@ -21,10 +20,20 @@ use egg_mode::{ }; use tokio::runtime::current_thread::block_on_all; +// mammut +use mammut::{Mastodon, Data, Registration}; +use mammut::apps::{AppBuilder, Scopes}; +use mammut::status_builder::StatusBuilder; + /********** * Generic usage functions ***********/ + +/* + * Those functions are related to the Twitter side of things + */ +/// Read last tweet id from a file fn read_state(s: &str) -> Option { let state = read_to_string(s); @@ -35,10 +44,12 @@ fn read_state(s: &str) -> Option { None } +/// Write last treated tweet id to a file fn write_state(f: &str, s: u64) -> Result<(), std::io::Error> { write(f, format!("{}", s)) } +/// Get twitter oauth2 token fn get_oauth2_token(config: &Config) -> Token { let con_token = KeyPair::new(String::from(&config.twitter.consumer_key), String::from(&config.twitter.consumer_secret)); let access_token = KeyPair::new(String::from(&config.twitter.access_key), String::from(&config.twitter.access_secret)); @@ -49,6 +60,7 @@ fn get_oauth2_token(config: &Config) -> Token { } } +/// Get twitter user timeline fn get_user_timeline(config: &Config, token: Token, lid: Option) -> Result, Box> { // fix the page size to 200 as it is the maximum Twitter authorizes let (_timeline, feed) = block_on_all(user_timeline(&config.twitter.username, false, false, &token) @@ -58,6 +70,21 @@ fn get_user_timeline(config: &Config, token: Token, lid: Option) -> Result< Ok(feed.to_vec()) } +/* + * Those functions are related to the Mastodon side of things + */ +/// Get Mastodon Data +fn get_mastodon_token(masto: &MastodonConfig) -> Mastodon { + let data = Data { + base: Cow::from(String::from(&masto.base)), + client_id: Cow::from(String::from(&masto.client_id)), + client_secret: Cow::from(String::from(&masto.client_secret)), + redirect: Cow::from(String::from(&masto.redirect)), + token: Cow::from(String::from(&masto.token)), + }; + + Mastodon::from_data(data) +} /********** * Config structure ***********/ @@ -66,6 +93,7 @@ fn get_user_timeline(config: &Config, token: Token, lid: Option) -> Result< #[derive(Debug, Deserialize)] pub struct Config { twitter: TwitterConfig, + mastodon: MastodonConfig, } #[derive(Debug, Deserialize)] @@ -78,38 +106,61 @@ struct TwitterConfig { last_tweet_path: String, } -impl Config { - /// parses configuration from command line and TOML config file - pub fn new() -> Config { - let matches = App::new(env!("CARGO_PKG_NAME")) - .version(env!("CARGO_PKG_VERSION")) - .about("A Twitter to Mastodon bot") - .arg(Arg::with_name("config") - .short("c") - .long("config") - .value_name("CONFIG_FILE") - .help("TOML config file for scootaloo (default /usr/local/etc/scootaloo.toml)") - .takes_value(true) - .display_order(1)) - .get_matches(); - - let toml_file = matches.value_of("config").unwrap_or("/usr/local/etc/scootaloo.toml"); - let toml_config = read_to_string(toml_file).unwrap_or_else(|e| - panic!("Cannot open config file {}: {}", toml_file, e) - ); - - let config: Config = toml::from_str(&toml_config).unwrap_or_else(|e| - panic!("Cannot parse TOML file {}: {}", toml_file, e) - ); - - config - } +#[derive(Debug, Deserialize)] +struct MastodonConfig { + base: String, + client_id: String, + client_secret: String, + redirect: String, + token: String, } /********* - * Main function + * Main functions *********/ +/// Parses the TOML file into a Config Struct +pub fn parse_toml(toml_file: &str) -> Config { + let toml_config = read_to_string(toml_file).unwrap_or_else(|e| + panic!("Cannot open config file {}: {}", toml_file, e) + ); + + let config: Config = toml::from_str(&toml_config).unwrap_or_else(|e| + panic!("Cannot parse TOML file {}: {}", toml_file, e) + ); + + config +} + +/// Generic register function +/// As this function is supposed to be run only once, it will panic for every error it encounters +/// Most of this function is a direct copy/paste of the official `mammut` crate +pub fn register(host: &str) { + let app = AppBuilder { + client_name: env!("CARGO_PKG_NAME"), + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: Scopes::Write, + website: Some("https://framagit.org/veretcle/scootaloo"), + }; + + let mut registration = Registration::new(host); + registration.register(app).expect("Registration failed!"); + let url = registration.authorise().expect("Cannot generate registration URI!"); + + println!("Click this link to authorize on Mastodon: {}", url); + println!("Paste the returned authorization code: "); + + let mut input = String::new(); + io::stdin().read_line(&mut input).expect("Unable to read back registration code!"); + + let code = input.trim(); + let mastodon = registration.create_access_token(code.to_string()).expect("Unable to create access token!"); + + let toml = toml::to_string(&*mastodon).unwrap(); + + println!("Please insert the following block at the end of your configuration file:\n[mastodon]\n{}", toml); +} + /// This is where the magic happens pub fn run(config: Config) { // retrieve the last tweet ID for the username @@ -118,6 +169,15 @@ pub fn run(config: Config) { // get OAuth2 token let token = get_oauth2_token(&config); + // get Mastodon instance + let mastodon = get_mastodon_token(&config.mastodon); + + let status = StatusBuilder::new("Hello World!".into()); + + mastodon.new_status(status).expect("tamerelol"); + + return; + // get user timeline feed (Vec) let feed = get_user_timeline(&config, token, last_tweet_id).unwrap_or_else(|e| panic!("Something went wrong when trying to retrieve {}’s timeline: {}", &config.twitter.username, e) diff --git a/src/main.rs b/src/main.rs index ddab3ea..ca75cb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,38 @@ +// self use scootaloo::*; +// clap +use clap::{App, Arg, SubCommand}; + fn main() { - let config = Config::new(); + let matches = App::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .about("A Twitter to Mastodon bot") + .arg(Arg::with_name("config") + .short("c") + .long("config") + .value_name("CONFIG_FILE") + .help("TOML config file for scootaloo (default /usr/local/etc/scootaloo.toml)") + .takes_value(true) + .display_order(1)) + .subcommand(SubCommand::with_name("register") + .version(env!("CARGO_PKG_VERSION")) + .about("Command to register to a Mastodon Instance") + .arg(Arg::with_name("host") + .short("h") + .long("host") + .value_name("HOST") + .help("Base URL of the Mastodon instance to register to (no default)") + .takes_value(true) + .required(true) + .display_order(1))) + .get_matches(); + if let Some(matches) = matches.subcommand_matches("register") { + register(matches.value_of("host").unwrap()); + return; + } + + let config = parse_toml(matches.value_of("config").unwrap()); run(config); }