@@ -234,6 +234,8 @@ fn get_setup() -> Setup {
234234 const QUIET : & str = "quiet" ;
235235 const SYSTEM_CACHE : & str = "system-cache" ;
236236 const TEMP_DIR : & str = "tmp" ;
237+ const TOKEN : & str = "token" ;
238+ const TOKEN_PORT : & str = "token-port" ;
237239 const USERNAME : & str = "username" ;
238240 const VERBOSE : & str = "verbose" ;
239241 const VERSION : & str = "version" ;
@@ -276,6 +278,8 @@ fn get_setup() -> Setup {
276278 const ALSA_MIXER_INDEX_SHORT : & str = "s" ;
277279 const ALSA_MIXER_CONTROL_SHORT : & str = "T" ;
278280 const TEMP_DIR_SHORT : & str = "t" ;
281+ const TOKEN_SHORT : & str = "k" ;
282+ const TOKEN_PORT_SHORT : & str = "K" ;
279283 const NORMALISATION_ATTACK_SHORT : & str = "U" ;
280284 const USERNAME_SHORT : & str = "u" ;
281285 const VERSION_SHORT : & str = "V" ;
@@ -415,6 +419,18 @@ fn get_setup() -> Setup {
415419 DEVICE_IS_GROUP ,
416420 "Whether the device represents a group. Defaults to false." ,
417421 )
422+ . optopt (
423+ TOKEN_SHORT ,
424+ TOKEN ,
425+ "Spotify access token to sign in with. Use empty string to obtain token." ,
426+ "TOKEN" ,
427+ )
428+ . optopt (
429+ TOKEN_PORT_SHORT ,
430+ TOKEN_PORT ,
431+ "The port the oauth redirect server uses 1 - 65535. Ports <= 1024 may require root privileges." ,
432+ "PORT" ,
433+ )
418434 . optopt (
419435 TEMP_DIR_SHORT ,
420436 TEMP_DIR ,
@@ -670,7 +686,10 @@ fn get_setup() -> Setup {
670686 trace ! ( "Environment variable(s):" ) ;
671687
672688 for ( k, v) in & env_vars {
673- if matches ! ( k. as_str( ) , "LIBRESPOT_PASSWORD" | "LIBRESPOT_USERNAME" ) {
689+ if matches ! (
690+ k. as_str( ) ,
691+ "LIBRESPOT_PASSWORD" | "LIBRESPOT_USERNAME" | "LIBRESPOT_TOKEN"
692+ ) {
674693 trace ! ( "\t \t {k}=\" XXXXXXXX\" " ) ;
675694 } else if v. is_empty ( ) {
676695 trace ! ( "\t \t {k}=" ) ;
@@ -702,7 +721,10 @@ fn get_setup() -> Setup {
702721 && matches. opt_defined ( opt)
703722 && matches. opt_present ( opt)
704723 {
705- if matches ! ( opt, PASSWORD | PASSWORD_SHORT | USERNAME | USERNAME_SHORT ) {
724+ if matches ! (
725+ opt,
726+ PASSWORD | PASSWORD_SHORT | USERNAME | USERNAME_SHORT | TOKEN | TOKEN_SHORT
727+ ) {
706728 // Don't log creds.
707729 trace ! ( "\t \t {opt} \" XXXXXXXX\" " ) ;
708730 } else {
@@ -1081,129 +1103,6 @@ fn get_setup() -> Setup {
10811103 }
10821104 } ;
10831105
1084- let credentials = {
1085- let cached_creds = cache. as_ref ( ) . and_then ( Cache :: credentials) ;
1086-
1087- if let Some ( username) = opt_str ( USERNAME ) {
1088- if username. is_empty ( ) {
1089- empty_string_error_msg ( USERNAME , USERNAME_SHORT ) ;
1090- }
1091- if let Some ( password) = opt_str ( PASSWORD ) {
1092- if password. is_empty ( ) {
1093- empty_string_error_msg ( PASSWORD , PASSWORD_SHORT ) ;
1094- }
1095- Some ( Credentials :: with_password ( username, password) )
1096- } else {
1097- match cached_creds {
1098- Some ( creds) if Some ( & username) == creds. username . as_ref ( ) => Some ( creds) ,
1099- _ => {
1100- let prompt = & format ! ( "Password for {username}: " ) ;
1101- match rpassword:: prompt_password ( prompt) {
1102- Ok ( password) => {
1103- if !password. is_empty ( ) {
1104- Some ( Credentials :: with_password ( username, password) )
1105- } else {
1106- trace ! ( "Password was empty." ) ;
1107- if cached_creds. is_some ( ) {
1108- trace ! ( "Using cached credentials." ) ;
1109- }
1110- cached_creds
1111- }
1112- }
1113- Err ( e) => {
1114- warn ! ( "Cannot parse password: {}" , e) ;
1115- if cached_creds. is_some ( ) {
1116- trace ! ( "Using cached credentials." ) ;
1117- }
1118- cached_creds
1119- }
1120- }
1121- }
1122- }
1123- }
1124- } else {
1125- if cached_creds. is_some ( ) {
1126- trace ! ( "Using cached credentials." ) ;
1127- }
1128- cached_creds
1129- }
1130- } ;
1131-
1132- let enable_discovery = !opt_present ( DISABLE_DISCOVERY ) ;
1133-
1134- if credentials. is_none ( ) && !enable_discovery {
1135- error ! ( "Credentials are required if discovery is disabled." ) ;
1136- exit ( 1 ) ;
1137- }
1138-
1139- if !enable_discovery && opt_present ( ZEROCONF_PORT ) {
1140- warn ! (
1141- "With the `--{}` / `-{}` flag set `--{}` / `-{}` has no effect." ,
1142- DISABLE_DISCOVERY , DISABLE_DISCOVERY_SHORT , ZEROCONF_PORT , ZEROCONF_PORT_SHORT
1143- ) ;
1144- }
1145-
1146- let zeroconf_port = if enable_discovery {
1147- opt_str ( ZEROCONF_PORT )
1148- . map ( |port| match port. parse :: < u16 > ( ) {
1149- Ok ( value) if value != 0 => value,
1150- _ => {
1151- let valid_values = & format ! ( "1 - {}" , u16 :: MAX ) ;
1152- invalid_error_msg ( ZEROCONF_PORT , ZEROCONF_PORT_SHORT , & port, valid_values, "" ) ;
1153-
1154- exit ( 1 ) ;
1155- }
1156- } )
1157- . unwrap_or ( 0 )
1158- } else {
1159- 0
1160- } ;
1161-
1162- // #1046: not all connections are supplied an `autoplay` user attribute to run statelessly.
1163- // This knob allows for a manual override.
1164- let autoplay = match opt_str ( AUTOPLAY ) {
1165- Some ( value) => match value. as_ref ( ) {
1166- "on" => Some ( true ) ,
1167- "off" => Some ( false ) ,
1168- _ => {
1169- invalid_error_msg (
1170- AUTOPLAY ,
1171- AUTOPLAY_SHORT ,
1172- & opt_str ( AUTOPLAY ) . unwrap_or_default ( ) ,
1173- "on, off" ,
1174- "" ,
1175- ) ;
1176- exit ( 1 ) ;
1177- }
1178- } ,
1179- None => SessionConfig :: default ( ) . autoplay ,
1180- } ;
1181-
1182- let zeroconf_ip: Vec < std:: net:: IpAddr > = if opt_present ( ZEROCONF_INTERFACE ) {
1183- if let Some ( zeroconf_ip) = opt_str ( ZEROCONF_INTERFACE ) {
1184- zeroconf_ip
1185- . split ( ',' )
1186- . map ( |s| {
1187- s. trim ( ) . parse :: < std:: net:: IpAddr > ( ) . unwrap_or_else ( |_| {
1188- invalid_error_msg (
1189- ZEROCONF_INTERFACE ,
1190- ZEROCONF_INTERFACE_SHORT ,
1191- s,
1192- "IPv4 and IPv6 addresses" ,
1193- "" ,
1194- ) ;
1195- exit ( 1 ) ;
1196- } )
1197- } )
1198- . collect ( )
1199- } else {
1200- warn ! ( "Unable to use zeroconf-interface option, default to all interfaces." ) ;
1201- vec ! [ ]
1202- }
1203- } else {
1204- vec ! [ ]
1205- } ;
1206-
12071106 let connect_config = {
12081107 let connect_default_config = ConnectConfig :: default ( ) ;
12091108
@@ -1330,6 +1229,26 @@ fn get_setup() -> Setup {
13301229 }
13311230 } ;
13321231
1232+ // #1046: not all connections are supplied an `autoplay` user attribute to run statelessly.
1233+ // This knob allows for a manual override.
1234+ let autoplay = match opt_str ( AUTOPLAY ) {
1235+ Some ( value) => match value. as_ref ( ) {
1236+ "on" => Some ( true ) ,
1237+ "off" => Some ( false ) ,
1238+ _ => {
1239+ invalid_error_msg (
1240+ AUTOPLAY ,
1241+ AUTOPLAY_SHORT ,
1242+ & opt_str ( AUTOPLAY ) . unwrap_or_default ( ) ,
1243+ "on, off" ,
1244+ "" ,
1245+ ) ;
1246+ exit ( 1 ) ;
1247+ }
1248+ } ,
1249+ None => SessionConfig :: default ( ) . autoplay ,
1250+ } ;
1251+
13331252 let session_config = SessionConfig {
13341253 device_id : device_id ( & connect_config. name ) ,
13351254 proxy : opt_str ( PROXY ) . or_else ( || std:: env:: var ( "http_proxy" ) . ok ( ) ) . map (
@@ -1364,6 +1283,130 @@ fn get_setup() -> Setup {
13641283 ..SessionConfig :: default ( )
13651284 } ;
13661285
1286+ let credentials = {
1287+ let cached_creds = cache. as_ref ( ) . and_then ( Cache :: credentials) ;
1288+
1289+ let token_port = if opt_present ( TOKEN_PORT ) {
1290+ opt_str ( TOKEN_PORT )
1291+ . map ( |port| match port. parse :: < u16 > ( ) {
1292+ Ok ( value) => value,
1293+ _ => {
1294+ let valid_values = & format ! ( "1 - {}" , u16 :: MAX ) ;
1295+ invalid_error_msg ( TOKEN_PORT , TOKEN_PORT_SHORT , & port, valid_values, "" ) ;
1296+
1297+ exit ( 1 ) ;
1298+ }
1299+ } )
1300+ . unwrap_or ( 0 )
1301+ } else {
1302+ 5588
1303+ } ;
1304+ if let Some ( mut access_token) = opt_str ( TOKEN ) {
1305+ if access_token. is_empty ( ) {
1306+ access_token =
1307+ librespot:: oauth:: get_access_token ( & session_config. client_id , token_port) ;
1308+ }
1309+ Some ( Credentials :: with_access_token ( access_token) )
1310+ } else if let Some ( username) = opt_str ( USERNAME ) {
1311+ if username. is_empty ( ) {
1312+ empty_string_error_msg ( USERNAME , USERNAME_SHORT ) ;
1313+ }
1314+ if let Some ( password) = opt_str ( PASSWORD ) {
1315+ if password. is_empty ( ) {
1316+ empty_string_error_msg ( PASSWORD , PASSWORD_SHORT ) ;
1317+ }
1318+ Some ( Credentials :: with_password ( username, password) )
1319+ } else {
1320+ match cached_creds {
1321+ Some ( creds) if Some ( & username) == creds. username . as_ref ( ) => Some ( creds) ,
1322+ _ => {
1323+ let prompt = & format ! ( "Password for {username}: " ) ;
1324+ match rpassword:: prompt_password ( prompt) {
1325+ Ok ( password) => {
1326+ if !password. is_empty ( ) {
1327+ Some ( Credentials :: with_password ( username, password) )
1328+ } else {
1329+ trace ! ( "Password was empty." ) ;
1330+ if cached_creds. is_some ( ) {
1331+ trace ! ( "Using cached credentials." ) ;
1332+ }
1333+ cached_creds
1334+ }
1335+ }
1336+ Err ( e) => {
1337+ warn ! ( "Cannot parse password: {}" , e) ;
1338+ if cached_creds. is_some ( ) {
1339+ trace ! ( "Using cached credentials." ) ;
1340+ }
1341+ cached_creds
1342+ }
1343+ }
1344+ }
1345+ }
1346+ }
1347+ } else {
1348+ if cached_creds. is_some ( ) {
1349+ trace ! ( "Using cached credentials." ) ;
1350+ }
1351+ cached_creds
1352+ }
1353+ } ;
1354+
1355+ let enable_discovery = !opt_present ( DISABLE_DISCOVERY ) ;
1356+
1357+ if credentials. is_none ( ) && !enable_discovery {
1358+ error ! ( "Credentials are required if discovery is disabled." ) ;
1359+ exit ( 1 ) ;
1360+ }
1361+
1362+ if !enable_discovery && opt_present ( ZEROCONF_PORT ) {
1363+ warn ! (
1364+ "With the `--{}` / `-{}` flag set `--{}` / `-{}` has no effect." ,
1365+ DISABLE_DISCOVERY , DISABLE_DISCOVERY_SHORT , ZEROCONF_PORT , ZEROCONF_PORT_SHORT
1366+ ) ;
1367+ }
1368+
1369+ let zeroconf_port = if enable_discovery {
1370+ opt_str ( ZEROCONF_PORT )
1371+ . map ( |port| match port. parse :: < u16 > ( ) {
1372+ Ok ( value) if value != 0 => value,
1373+ _ => {
1374+ let valid_values = & format ! ( "1 - {}" , u16 :: MAX ) ;
1375+ invalid_error_msg ( ZEROCONF_PORT , ZEROCONF_PORT_SHORT , & port, valid_values, "" ) ;
1376+
1377+ exit ( 1 ) ;
1378+ }
1379+ } )
1380+ . unwrap_or ( 0 )
1381+ } else {
1382+ 0
1383+ } ;
1384+
1385+ let zeroconf_ip: Vec < std:: net:: IpAddr > = if opt_present ( ZEROCONF_INTERFACE ) {
1386+ if let Some ( zeroconf_ip) = opt_str ( ZEROCONF_INTERFACE ) {
1387+ zeroconf_ip
1388+ . split ( ',' )
1389+ . map ( |s| {
1390+ s. trim ( ) . parse :: < std:: net:: IpAddr > ( ) . unwrap_or_else ( |_| {
1391+ invalid_error_msg (
1392+ ZEROCONF_INTERFACE ,
1393+ ZEROCONF_INTERFACE_SHORT ,
1394+ s,
1395+ "IPv4 and IPv6 addresses" ,
1396+ "" ,
1397+ ) ;
1398+ exit ( 1 ) ;
1399+ } )
1400+ } )
1401+ . collect ( )
1402+ } else {
1403+ warn ! ( "Unable to use zeroconf-interface option, default to all interfaces." ) ;
1404+ vec ! [ ]
1405+ }
1406+ } else {
1407+ vec ! [ ]
1408+ } ;
1409+
13671410 let player_config = {
13681411 let player_default_config = PlayerConfig :: default ( ) ;
13691412
0 commit comments