Merge pull request #72 from tilosp/rustfmt
Run cargo fmt and check it using github actions
This commit is contained in:
commit
258e758395
7 changed files with 1096 additions and 936 deletions
25
.github/workflows/rustfmt.yml
vendored
Normal file
25
.github/workflows/rustfmt.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
on: [push, pull_request]
|
||||
|
||||
name: rustfmt
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
name: rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: Run cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
use reqwest::{Url, cookie::{CookieStore}, header::COOKIE};
|
||||
use log::*;
|
||||
use reqwest::{cookie::CookieStore, header::COOKIE, Url};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use steamguard::{SteamGuardAccount, steamapi::Session};
|
||||
use log::*;
|
||||
use std::collections::HashMap;
|
||||
use steamguard::{steamapi::Session, SteamGuardAccount};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountLinker {
|
||||
|
@ -15,7 +15,7 @@ pub struct AccountLinker {
|
|||
|
||||
impl AccountLinker {
|
||||
pub fn new() -> AccountLinker {
|
||||
return AccountLinker{
|
||||
return AccountLinker {
|
||||
device_id: generate_device_id(),
|
||||
phone_number: String::from(""),
|
||||
account: SteamGuardAccount::new(),
|
||||
|
@ -23,7 +23,7 @@ impl AccountLinker {
|
|||
.cookie_store(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn link(&self, session: &mut Session) {
|
||||
|
@ -56,7 +56,8 @@ impl AccountLinker {
|
|||
params.insert("skipvoip", "1");
|
||||
}
|
||||
|
||||
let resp = self.client
|
||||
let resp = self
|
||||
.client
|
||||
.post("https://steamcommunity.com/steamguard/phoneajax")
|
||||
.header(COOKIE, cookies.cookies(&url).unwrap())
|
||||
.send()
|
||||
|
@ -82,5 +83,5 @@ fn generate_device_id() -> String {
|
|||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct AddAuthenticatorResponse {
|
||||
pub response: SteamGuardAccount
|
||||
pub response: SteamGuardAccount,
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::error::Error;
|
||||
use steamguard::SteamGuardAccount;
|
||||
use log::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
|
|
113
src/main.rs
113
src/main.rs
|
@ -1,19 +1,28 @@
|
|||
extern crate rpassword;
|
||||
use steamguard::{SteamGuardAccount, Confirmation, ConfirmationType, steamapi};
|
||||
use std::collections::HashSet;
|
||||
use std::{io::{Write, stdout, stdin}, path::Path};
|
||||
use clap::{App, Arg, crate_version};
|
||||
use clap::{crate_version, App, Arg};
|
||||
use log::*;
|
||||
use regex::Regex;
|
||||
use termion::{raw::IntoRawMode, screen::AlternateScreen, event::{Key, Event}, input::{TermRead}};
|
||||
use std::collections::HashSet;
|
||||
use std::{
|
||||
io::{stdin, stdout, Write},
|
||||
path::Path,
|
||||
};
|
||||
use steamguard::{steamapi, Confirmation, ConfirmationType, SteamGuardAccount};
|
||||
use termion::{
|
||||
event::{Event, Key},
|
||||
input::TermRead,
|
||||
raw::IntoRawMode,
|
||||
screen::AlternateScreen,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
mod accountmanager;
|
||||
mod accountlinker;
|
||||
mod accountmanager;
|
||||
|
||||
lazy_static! {
|
||||
static ref CAPTCHA_VALID_CHARS: Regex = Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap();
|
||||
static ref CAPTCHA_VALID_CHARS: Regex =
|
||||
Regex::new("^([A-H]|[J-N]|[P-R]|[T-Z]|[2-4]|[7-9]|[@%&])+$").unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -80,11 +89,12 @@ fn main() {
|
|||
)
|
||||
.get_matches();
|
||||
|
||||
|
||||
let verbosity = matches.occurrences_of("verbosity") as usize + 2;
|
||||
stderrlog::new()
|
||||
.verbosity(verbosity)
|
||||
.module(module_path!()).init().unwrap();
|
||||
.module(module_path!())
|
||||
.init()
|
||||
.unwrap();
|
||||
|
||||
if let Some(demo_matches) = matches.subcommand_matches("debug") {
|
||||
if demo_matches.is_present("demo-conf-menu") {
|
||||
|
@ -134,7 +144,13 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
debug!("selected accounts: {:?}", selected_accounts.iter().map(|a| a.account_name.clone()).collect::<Vec<String>>());
|
||||
debug!(
|
||||
"selected accounts: {:?}",
|
||||
selected_accounts
|
||||
.iter()
|
||||
.map(|a| a.account_name.clone())
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
|
||||
if let Some(trade_matches) = matches.subcommand_matches("trade") {
|
||||
info!("trade");
|
||||
|
@ -162,8 +178,7 @@ fn main() {
|
|||
let result = account.accept_confirmation(conf);
|
||||
debug!("accept confirmation result: {:?}", result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if termion::is_tty(&stdout()) {
|
||||
let (accept, deny) = prompt_confirmation_menu(confirmations);
|
||||
for conf in &accept {
|
||||
|
@ -174,8 +189,7 @@ fn main() {
|
|||
let result = account.deny_confirmation(conf);
|
||||
debug!("deny confirmation result: {:?}", result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
warn!("not a tty, not showing menu");
|
||||
for conf in &confirmations {
|
||||
println!("{}", conf.description());
|
||||
|
@ -219,7 +233,9 @@ fn test_validate_captcha_text() {
|
|||
fn prompt() -> String {
|
||||
let mut text = String::new();
|
||||
let _ = std::io::stdout().flush();
|
||||
stdin().read_line(&mut text).expect("Did not enter a correct string");
|
||||
stdin()
|
||||
.read_line(&mut text)
|
||||
.expect("Did not enter a correct string");
|
||||
return String::from(text.strip_suffix('\n').unwrap());
|
||||
}
|
||||
|
||||
|
@ -238,7 +254,9 @@ fn prompt_captcha_text(captcha_gid: &String) -> String {
|
|||
}
|
||||
|
||||
/// Returns a tuple of (accepted, denied). Ignored confirmations are not included.
|
||||
fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmation>, Vec<Confirmation>) {
|
||||
fn prompt_confirmation_menu(
|
||||
confirmations: Vec<Confirmation>,
|
||||
) -> (Vec<Confirmation>, Vec<Confirmation>) {
|
||||
println!("press a key other than enter to show the menu.");
|
||||
let mut to_accept_idx: HashSet<usize> = HashSet::new();
|
||||
let mut to_deny_idx: HashSet<usize> = HashSet::new();
|
||||
|
@ -263,13 +281,22 @@ fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmati
|
|||
to_deny_idx.remove(&selected_idx);
|
||||
}
|
||||
Event::Key(Key::Char('A')) => {
|
||||
(0..confirmations.len()).for_each(|i| { to_accept_idx.insert(i); to_deny_idx.remove(&i); });
|
||||
(0..confirmations.len()).for_each(|i| {
|
||||
to_accept_idx.insert(i);
|
||||
to_deny_idx.remove(&i);
|
||||
});
|
||||
}
|
||||
Event::Key(Key::Char('D')) => {
|
||||
(0..confirmations.len()).for_each(|i| { to_accept_idx.remove(&i); to_deny_idx.insert(i); });
|
||||
(0..confirmations.len()).for_each(|i| {
|
||||
to_accept_idx.remove(&i);
|
||||
to_deny_idx.insert(i);
|
||||
});
|
||||
}
|
||||
Event::Key(Key::Char('I')) => {
|
||||
(0..confirmations.len()).for_each(|i| { to_accept_idx.remove(&i); to_deny_idx.remove(&i); });
|
||||
(0..confirmations.len()).for_each(|i| {
|
||||
to_accept_idx.remove(&i);
|
||||
to_deny_idx.remove(&i);
|
||||
});
|
||||
}
|
||||
Event::Key(Key::Up) if selected_idx > 0 => {
|
||||
selected_idx -= 1;
|
||||
|
@ -286,27 +313,51 @@ fn prompt_confirmation_menu(confirmations: Vec<Confirmation>) -> (Vec<Confirmati
|
|||
_ => {}
|
||||
}
|
||||
|
||||
write!(screen, "{}{}{}arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n", termion::clear::All, termion::cursor::Goto(1, 1), termion::color::Fg(termion::color::White)).unwrap();
|
||||
write!(
|
||||
screen,
|
||||
"{}{}{}arrow keys to select, [a]ccept, [d]eny, [i]gnore, [enter] confirm choices\n\n",
|
||||
termion::clear::All,
|
||||
termion::cursor::Goto(1, 1),
|
||||
termion::color::Fg(termion::color::White)
|
||||
)
|
||||
.unwrap();
|
||||
for i in 0..confirmations.len() {
|
||||
if selected_idx == i {
|
||||
write!(screen, "\r{} >", termion::color::Fg(termion::color::LightYellow)).unwrap();
|
||||
}
|
||||
else {
|
||||
write!(
|
||||
screen,
|
||||
"\r{} >",
|
||||
termion::color::Fg(termion::color::LightYellow)
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
write!(screen, "\r{} ", termion::color::Fg(termion::color::White)).unwrap();
|
||||
}
|
||||
|
||||
if to_accept_idx.contains(&i) {
|
||||
write!(screen, "{}[a]", termion::color::Fg(termion::color::LightGreen)).unwrap();
|
||||
}
|
||||
else if to_deny_idx.contains(&i) {
|
||||
write!(screen, "{}[d]", termion::color::Fg(termion::color::LightRed)).unwrap();
|
||||
}
|
||||
else {
|
||||
write!(
|
||||
screen,
|
||||
"{}[a]",
|
||||
termion::color::Fg(termion::color::LightGreen)
|
||||
)
|
||||
.unwrap();
|
||||
} else if to_deny_idx.contains(&i) {
|
||||
write!(
|
||||
screen,
|
||||
"{}[d]",
|
||||
termion::color::Fg(termion::color::LightRed)
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
write!(screen, "[ ]").unwrap();
|
||||
}
|
||||
|
||||
if selected_idx == i {
|
||||
write!(screen, "{}", termion::color::Fg(termion::color::LightYellow)).unwrap();
|
||||
write!(
|
||||
screen,
|
||||
"{}",
|
||||
termion::color::Fg(termion::color::LightYellow)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
write!(screen, " {}\n", confirmations[i].description()).unwrap();
|
||||
|
@ -346,7 +397,7 @@ fn do_login(account: &mut SteamGuardAccount) {
|
|||
let server_time = steamapi::get_server_time();
|
||||
login.twofactor_code = account.generate_code(server_time);
|
||||
}
|
||||
Err(steamapi::LoginError::NeedCaptcha{ captcha_gid }) => {
|
||||
Err(steamapi::LoginError::NeedCaptcha { captcha_gid }) => {
|
||||
login.captcha_text = prompt_captcha_text(&captcha_gid);
|
||||
}
|
||||
Err(steamapi::LoginError::NeedEmail) => {
|
||||
|
|
|
@ -21,7 +21,7 @@ pub enum ConfirmationType {
|
|||
Trade = 2,
|
||||
MarketSell = 3,
|
||||
AccountRecovery = 6,
|
||||
Unknown
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<&str> for ConfirmationType {
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
use std::{collections::HashMap, convert::TryInto, thread, time};
|
||||
use anyhow::Result;
|
||||
pub use confirmation::{Confirmation, ConfirmationType};
|
||||
use hmacsha1::hmac_sha1;
|
||||
use reqwest::{Url, cookie::CookieStore, header::{COOKIE, USER_AGENT}};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use log::*;
|
||||
use reqwest::{
|
||||
cookie::CookieStore,
|
||||
header::{COOKIE, USER_AGENT},
|
||||
Url,
|
||||
};
|
||||
use scraper::{Html, Selector};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, convert::TryInto, thread, time};
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
pub mod steamapi;
|
||||
mod confirmation;
|
||||
pub mod steamapi;
|
||||
|
||||
// const STEAMAPI_BASE: String = "https://api.steampowered.com";
|
||||
// const COMMUNITY_BASE: String = "https://steamcommunity.com";
|
||||
|
@ -21,9 +25,9 @@ mod confirmation;
|
|||
// const TWO_FACTOR_BASE: String = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001";
|
||||
// static TWO_FACTOR_TIME_QUERY: String = TWO_FACTOR_BASE.Replace("%s", "QueryTime");
|
||||
|
||||
extern crate hmacsha1;
|
||||
extern crate base64;
|
||||
extern crate cookie;
|
||||
extern crate hmacsha1;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SteamGuardAccount {
|
||||
|
@ -63,7 +67,7 @@ fn generate_confirmation_hash_for_time(time: i64, tag: &str, identity_secret: &S
|
|||
|
||||
impl SteamGuardAccount {
|
||||
pub fn new() -> Self {
|
||||
return SteamGuardAccount{
|
||||
return SteamGuardAccount {
|
||||
account_name: String::from(""),
|
||||
serial_number: String::from(""),
|
||||
revocation_code: String::from(""),
|
||||
|
@ -75,11 +79,14 @@ impl SteamGuardAccount {
|
|||
fully_enrolled: false,
|
||||
device_id: String::from(""),
|
||||
session: Option::None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn generate_code(&self, time: i64) -> String {
|
||||
let steam_guard_code_translations: [u8; 26] = [50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89];
|
||||
let steam_guard_code_translations: [u8; 26] = [
|
||||
50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84,
|
||||
86, 87, 88, 89,
|
||||
];
|
||||
|
||||
// this effectively makes it so that it creates a new code every 30 seconds.
|
||||
let time_bytes: [u8; 8] = build_time_bytes(time / 30i64);
|
||||
|
@ -87,18 +94,18 @@ impl SteamGuardAccount {
|
|||
let hashed_data = hmacsha1::hmac_sha1(&shared_secret, &time_bytes);
|
||||
let mut code_array: [u8; 5] = [0; 5];
|
||||
let b = (hashed_data[19] & 0xF) as usize;
|
||||
let mut code_point: i32 =
|
||||
((hashed_data[b] & 0x7F) as i32) << 24 |
|
||||
((hashed_data[b + 1] & 0xFF) as i32) << 16 |
|
||||
((hashed_data[b + 2] & 0xFF) as i32) << 8 |
|
||||
((hashed_data[b + 3] & 0xFF) as i32);
|
||||
let mut code_point: i32 = ((hashed_data[b] & 0x7F) as i32) << 24
|
||||
| ((hashed_data[b + 1] & 0xFF) as i32) << 16
|
||||
| ((hashed_data[b + 2] & 0xFF) as i32) << 8
|
||||
| ((hashed_data[b + 3] & 0xFF) as i32);
|
||||
|
||||
for i in 0..5 {
|
||||
code_array[i] = steam_guard_code_translations[code_point as usize % steam_guard_code_translations.len()];
|
||||
code_array[i] = steam_guard_code_translations
|
||||
[code_point as usize % steam_guard_code_translations.len()];
|
||||
code_point /= steam_guard_code_translations.len() as i32;
|
||||
}
|
||||
|
||||
return String::from_utf8(code_array.iter().map(|c| *c).collect()).unwrap()
|
||||
return String::from_utf8(code_array.iter().map(|c| *c).collect()).unwrap();
|
||||
}
|
||||
|
||||
fn get_confirmation_query_params(&self, tag: &str) -> HashMap<&str, String> {
|
||||
|
@ -107,7 +114,10 @@ impl SteamGuardAccount {
|
|||
let mut params = HashMap::new();
|
||||
params.insert("p", self.device_id.clone());
|
||||
params.insert("a", session.steam_id.to_string());
|
||||
params.insert("k", generate_confirmation_hash_for_time(time, tag, &self.identity_secret));
|
||||
params.insert(
|
||||
"k",
|
||||
generate_confirmation_hash_for_time(time, tag, &self.identity_secret),
|
||||
);
|
||||
params.insert("t", time.to_string());
|
||||
params.insert("m", String::from("android"));
|
||||
params.insert("tag", String::from(tag));
|
||||
|
@ -126,7 +136,10 @@ impl SteamGuardAccount {
|
|||
cookies.add_cookie_str(format!("sessionid={}", session_id).as_str(), &url);
|
||||
cookies.add_cookie_str(format!("steamid={}", session.steam_id).as_str(), &url);
|
||||
cookies.add_cookie_str(format!("steamLogin={}", session.steam_login).as_str(), &url);
|
||||
cookies.add_cookie_str(format!("steamLoginSecure={}", session.steam_login_secure).as_str(), &url);
|
||||
cookies.add_cookie_str(
|
||||
format!("steamLoginSecure={}", session.steam_login_secure).as_str(),
|
||||
&url,
|
||||
);
|
||||
return cookies;
|
||||
}
|
||||
|
||||
|
@ -174,7 +187,7 @@ impl SteamGuardAccount {
|
|||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
struct SendConfirmationResponse {
|
||||
pub success: bool
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
let resp: SendConfirmationResponse = client.get("https://steamcommunity.com/mobileconf/ajaxop".parse::<Url>().unwrap())
|
||||
|
@ -242,7 +255,12 @@ fn parse_confirmations(text: String) -> anyhow::Result<Vec<Confirmation>> {
|
|||
let conf = Confirmation {
|
||||
id: elem.value().attr("data-confid").unwrap().parse()?,
|
||||
key: elem.value().attr("data-key").unwrap().parse()?,
|
||||
conf_type: elem.value().attr("data-type").unwrap().try_into().unwrap_or(ConfirmationType::Unknown),
|
||||
conf_type: elem
|
||||
.value()
|
||||
.attr("data-type")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap_or(ConfirmationType::Unknown),
|
||||
creator: elem.value().attr("data-creator").unwrap().parse()?,
|
||||
};
|
||||
confirmations.push(conf);
|
||||
|
@ -258,7 +276,11 @@ mod tests {
|
|||
fn test_build_time_bytes() {
|
||||
let t1 = build_time_bytes(1617591917i64);
|
||||
let t2: [u8; 8] = [0, 0, 0, 0, 96, 106, 126, 109];
|
||||
assert!(t1.iter().zip(t2.iter()).all(|(a,b)| a == b), "Arrays are not equal, got {:?}", t1);
|
||||
assert!(
|
||||
t1.iter().zip(t2.iter()).all(|(a, b)| a == b),
|
||||
"Arrays are not equal, got {:?}",
|
||||
t1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -272,7 +294,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_generate_confirmation_hash_for_time() {
|
||||
assert_eq!(generate_confirmation_hash_for_time(1617591917, "conf", &String::from("GQP46b73Ws7gr8GmZFR0sDuau5c=")), String::from("NaL8EIMhfy/7vBounJ0CvpKbrPk="));
|
||||
assert_eq!(
|
||||
generate_confirmation_hash_for_time(
|
||||
1617591917,
|
||||
"conf",
|
||||
&String::from("GQP46b73Ws7gr8GmZFR0sDuau5c=")
|
||||
),
|
||||
String::from("NaL8EIMhfy/7vBounJ0CvpKbrPk=")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -280,35 +309,50 @@ mod tests {
|
|||
let text = include_str!("fixtures/confirmations/multiple-confirmations.html");
|
||||
let confirmations = parse_confirmations(text.into()).unwrap();
|
||||
assert_eq!(confirmations.len(), 5);
|
||||
assert_eq!(confirmations[0], Confirmation {
|
||||
assert_eq!(
|
||||
confirmations[0],
|
||||
Confirmation {
|
||||
id: 9890792058,
|
||||
key: 15509106087034649470,
|
||||
conf_type: ConfirmationType::MarketSell,
|
||||
creator: 3392884950693131245,
|
||||
});
|
||||
assert_eq!(confirmations[1], Confirmation {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
confirmations[1],
|
||||
Confirmation {
|
||||
id: 9890791666,
|
||||
key: 2661901169510258722,
|
||||
conf_type: ConfirmationType::MarketSell,
|
||||
creator: 3392884950693130525,
|
||||
});
|
||||
assert_eq!(confirmations[2], Confirmation {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
confirmations[2],
|
||||
Confirmation {
|
||||
id: 9890791241,
|
||||
key: 15784514761287735229,
|
||||
conf_type: ConfirmationType::MarketSell,
|
||||
creator: 3392884950693129565,
|
||||
});
|
||||
assert_eq!(confirmations[3], Confirmation {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
confirmations[3],
|
||||
Confirmation {
|
||||
id: 9890790828,
|
||||
key: 5049250785011653560,
|
||||
conf_type: ConfirmationType::MarketSell,
|
||||
creator: 3392884950693128685,
|
||||
});
|
||||
assert_eq!(confirmations[4], Confirmation {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
confirmations[4],
|
||||
Confirmation {
|
||||
id: 9890790159,
|
||||
key: 6133112455066694993,
|
||||
conf_type: ConfirmationType::MarketSell,
|
||||
creator: 3392884950693127345,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
use reqwest::{Url, cookie::{CookieStore}, header::COOKIE, header::{SET_COOKIE, USER_AGENT}};
|
||||
use rsa::{PublicKey, RsaPublicKey};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use log::*;
|
||||
use reqwest::{
|
||||
cookie::CookieStore,
|
||||
header::COOKIE,
|
||||
header::{SET_COOKIE, USER_AGENT},
|
||||
Url,
|
||||
};
|
||||
use rsa::{PublicKey, RsaPublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct LoginResponse {
|
||||
|
@ -48,7 +53,7 @@ struct RsaResponse {
|
|||
pub enum LoginError {
|
||||
BadRSA,
|
||||
BadCredentials,
|
||||
NeedCaptcha{ captcha_gid: String },
|
||||
NeedCaptcha { captcha_gid: String },
|
||||
Need2FA,
|
||||
NeedEmail,
|
||||
TooManyAttempts,
|
||||
|
@ -86,14 +91,15 @@ impl UserLogin {
|
|||
.cookie_store(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Updates the cookie jar with the session cookies by pinging steam servers.
|
||||
fn update_session(&self) {
|
||||
trace!("UserLogin::update_session");
|
||||
let url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
||||
self.cookies.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
|
||||
self.cookies
|
||||
.add_cookie_str("mobileClientVersion=0 (2.1.3)", &url);
|
||||
self.cookies.add_cookie_str("mobileClient=android", &url);
|
||||
self.cookies.add_cookie_str("Steam_Language=english", &url);
|
||||
|
||||
|
@ -114,7 +120,9 @@ impl UserLogin {
|
|||
pub fn login(&mut self) -> anyhow::Result<Session, LoginError> {
|
||||
trace!("UserLogin::login");
|
||||
if self.captcha_required && self.captcha_text.len() == 0 {
|
||||
return Err(LoginError::NeedCaptcha{captcha_gid: self.captcha_gid.clone()});
|
||||
return Err(LoginError::NeedCaptcha {
|
||||
captcha_gid: self.captcha_gid.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let url = "https://steamcommunity.com".parse::<Url>().unwrap();
|
||||
|
@ -123,9 +131,20 @@ impl UserLogin {
|
|||
}
|
||||
|
||||
let mut params = HashMap::new();
|
||||
params.insert("donotcache", format!("{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() * 1000));
|
||||
params.insert(
|
||||
"donotcache",
|
||||
format!(
|
||||
"{}",
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
* 1000
|
||||
),
|
||||
);
|
||||
params.insert("username", self.username.clone());
|
||||
let resp = self.client
|
||||
let resp = self
|
||||
.client
|
||||
.post("https://steamcommunity.com/login/getrsakey")
|
||||
.form(¶ms)
|
||||
.send()
|
||||
|
@ -149,7 +168,17 @@ impl UserLogin {
|
|||
trace!("twofactorcode: {}", self.twofactor_code);
|
||||
trace!("emailauth: {}", self.email_code);
|
||||
let mut params = HashMap::new();
|
||||
params.insert("donotcache", format!("{}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() * 1000));
|
||||
params.insert(
|
||||
"donotcache",
|
||||
format!(
|
||||
"{}",
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
* 1000
|
||||
),
|
||||
);
|
||||
params.insert("username", self.username.clone());
|
||||
params.insert("password", encrypted_password);
|
||||
params.insert("twofactorcode", self.twofactor_code.clone());
|
||||
|
@ -159,13 +188,18 @@ impl UserLogin {
|
|||
params.insert("rsatimestamp", rsa_timestamp);
|
||||
params.insert("remember_login", String::from("true"));
|
||||
params.insert("oauth_client_id", String::from("DE45CD61"));
|
||||
params.insert("oauth_scope", String::from("read_profile write_profile read_client write_client"));
|
||||
params.insert(
|
||||
"oauth_scope",
|
||||
String::from("read_profile write_profile read_client write_client"),
|
||||
);
|
||||
|
||||
let login_resp: LoginResponse;
|
||||
match self.client
|
||||
match self
|
||||
.client
|
||||
.post("https://steamcommunity.com/login/dologin")
|
||||
.form(¶ms)
|
||||
.send() {
|
||||
.send()
|
||||
{
|
||||
Ok(resp) => {
|
||||
// https://stackoverflow.com/questions/49928648/rubys-mechanize-error-401-while-sending-a-post-request-steam-trade-offer-send
|
||||
let text = resp.text().unwrap();
|
||||
|
@ -198,7 +232,9 @@ impl UserLogin {
|
|||
|
||||
if login_resp.captcha_needed {
|
||||
self.captcha_gid = login_resp.captcha_gid.clone();
|
||||
return Err(LoginError::NeedCaptcha{ captcha_gid: self.captcha_gid.clone() });
|
||||
return Err(LoginError::NeedCaptcha {
|
||||
captcha_gid: self.captcha_gid.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if login_resp.emailauth_needed {
|
||||
|
@ -214,7 +250,6 @@ impl UserLogin {
|
|||
return Err(LoginError::BadCredentials);
|
||||
}
|
||||
|
||||
|
||||
// transfer login parameters? Not completely sure what this is for.
|
||||
// i guess steam changed their authentication scheme slightly
|
||||
let oauth;
|
||||
|
@ -223,10 +258,7 @@ impl UserLogin {
|
|||
debug!("received transfer parameters, relaying data...");
|
||||
for url in urls {
|
||||
trace!("posting transfer to {}", url);
|
||||
let result = self.client
|
||||
.post(url)
|
||||
.json(¶ms)
|
||||
.send();
|
||||
let result = self.client.post(url).json(¶ms).send();
|
||||
trace!("result: {:?}", result);
|
||||
match result {
|
||||
Ok(resp) => {
|
||||
|
@ -257,7 +289,10 @@ impl UserLogin {
|
|||
let cookies = self.cookies.cookies(&url).unwrap();
|
||||
let all_cookies = cookies.to_str().unwrap();
|
||||
let mut session_id = String::from("");
|
||||
for cookie in all_cookies.split(";").map(|s| cookie::Cookie::parse(s).unwrap()) {
|
||||
for cookie in all_cookies
|
||||
.split(";")
|
||||
.map(|s| cookie::Cookie::parse(s).unwrap())
|
||||
{
|
||||
if cookie.name() == "sessionid" {
|
||||
session_id = String::from(cookie.value());
|
||||
}
|
||||
|
@ -269,7 +304,7 @@ impl UserLogin {
|
|||
}
|
||||
|
||||
fn build_session(&self, data: OAuthData, session_id: String) -> Session {
|
||||
return Session{
|
||||
return Session {
|
||||
token: data.oauth_token,
|
||||
steam_id: data.steamid,
|
||||
steam_login: format!("{}%7C%7C{}", data.steamid, data.wgtoken),
|
||||
|
@ -286,9 +321,7 @@ impl UserLogin {
|
|||
for c in set_cookie_iter {
|
||||
c.to_str()
|
||||
.into_iter()
|
||||
.for_each(|cookie_str| {
|
||||
self.cookies.add_cookie_str(cookie_str, &url)
|
||||
});
|
||||
.for_each(|cookie_str| self.cookies.add_cookie_str(cookie_str, &url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -326,7 +359,9 @@ pub fn get_server_time() -> i64 {
|
|||
.send();
|
||||
let value: serde_json::Value = resp.unwrap().json().unwrap();
|
||||
|
||||
return String::from(value["response"]["server_time"].as_str().unwrap()).parse().unwrap();
|
||||
return String::from(value["response"]["server_time"].as_str().unwrap())
|
||||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn encrypt_password(rsa_resp: RsaResponse, password: &String) -> String {
|
||||
|
@ -338,7 +373,11 @@ fn encrypt_password(rsa_resp: RsaResponse, password: &String) -> String {
|
|||
#[cfg(not(test))]
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let padding = rsa::PaddingScheme::new_pkcs1v15_encrypt();
|
||||
let encrypted_password = base64::encode(public_key.encrypt(&mut rng, padding, password.as_bytes()).unwrap());
|
||||
let encrypted_password = base64::encode(
|
||||
public_key
|
||||
.encrypt(&mut rng, padding, password.as_bytes())
|
||||
.unwrap(),
|
||||
);
|
||||
return encrypted_password;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue