2015-07-19 20:36:14 +00:00
|
|
|
extern crate getopts;
|
2015-07-09 19:08:56 +00:00
|
|
|
extern crate librespot;
|
2015-07-19 20:36:14 +00:00
|
|
|
extern crate rpassword;
|
2015-04-25 20:32:07 +00:00
|
|
|
|
2016-01-01 23:16:12 +00:00
|
|
|
use rpassword::read_password;
|
2015-06-23 14:38:29 +00:00
|
|
|
use std::clone::Clone;
|
2015-05-09 10:07:24 +00:00
|
|
|
use std::fs::File;
|
2015-07-19 20:36:14 +00:00
|
|
|
use std::io::{stdout, Read, Write};
|
2016-03-14 23:41:51 +00:00
|
|
|
use std::path::PathBuf;
|
2015-07-02 17:24:25 +00:00
|
|
|
use std::thread;
|
2015-07-19 20:36:14 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
use librespot::audio_sink::DefaultSink;
|
2016-03-13 20:45:31 +00:00
|
|
|
use librespot::authentication::Credentials;
|
2016-01-01 23:16:12 +00:00
|
|
|
use librespot::discovery::DiscoveryManager;
|
2015-07-08 19:50:44 +00:00
|
|
|
use librespot::player::Player;
|
2016-01-02 02:30:24 +00:00
|
|
|
use librespot::session::{Bitrate, Config, Session};
|
2015-07-08 19:50:44 +00:00
|
|
|
use librespot::spirc::SpircManager;
|
2016-01-01 23:16:12 +00:00
|
|
|
use librespot::util::version::version_string;
|
2015-04-25 20:32:07 +00:00
|
|
|
|
2015-12-30 16:37:00 +00:00
|
|
|
static PASSWORD_ENV_NAME: &'static str = "SPOTIFY_PASSWORD";
|
|
|
|
|
2016-03-14 23:41:51 +00:00
|
|
|
fn usage(program: &str, opts: &getopts::Options) -> String {
|
2015-07-19 20:36:14 +00:00
|
|
|
let brief = format!("Usage: {} [options]", program);
|
|
|
|
format!("{}", opts.usage(&brief))
|
|
|
|
}
|
|
|
|
|
2016-03-14 23:41:51 +00:00
|
|
|
#[cfg(feature = "static-appkey")]
|
|
|
|
static APPKEY: Option<&'static [u8]> = Some(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/spotify_appkey.key")));
|
|
|
|
#[cfg(not(feature = "static-appkey"))]
|
|
|
|
static APPKEY: Option<&'static [u8]> = None;
|
|
|
|
|
2015-04-25 20:32:07 +00:00
|
|
|
fn main() {
|
2015-07-19 20:36:14 +00:00
|
|
|
let args: Vec<String> = std::env::args().collect();
|
|
|
|
let program = args[0].clone();
|
|
|
|
|
2016-03-14 23:41:51 +00:00
|
|
|
let mut opts = getopts::Options::new();
|
|
|
|
opts.optopt("u", "username", "Username to sign in with", "USERNAME")
|
|
|
|
.optopt("p", "password", "Password", "PASSWORD")
|
2016-03-14 22:56:50 +00:00
|
|
|
.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
|
2016-01-02 02:30:24 +00:00
|
|
|
.reqopt("n", "name", "Device name", "NAME")
|
|
|
|
.optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE");
|
|
|
|
|
2016-03-14 23:41:51 +00:00
|
|
|
if APPKEY.is_none() {
|
|
|
|
opts.reqopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
|
|
|
|
} else {
|
|
|
|
opts.optopt("a", "appkey", "Path to a spotify appkey", "APPKEY");
|
|
|
|
};
|
|
|
|
|
2015-07-19 20:36:14 +00:00
|
|
|
let matches = match opts.parse(&args[1..]) {
|
2016-01-02 15:19:39 +00:00
|
|
|
Ok(m) => m,
|
|
|
|
Err(f) => {
|
|
|
|
print!("Error: {}\n{}", f.to_string(), usage(&*program, &opts));
|
|
|
|
return;
|
2015-07-19 20:36:14 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-14 23:41:51 +00:00
|
|
|
let appkey = matches.opt_str("a").map(|appkey_path| {
|
|
|
|
let mut file = File::open(appkey_path)
|
|
|
|
.expect("Could not open app key.");
|
2016-01-01 23:16:12 +00:00
|
|
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
file.read_to_end(&mut data).unwrap();
|
|
|
|
|
|
|
|
data
|
2016-03-14 23:41:51 +00:00
|
|
|
}).or_else(|| APPKEY.map(ToOwned::to_owned)).unwrap();
|
2015-07-19 20:36:14 +00:00
|
|
|
|
2016-01-02 01:30:03 +00:00
|
|
|
let username = matches.opt_str("u");
|
2016-03-14 22:56:50 +00:00
|
|
|
let cache_location = matches.opt_str("c").map(PathBuf::from);
|
2015-07-19 20:36:14 +00:00
|
|
|
let name = matches.opt_str("n").unwrap();
|
|
|
|
|
2016-01-02 01:30:03 +00:00
|
|
|
let credentials = username.map(|u| {
|
2016-01-02 15:19:39 +00:00
|
|
|
let password = matches.opt_str("p")
|
|
|
|
.or_else(|| std::env::var(PASSWORD_ENV_NAME).ok())
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
print!("Password: ");
|
|
|
|
stdout().flush().unwrap();
|
|
|
|
read_password().unwrap()
|
|
|
|
});
|
2016-01-02 01:30:03 +00:00
|
|
|
|
|
|
|
(u, password)
|
2015-07-19 20:36:14 +00:00
|
|
|
});
|
2016-01-02 01:53:20 +00:00
|
|
|
std::env::remove_var(PASSWORD_ENV_NAME);
|
2015-05-09 10:07:24 +00:00
|
|
|
|
2016-01-02 02:30:24 +00:00
|
|
|
let bitrate = match matches.opt_str("b").as_ref().map(String::as_ref) {
|
2016-01-02 15:19:39 +00:00
|
|
|
None => Bitrate::Bitrate160, // default value
|
2016-01-02 02:30:24 +00:00
|
|
|
|
2016-01-02 15:19:39 +00:00
|
|
|
Some("96") => Bitrate::Bitrate96,
|
2016-01-02 02:30:24 +00:00
|
|
|
Some("160") => Bitrate::Bitrate160,
|
|
|
|
Some("320") => Bitrate::Bitrate320,
|
2016-01-02 15:19:39 +00:00
|
|
|
Some(b) => panic!("Invalid bitrate {}", b),
|
2016-01-02 02:30:24 +00:00
|
|
|
};
|
|
|
|
|
2015-05-09 10:07:24 +00:00
|
|
|
let config = Config {
|
|
|
|
application_key: appkey,
|
2015-07-01 23:27:19 +00:00
|
|
|
user_agent: version_string(),
|
2016-01-01 23:16:12 +00:00
|
|
|
device_name: name,
|
2016-03-13 22:35:09 +00:00
|
|
|
cache_location: cache_location.clone(),
|
2016-01-02 15:19:39 +00:00
|
|
|
bitrate: bitrate,
|
2015-05-09 10:07:24 +00:00
|
|
|
};
|
2015-12-30 18:50:23 +00:00
|
|
|
|
2015-06-23 14:38:29 +00:00
|
|
|
let session = Session::new(config);
|
2016-01-02 01:30:03 +00:00
|
|
|
|
2016-03-14 22:56:50 +00:00
|
|
|
let credentials_path = cache_location.map(|c| c.join("credentials.json"));
|
2016-03-13 22:35:09 +00:00
|
|
|
|
|
|
|
let credentials = credentials.map(|(username, password)| {
|
|
|
|
Credentials::with_password(username, password)
|
|
|
|
}).or_else(|| {
|
2016-03-14 22:56:50 +00:00
|
|
|
credentials_path.as_ref()
|
|
|
|
.and_then(|p| File::open(p).ok())
|
|
|
|
.map(Credentials::from_reader)
|
2016-03-13 22:35:09 +00:00
|
|
|
}).unwrap_or_else(|| {
|
2016-03-14 23:41:51 +00:00
|
|
|
println!("No username provided and no stored credentials, starting discovery ...");
|
|
|
|
|
2016-01-02 01:30:03 +00:00
|
|
|
let mut discovery = DiscoveryManager::new(session.clone());
|
2016-03-13 20:45:31 +00:00
|
|
|
discovery.run()
|
|
|
|
});
|
|
|
|
|
2016-03-13 22:35:09 +00:00
|
|
|
let reusable_credentials = session.login(credentials).unwrap();
|
2016-03-14 22:56:50 +00:00
|
|
|
if let Some(path) = credentials_path {
|
|
|
|
reusable_credentials.save_to_file(path);
|
|
|
|
}
|
2015-05-09 10:07:24 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
let player = Player::new(session.clone(), || DefaultSink::open());
|
2016-03-13 22:35:09 +00:00
|
|
|
let spirc = SpircManager::new(session.clone(), player);
|
2016-01-02 15:19:39 +00:00
|
|
|
thread::spawn(move || spirc.run());
|
2015-07-02 17:24:25 +00:00
|
|
|
|
2016-01-01 23:16:12 +00:00
|
|
|
loop {
|
|
|
|
session.poll();
|
|
|
|
}
|
2015-07-01 17:49:03 +00:00
|
|
|
}
|