feat: add multi-account ability

This commit is contained in:
VC
2022-11-03 16:14:25 +01:00
parent 44ec3edfe2
commit dad49da090
11 changed files with 341 additions and 163 deletions

2
Cargo.lock generated
View File

@@ -2016,7 +2016,7 @@ dependencies = [
[[package]] [[package]]
name = "scootaloo" name = "scootaloo"
version = "0.6.1" version = "0.7.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "scootaloo" name = "scootaloo"
version = "0.6.1" version = "0.7.0"
authors = ["VC <veretcle+framagit@mateu.be>"] authors = ["VC <veretcle+framagit@mateu.be>"]
edition = "2021" edition = "2021"

View File

@@ -10,23 +10,23 @@ If any of the last steps failed, the Toot gets published with the exact same tex
RT are excluded, replies are included when considered part of a thread (reply to self), not the actual replies to other Twitter users. RT are excluded, replies are included when considered part of a thread (reply to self), not the actual replies to other Twitter users.
# Usage # Usage
## Configuring
First up, create a configuration file (default path is `/usr/local/etc/scootaloo.toml`). It will look like this: First up, create a configuration file (default path is `/usr/local/etc/scootaloo.toml`). It will look like this:
```toml ```toml
[scootaloo] [scootaloo]
db_path = "/var/lib/scootaloo/scootaloo.sqlite" ## file containing the SQLite Tweet corresponding Toot DB, must be writeable
db_path="/var/lib/scootaloo/scootaloo.sqlite" ## file containing the SQLite Tweet corresponding Toot DB, must be writeable cache_path = "/tmp/scootaloo" ## a dir where the temporary files will be download, must be writeable
cache_path="/tmp/scootaloo" ## a dir where the temporary files will be download, must be writeable
[twitter] [twitter]
username="NintendojoFR" ## User Timeline to copy
## Consumer/Access key for Twitter (can be generated at https://developer.twitter.com/en/apps) ## Consumer/Access key for Twitter (can be generated at https://developer.twitter.com/en/apps)
consumer_key="MYCONSUMERKEY" consumer_key = "MYCONSUMERKEY"
consumer_secret="MYCONSUMERSECRET" consumer_secret = "MYCONSUMERSECRET"
access_key="MYACCESSKEY" access_key = "MYACCESSKEY"
access_secret="MYACCESSSECRET" access_secret = "MYACCESSSECRET"
[mastodon]
``` ```
Then run the command with the `init` subcommand to initiate the DB: Then run the command with the `init` subcommand to initiate the DB:
@@ -44,7 +44,8 @@ scootaloo register --host https://m.nintendojo.fr
This will give you the end of the TOML file. It will look like this: This will give you the end of the TOML file. It will look like this:
```toml ```toml
[mastodon] [mastodon.nintendojofr] ## account
twitter_screen_name="NintendojoFR" ## User Timeline to copy
base = "https://m.nintendojo.fr" base = "https://m.nintendojo.fr"
client_id = "MYCLIENTID" client_id = "MYCLIENTID"
client_secret = "MYCLIENTSECRET" client_secret = "MYCLIENTSECRET"
@@ -52,6 +53,10 @@ redirect = "urn:ietf:wg:oauth:2.0:oob"
token = "MYTOKEN" token = "MYTOKEN"
``` ```
You can add other account if you like, after the `[mastodon]` moniker. Scootaloo would theorically support an unlimited number of accounts.
## Running
You can then run the application via `cron` for example. Here is the generic usage: You can then run the application via `cron` for example. Here is the generic usage:
```sh ```sh
@@ -71,6 +76,7 @@ OPTIONS:
SUBCOMMANDS: SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s) help Prints this message or the help of the given subcommand(s)
init Command to init Scootaloo DB init Command to init Scootaloo DB
migrate Command to migrate Scootaloo DB
register Command to register to a Mastodon Instance register Command to register to a Mastodon Instance
``` ```
@@ -86,5 +92,17 @@ sqlite3 /var/lib/scootaloo/scootaloo.sqlite
And inserting the data: And inserting the data:
```sql ```sql
INSERT INTO tweet_to_toot VALUES (1383782580412030982, ""); INSERT INTO tweet_to_toot VALUES ("<twitter_screen_name>", 1383782580412030982, "<twitter_screen_name>");
``` ```
The last value is supposed to be the Toot ID. It cannot be null, so you better initialize it with something unique, like the Twitter Screen Name for example.
# Migrating from Scootaloo ⩽ 0.6.1
The DB scheme has change between version 0.6.x and 0.7.x (this is due to the multi-account nature of Scootaloo from 0.7.x onward). You need to migrate your DB. You can do so by issuing the command:
```
scootaloo migrate
```
You can optionnally specify a screen name with the `--name` option. By default, itll take the first screen name in the config file.

