Main mastodon logic, reorganization of the global app to reflect the fact that a subcommand is possible

This commit is contained in:
VC
2020-03-01 13:44:32 +01:00
parent 3641bf61bb
commit 18655f1e68
2 changed files with 122 additions and 31 deletions

View File

@@ -1,12 +1,11 @@
// std // std
use std::{ use std::{
borrow::Cow,
io,
fs::{read_to_string, write}, fs::{read_to_string, write},
error::Error, error::Error,
}; };
// clap
use clap::{App, Arg};
// toml // toml
use serde::Deserialize; use serde::Deserialize;
@@ -21,10 +20,20 @@ use egg_mode::{
}; };
use tokio::runtime::current_thread::block_on_all; 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 * 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<u64> { fn read_state(s: &str) -> Option<u64> {
let state = read_to_string(s); let state = read_to_string(s);
@@ -35,10 +44,12 @@ fn read_state(s: &str) -> Option<u64> {
None None
} }
/// Write last treated tweet id to a file
fn write_state(f: &str, s: u64) -> Result<(), std::io::Error> { fn write_state(f: &str, s: u64) -> Result<(), std::io::Error> {
write(f, format!("{}", s)) write(f, format!("{}", s))
} }
/// Get twitter oauth2 token
fn get_oauth2_token(config: &Config) -> 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 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)); 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<u64>) -> Result<Vec<Tweet>, Box<dyn Error>> { fn get_user_timeline(config: &Config, token: Token, lid: Option<u64>) -> Result<Vec<Tweet>, Box<dyn Error>> {
// fix the page size to 200 as it is the maximum Twitter authorizes // 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) 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<u64>) -> Result<
Ok(feed.to_vec()) 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 * Config structure
***********/ ***********/
@@ -66,6 +93,7 @@ fn get_user_timeline(config: &Config, token: Token, lid: Option<u64>) -> Result<
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Config {
twitter: TwitterConfig, twitter: TwitterConfig,
mastodon: MastodonConfig,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -78,22 +106,21 @@ struct TwitterConfig {
last_tweet_path: String, last_tweet_path: String,
} }
impl Config { #[derive(Debug, Deserialize)]
/// parses configuration from command line and TOML config file struct MastodonConfig {
pub fn new() -> Config { base: String,
let matches = App::new(env!("CARGO_PKG_NAME")) client_id: String,
.version(env!("CARGO_PKG_VERSION")) client_secret: String,
.about("A Twitter to Mastodon bot") redirect: String,
.arg(Arg::with_name("config") token: String,
.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"); /*********
* 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| let toml_config = read_to_string(toml_file).unwrap_or_else(|e|
panic!("Cannot open config file {}: {}", toml_file, e) panic!("Cannot open config file {}: {}", toml_file, e)
); );
@@ -103,12 +130,36 @@ impl Config {
); );
config config
}
} }
/********* /// Generic register function
* Main 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 /// This is where the magic happens
pub fn run(config: Config) { pub fn run(config: Config) {
@@ -118,6 +169,15 @@ pub fn run(config: Config) {
// get OAuth2 token // get OAuth2 token
let token = get_oauth2_token(&config); 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<tweet>) // get user timeline feed (Vec<tweet>)
let feed = get_user_timeline(&config, token, last_tweet_id).unwrap_or_else(|e| 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) panic!("Something went wrong when trying to retrieve {}s timeline: {}", &config.twitter.username, e)

View File

@@ -1,7 +1,38 @@
// self
use scootaloo::*; use scootaloo::*;
// clap
use clap::{App, Arg, SubCommand};
fn main() { 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); run(config);
} }