View File

@@ -1,17 +1,17 @@
use std::{collections::HashMap, fs::read_to_string};
use serde::Deserialize; use serde::Deserialize;
use std::fs::read_to_string;
/// General configuration Struct /// General configuration Struct
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Config { pub struct Config {
pub twitter: TwitterConfig, pub twitter: TwitterConfig,
pub mastodon: MastodonConfig, pub mastodon: HashMap<String, MastodonConfig>,
pub scootaloo: ScootalooConfig, pub scootaloo: ScootalooConfig,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct TwitterConfig { pub struct TwitterConfig {
pub username: String,
pub consumer_key: String, pub consumer_key: String,
pub consumer_secret: String, pub consumer_secret: String,
pub access_key: String, pub access_key: String,
@@ -20,6 +20,7 @@ pub struct TwitterConfig {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct MastodonConfig { pub struct MastodonConfig {
pub twitter_screen_name: String,
pub base: String, pub base: String,
pub client_id: String, pub client_id: String,
pub client_secret: String, pub client_secret: String,

View File

@@ -15,7 +15,7 @@ use twitter::*;
mod util; mod util;
mod state; mod state;
pub use state::init_db; pub use state::{init_db, migrate_db};
use state::{read_state, write_state, TweetToToot}; use state::{read_state, write_state, TweetToToot};
use elefren::{prelude::*, status_builder::StatusBuilder}; use elefren::{prelude::*, status_builder::StatusBuilder};
@@ -27,6 +27,10 @@ use tokio::fs::remove_file;
/// This is where the magic happens /// This is where the magic happens
#[tokio::main] #[tokio::main]
pub async fn run(config: Config) { pub async fn run(config: Config) {
// get OAuth2 token
let token = get_oauth2_token(&config.twitter);
for mastodon_config in config.mastodon.values() {
// open the SQLite connection // open the SQLite connection
let conn = Connection::open(&config.scootaloo.db_path).unwrap_or_else(|e| { let conn = Connection::open(&config.scootaloo.db_path).unwrap_or_else(|e| {
panic!( panic!(
@@ -35,23 +39,20 @@ pub async fn run(config: Config) {
) )
}); });
// retrieve the last tweet ID for the username // retrieve the last tweet ID for the username
let last_tweet_id = read_state(&conn, None) let last_tweet_id = read_state(&conn, &mastodon_config.twitter_screen_name, None)
.unwrap_or_else(|e| panic!("Cannot retrieve last_tweet_id: {}", e)) .unwrap_or_else(|e| panic!("Cannot retrieve last_tweet_id: {}", e))
.map(|s| s.tweet_id); .map(|s| s.tweet_id);
// get OAuth2 token
let token = get_oauth2_token(&config.twitter);
// get Mastodon instance // get Mastodon instance
let mastodon = get_mastodon_token(&config.mastodon); let mastodon = get_mastodon_token(mastodon_config);
// get user timeline feed (Vec<tweet>) // get user timeline feed (Vec<tweet>)
let mut feed = get_user_timeline(&config.twitter, token, last_tweet_id) let mut feed = get_user_timeline(mastodon_config, &token, last_tweet_id)
.await .await
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
panic!( panic!(
"Something went wrong when trying to retrieve {}s timeline: {}", "Something went wrong when trying to retrieve {}s timeline: {}",
&config.twitter.username, e &mastodon_config.twitter_screen_name, e
) )
}); });
@@ -70,13 +71,17 @@ pub async fn run(config: Config) {
let mut toot_reply_id: Option<String> = None; let mut toot_reply_id: Option<String> = None;
// determine if the tweet is part of a thread (response to self) or a standard response // determine if the tweet is part of a thread (response to self) or a standard response
if let Some(r) = &tweet.in_reply_to_screen_name { if let Some(r) = &tweet.in_reply_to_screen_name {
if r.to_lowercase() != config.twitter.username.to_lowercase() { if r.to_lowercase() != mastodon_config.twitter_screen_name.to_lowercase() {
// we are responding not threading // we are responding not threadin
info!("Tweet is a direct response, skipping"); info!("Tweet is a direct response, skipping");
continue; continue;
} }
info!("Tweet is a thread"); info!("Tweet is a thread");
toot_reply_id = read_state(&conn, tweet.in_reply_to_status_id) toot_reply_id = read_state(
&conn,
&mastodon_config.twitter_screen_name,
tweet.in_reply_to_status_id,
)
.unwrap_or(None) .unwrap_or(None)
.map(|s| s.toot_id); .map(|s| s.toot_id);
}; };
@@ -146,6 +151,7 @@ pub async fn run(config: Config) {
// last_tweet gathered not to be written // last_tweet gathered not to be written
let ttt_towrite = TweetToToot { let ttt_towrite = TweetToToot {
twitter_screen_name: mastodon_config.twitter_screen_name.clone(),
tweet_id: tweet.id, tweet_id: tweet.id,
toot_id: published_status.id, toot_id: published_status.id,
}; };
@@ -154,4 +160,5 @@ pub async fn run(config: Config) {
write_state(&conn, ttt_towrite) write_state(&conn, ttt_towrite)
.unwrap_or_else(|e| panic!("Cant write the last tweet retrieved: {}", e)); .unwrap_or_else(|e| panic!("Cant write the last tweet retrieved: {}", e));
} }
}
} }

View File

@@ -43,7 +43,16 @@ fn main() {
.help("Base URL of the Mastodon instance to register to (no default)") .help("Base URL of the Mastodon instance to register to (no default)")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.display_order(1), .display_order(1)
)
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.help("Twitter Screen Name (like https://twitter.com/screen_name, no default)")
.takes_value(true)
.required(true)
.display_order(2)
), ),
) )
.subcommand( .subcommand(
@@ -63,11 +72,36 @@ fn main() {
.display_order(1), .display_order(1),
), ),
) )
.subcommand(
SubCommand::with_name("migrate")
.version(env!("CARGO_PKG_VERSION"))
.about("Command to migrate Scootaloo DB")
.arg(
Arg::with_name("config")
.short("c")
.long("config")
.value_name("CONFIG_FILE")
.help(&format!("TOML config file for scootaloo (default {})", DEFAULT_CONFIG_PATH))
.takes_value(true)
.display_order(1),
)
.arg(
Arg::with_name("name")
.short("n")
.long("name")
.help("Twitter Screen Name (like https://twitter.com/screen_name, no default)")
.takes_value(true)
.display_order(2)
)
)
.get_matches(); .get_matches();
match matches.subcommand() { match matches.subcommand() {
("register", Some(sub_m)) => { ("register", Some(sub_m)) => {
register(sub_m.value_of("host").unwrap()); register(
sub_m.value_of("host").unwrap(),
sub_m.value_of("name").unwrap(),
);
return; return;
} }
("init", Some(sub_m)) => { ("init", Some(sub_m)) => {
@@ -75,6 +109,17 @@ fn main() {
init_db(&config.scootaloo.db_path).unwrap(); init_db(&config.scootaloo.db_path).unwrap();
return; return;
} }
("migrate", Some(sub_m)) => {
let config = parse_toml(sub_m.value_of("config").unwrap_or(DEFAULT_CONFIG_PATH));
let config_twitter_screen_name =
&config.mastodon.values().next().unwrap().twitter_screen_name;
migrate_db(
&config.scootaloo.db_path,
sub_m.value_of("name").unwrap_or(config_twitter_screen_name),
)
.unwrap();
return;
}
_ => (), _ => (),
} }

View File

@@ -65,7 +65,7 @@ pub fn build_basic_status(tweet: &Tweet) -> String {
/// Generic register function /// Generic register function
/// As this function is supposed to be run only once, it will panic for every error it encounters /// 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 `elefren` crate /// Most of this function is a direct copy/paste of the official `elefren` crate
pub fn register(host: &str) { pub fn register(host: &str, screen_name: &str) {
let mut builder = App::builder(); let mut builder = App::builder();
builder builder
.client_name(Cow::from(env!("CARGO_PKG_NAME").to_string())) .client_name(Cow::from(env!("CARGO_PKG_NAME").to_string()))
@@ -100,7 +100,12 @@ pub fn register(host: &str) {
let toml = toml::to_string(&*mastodon).unwrap(); let toml = toml::to_string(&*mastodon).unwrap();
println!( println!(
"Please insert the following block at the end of your configuration file:\n[mastodon]\n{}", "Please insert the following block at the end of your configuration file:
\n[mastodon.{}]
\ntwitter_screen_name = \"{}\"
\n{}",
screen_name.to_lowercase(),
screen_name,
toml toml
); );
} }

View File

@@ -1,10 +1,13 @@
use log::debug;
use rusqlite::{params, Connection, OptionalExtension};
use std::error::Error; use std::error::Error;
use log::debug;
use rusqlite::{params, Connection, OptionalExtension};
/// Struct for each query line /// Struct for each query line
#[derive(Debug)] #[derive(Debug)]
pub struct TweetToToot { pub struct TweetToToot {
pub twitter_screen_name: String,
pub tweet_id: u64, pub tweet_id: u64,
pub toot_id: String, pub toot_id: String,
} }
@@ -13,12 +16,13 @@ pub struct TweetToToot {
/// if a tweet_id is passed, read this particular tweet from DB /// if a tweet_id is passed, read this particular tweet from DB
pub fn read_state( pub fn read_state(
conn: &Connection, conn: &Connection,
n: &str,
s: Option<u64>, s: Option<u64>,
) -> Result<Option<TweetToToot>, Box<dyn Error>> { ) -> Result<Option<TweetToToot>, Box<dyn Error>> {
debug!("Reading tweet_id {:?}", s); debug!("Reading tweet_id {:?}", s);
let query: String = match s { let query: String = match s {
Some(i) => format!("SELECT * FROM tweet_to_toot WHERE tweet_id = {}", i), Some(i) => format!("SELECT * FROM tweet_to_toot WHERE tweet_id = {} and twitter_screen_name = \"{}\"", i, n),
None => "SELECT * FROM tweet_to_toot ORDER BY tweet_id DESC LIMIT 1".to_string(), None => format!("SELECT * FROM tweet_to_toot WHERE twitter_screen_name = \"{}\" ORDER BY tweet_id DESC LIMIT 1", n),
}; };
let mut stmt = conn.prepare(&query)?; let mut stmt = conn.prepare(&query)?;
@@ -26,8 +30,9 @@ pub fn read_state(
let t = stmt let t = stmt
.query_row([], |row| { .query_row([], |row| {
Ok(TweetToToot { Ok(TweetToToot {
tweet_id: row.get(0)?, twitter_screen_name: row.get("twitter_screen_name")?,
toot_id: row.get(1)?, tweet_id: row.get("tweet_id")?,
toot_id: row.get("toot_id")?,
}) })
}) })
.optional()?; .optional()?;
@@ -39,8 +44,8 @@ pub fn read_state(
pub fn write_state(conn: &Connection, t: TweetToToot) -> Result<(), Box<dyn Error>> { pub fn write_state(conn: &Connection, t: TweetToToot) -> Result<(), Box<dyn Error>> {
debug!("Write struct {:?}", t); debug!("Write struct {:?}", t);
conn.execute( conn.execute(
"INSERT INTO tweet_to_toot (tweet_id, toot_id) VALUES (?1, ?2)", "INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id) VALUES (?1, ?2, ?3)",
params![t.tweet_id, t.toot_id], params![t.twitter_screen_name, t.tweet_id, t.toot_id],
)?; )?;
Ok(()) Ok(())
@@ -53,6 +58,7 @@ pub fn init_db(d: &str) -> Result<(), Box<dyn Error>> {
conn.execute( conn.execute(
"CREATE TABLE IF NOT EXISTS tweet_to_toot ( "CREATE TABLE IF NOT EXISTS tweet_to_toot (
twitter_screen_name TEXT NOT NULL,
tweet_id INTEGER PRIMARY KEY, tweet_id INTEGER PRIMARY KEY,
toot_id TEXT UNIQUE toot_id TEXT UNIQUE
)", )",
@@ -62,6 +68,31 @@ pub fn init_db(d: &str) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
/// Migrate DB from 0.6.x to 0.7.x
pub fn migrate_db(d: &str, s: &str) -> Result<(), Box<dyn Error>> {
debug!("Migrating DB for Scootaloo");
let conn = Connection::open(d)?;
let res = conn.execute(
&format!(
"ALTER TABLE tweet_to_toot
ADD COLUMN twitter_screen_name TEXT NOT NULL
DEFAULT \"{}\"",
s
),
[],
);
match res {
Err(e) => match e.to_string().as_str() {
"duplicate column name: twitter_screen_name" => Ok(()),
_ => Err(Box::new(e)),
},
_ => Ok(()),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -93,9 +124,9 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
conn.execute( conn.execute(
"INSERT INTO tweet_to_toot "INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id)
VALUES VALUES
(100, 'A');", ('tamerelol', 100, 'A');",
[], [],
) )
.unwrap(); .unwrap();
@@ -114,6 +145,7 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
let t_in = TweetToToot { let t_in = TweetToToot {
twitter_screen_name: "tamerelol".to_string(),
tweet_id: 123456789, tweet_id: 123456789,
toot_id: "987654321".to_string(), toot_id: "987654321".to_string(),
}; };
@@ -125,14 +157,16 @@ mod tests {
let t_out = stmt let t_out = stmt
.query_row([], |row| { .query_row([], |row| {
Ok(TweetToToot { Ok(TweetToToot {
tweet_id: row.get(0).unwrap(), twitter_screen_name: row.get("twitter_screen_name").unwrap(),
toot_id: row.get(1).unwrap(), tweet_id: row.get("tweet_id").unwrap(),
toot_id: row.get("toot_id").unwrap(),
}) })
}) })
.unwrap(); .unwrap();
assert_eq!(&t_out.twitter_screen_name, "tamerelol");
assert_eq!(t_out.tweet_id, 123456789); assert_eq!(t_out.tweet_id, 123456789);
assert_eq!(t_out.toot_id, "987654321".to_string()); assert_eq!(&t_out.toot_id, "987654321");
remove_file(d).unwrap(); remove_file(d).unwrap();
} }
@@ -146,15 +180,15 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
conn.execute( conn.execute(
"INSERT INTO tweet_to_toot (tweet_id, toot_id) "INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id)
VALUES VALUES
(101, 'A'), ('tamerelol', 101, 'A'),
(102, 'B');", ('tamerelol', 102, 'B');",
[], [],
) )
.unwrap(); .unwrap();
let t_out = read_state(&conn, None).unwrap().unwrap(); let t_out = read_state(&conn, "tamerelol", None).unwrap().unwrap();
remove_file(d).unwrap(); remove_file(d).unwrap();
@@ -170,7 +204,7 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
let t_out = read_state(&conn, None).unwrap(); let t_out = read_state(&conn, "tamerelol", None).unwrap();
remove_file(d).unwrap(); remove_file(d).unwrap();
@@ -186,14 +220,14 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
conn.execute( conn.execute(
"INSERT INTO tweet_to_toot (tweet_id, toot_id) "INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id)
VALUES VALUES
(100, 'A');", ('tamerelol', 100, 'A');",
[], [],
) )
.unwrap(); .unwrap();
let t_out = read_state(&conn, Some(101)).unwrap(); let t_out = read_state(&conn, "tamerelol", Some(101)).unwrap();
remove_file(d).unwrap(); remove_file(d).unwrap();
@@ -209,18 +243,62 @@ mod tests {
let conn = Connection::open(d).unwrap(); let conn = Connection::open(d).unwrap();
conn.execute( conn.execute(
"INSERT INTO tweet_to_toot (tweet_id, toot_id) "INSERT INTO tweet_to_toot (twitter_screen_name, tweet_id, toot_id)
VALUES VALUES
(100, 'A');", ('tamerelol', 100, 'A');",
[], [],
) )
.unwrap(); .unwrap();
let t_out = read_state(&conn, Some(100)).unwrap().unwrap(); let t_out = read_state(&conn, "tamerelol", Some(100)).unwrap().unwrap();
remove_file(d).unwrap(); remove_file(d).unwrap();
assert_eq!(t_out.tweet_id, 100); assert_eq!(t_out.tweet_id, 100);
assert_eq!(t_out.toot_id, "A"); assert_eq!(t_out.toot_id, "A");
} }
#[test]
fn test_migrate_db_add_column() {
let d = "/tmp/test_migrate_db_add_column.sqlite";
let conn = Connection::open(d).unwrap();
conn.execute(
"CREATE TABLE IF NOT EXISTS tweet_to_toot (
tweet_id INTEGER PRIMARY KEY,
toot_id TEXT UNIQUE
)",
[],
)
.unwrap();
migrate_db(d, "tamerelol").unwrap();
let mut stmt = conn.prepare("PRAGMA table_info(tweet_to_toot);").unwrap();
let mut t = stmt.query([]).unwrap();
while let Some(row) = t.next().unwrap() {
if row.get::<usize, u8>(0).unwrap() == 2 {
assert_eq!(
row.get::<usize, String>(1).unwrap(),
"twitter_screen_name".to_string()
);
}
}
remove_file(d).unwrap();
}
#[test]
fn test_migrate_db_no_add_column() {
let d = "/tmp/test_migrate_db_no_add_column.sqlite";
init_db(d).unwrap();
migrate_db(d, "tamerelol").unwrap();
remove_file(d).unwrap();
}
} }

View File

@@ -1,3 +1,4 @@
use crate::config::MastodonConfig;
use crate::config::TwitterConfig; use crate::config::TwitterConfig;
use crate::util::cache_media; use crate::util::cache_media;
use crate::ScootalooError; use crate::ScootalooError;
@@ -29,16 +30,16 @@ pub fn get_oauth2_token(config: &TwitterConfig) -> Token {
/// Gets Twitter user timeline /// Gets Twitter user timeline
pub async fn get_user_timeline( pub async fn get_user_timeline(
config: &TwitterConfig, config: &MastodonConfig,
token: Token, token: &Token,
lid: Option<u64>, lid: Option<u64>,
) -> Result<Vec<Tweet>, Box<dyn Error>> { ) -> 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 (_, feed) = user_timeline( let (_, feed) = user_timeline(
UserID::from(config.username.to_owned()), UserID::from(config.twitter_screen_name.to_owned()),
true, true,
false, false,
&token, token,
) )
.with_page_size(200) .with_page_size(200)
.older(lid) .older(lid)

View File

@@ -10,20 +10,43 @@ fn test_parse_good_toml() {
); );
assert_eq!(parse_good_toml.scootaloo.cache_path, "/tmp/scootaloo"); assert_eq!(parse_good_toml.scootaloo.cache_path, "/tmp/scootaloo");
assert_eq!(parse_good_toml.twitter.username, "tamerelol");
assert_eq!(parse_good_toml.twitter.consumer_key, "rand consumer key"); assert_eq!(parse_good_toml.twitter.consumer_key, "rand consumer key");
assert_eq!(parse_good_toml.twitter.consumer_secret, "secret"); assert_eq!(parse_good_toml.twitter.consumer_secret, "secret");
assert_eq!(parse_good_toml.twitter.access_key, "rand access key"); assert_eq!(parse_good_toml.twitter.access_key, "rand access key");
assert_eq!(parse_good_toml.twitter.access_secret, "super secret"); assert_eq!(parse_good_toml.twitter.access_secret, "super secret");
assert_eq!(parse_good_toml.mastodon.base, "https://m.nintendojo.fr");
assert_eq!(parse_good_toml.mastodon.client_id, "rand client id");
assert_eq!(parse_good_toml.mastodon.client_secret, "secret");
assert_eq!( assert_eq!(
parse_good_toml.mastodon.redirect, &parse_good_toml
.mastodon
.get("tamerelol")
.unwrap()
.twitter_screen_name,
"tamerelol"
);
assert_eq!(
&parse_good_toml.mastodon.get("tamerelol").unwrap().base,
"https://m.nintendojo.fr"
);
assert_eq!(
&parse_good_toml.mastodon.get("tamerelol").unwrap().client_id,
"rand client id"
);
assert_eq!(
&parse_good_toml
.mastodon
.get("tamerelol")
.unwrap()
.client_secret,
"secret"
);
assert_eq!(
&parse_good_toml.mastodon.get("tamerelol").unwrap().redirect,
"urn:ietf:wg:oauth:2.0:oob" "urn:ietf:wg:oauth:2.0:oob"
); );
assert_eq!(parse_good_toml.mastodon.token, "super secret"); assert_eq!(
&parse_good_toml.mastodon.get("tamerelol").unwrap().token,
"super secret"
);
} }
#[test] #[test]

View File

@@ -4,14 +4,14 @@ db_path="/var/random/scootaloo.sqlite"
cache_path="/tmp/scootaloo" cache_path="/tmp/scootaloo"
[twitter] [twitter]
username="tamerelol"
consumer_key="rand consumer key" consumer_key="rand consumer key"
consumer_secret="secret" consumer_secret="secret"
access_key="rand access key" access_key="rand access key"
access_secret="super secret" access_secret="super secret"
[mastodon] [mastodon]
[mastodon.tamerelol]
twitter_screen_name="tamerelol"
base = "https://m.nintendojo.fr" base = "https://m.nintendojo.fr"
client_id = "rand client id" client_id = "rand client id"
client_secret = "secret" client_secret = "secret"