Skip to content

Commit

Permalink
Convert twintail to be a library; separate CLI code from library code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Duosion committed Nov 24, 2024
1 parent 3d83442 commit d128333
Show file tree
Hide file tree
Showing 41 changed files with 1,935 additions and 1,026 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
[package]
name = "twintail"
version = "1.0.0"
version = "1.1.0"
edition = "2021"

[profile.release]
codegen-units = 1
lto = true
opt-level = 3
panic = "abort"
strip = true

[dependencies]
aes = "0.8.4"
anstyle = "1.0.10"
Expand Down
56 changes: 17 additions & 39 deletions src/api/sekai_client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use super::{
headers::Headers,
url::{
global_provider::GlobalUrlProvider, japan_provider::JapanUrlProvider,
server_provider::ServerUrlProvider, UrlProvider,
},
};
use super::{headers::Headers, url::UrlProvider};
use crate::{
config::AesConfig,
constants::{header, strings},
crypto::aes_msgpack,
error::ApiError,
Expand All @@ -14,7 +9,7 @@ use crate::{
AssetbundleInfo, GameVersion, SystemInfo, UserAuthRequest, UserAuthResponse,
UserRequest, UserSignup,
},
enums::{Platform, Server},
enums::Platform,
},
};
use reqwest::{Client, StatusCode};
Expand All @@ -41,35 +36,18 @@ impl SekaiApp {
pub struct SekaiClient<T: UrlProvider> {
headers: Headers,
client: Client,
aes_config: AesConfig,
pub url_provider: T,
server: Server,
pub app: SekaiApp,
}

impl SekaiClient<ServerUrlProvider> {
/// Creates a new SekaiClient that uses a ServerUrlProvider based on the passed ``server`` value.
pub async fn new(
version: String,
hash: String,
platform: Platform,
server: Server,
) -> Result<Self, ApiError> {
let provider = match server {
Server::Japan => ServerUrlProvider::Japan(JapanUrlProvider::default()),
Server::Global => ServerUrlProvider::Global(GlobalUrlProvider::default()),
};

Self::new_with_url_provider(version, hash, platform, server, provider).await
}
}

impl<T: UrlProvider> SekaiClient<T> {
/// Creates a new SekaiClient that uses a specific url provider.
pub async fn new_with_url_provider(
version: String,
hash: String,
platform: Platform,
server: Server,
aes_config: AesConfig,
url_provider: T,
) -> Result<Self, ApiError> {
let headers = Headers::builder()?
Expand All @@ -81,8 +59,8 @@ impl<T: UrlProvider> SekaiClient<T> {
let mut client = Self {
headers,
client: Client::new(),
aes_config,
url_provider,
server,
app: SekaiApp::new(version, hash, platform),
};

Expand Down Expand Up @@ -146,7 +124,7 @@ impl<T: UrlProvider> SekaiClient<T> {
match request.send().await?.error_for_status() {
Ok(response) => {
let bytes = response.bytes().await?;
Ok(aes_msgpack::from_slice(&bytes, &self.server)?)
Ok(aes_msgpack::from_slice(&bytes, &self.aes_config)?)
}
Err(err) => match err.status() {
Some(StatusCode::FORBIDDEN) => Err(ApiError::InvalidRequest(
Expand All @@ -171,7 +149,7 @@ impl<T: UrlProvider> SekaiClient<T> {
device_model: header::value::DEVICE_MODEL.into(),
operating_system: header::value::OPERATING_SYSTEM.into(),
},
&self.server,
&self.aes_config,
)?;

let request = self
Expand All @@ -183,7 +161,7 @@ impl<T: UrlProvider> SekaiClient<T> {
match request.send().await?.error_for_status() {
Ok(response) => {
let bytes = response.bytes().await?;
Ok(aes_msgpack::from_slice(&bytes, &self.server)?)
Ok(aes_msgpack::from_slice(&bytes, &self.aes_config)?)
}
Err(err) => match err.status() {
Some(StatusCode::UPGRADE_REQUIRED) => Err(ApiError::InvalidRequest(
Expand Down Expand Up @@ -214,7 +192,7 @@ impl<T: UrlProvider> SekaiClient<T> {
credential,
device_id: None,
},
&self.server,
&self.aes_config,
)?;

let request = self
Expand All @@ -228,7 +206,7 @@ impl<T: UrlProvider> SekaiClient<T> {
// parse body
let bytes = response.bytes().await?;
let auth_response: UserAuthResponse =
aes_msgpack::from_slice(&bytes, &self.server)?;
aes_msgpack::from_slice(&bytes, &self.aes_config)?;

// insert session token
self.headers
Expand Down Expand Up @@ -266,7 +244,7 @@ impl<T: UrlProvider> SekaiClient<T> {
Ok(response) => {
// parse body
let bytes = response.bytes().await?;
Ok(aes_msgpack::from_slice(&bytes, &self.server)?)
Ok(aes_msgpack::from_slice(&bytes, &self.aes_config)?)
}
Err(err) => match err.status() {
Some(StatusCode::FORBIDDEN) => Err(ApiError::InvalidRequest(
Expand Down Expand Up @@ -330,7 +308,7 @@ impl<T: UrlProvider> SekaiClient<T> {
Ok(response) => {
// parse body
let bytes = response.bytes().await?;
Ok(aes_msgpack::from_slice(&bytes, &self.server)?)
Ok(aes_msgpack::from_slice(&bytes, &self.aes_config)?)
}
Err(err) => Err(ApiError::InvalidRequest(err.to_string())),
}
Expand Down Expand Up @@ -376,7 +354,7 @@ impl<T: UrlProvider> SekaiClient<T> {
Ok(response) => {
// parse body
let bytes = response.bytes().await?;
Ok(aes_msgpack::from_slice(&bytes, &self.server)?)
Ok(aes_msgpack::from_slice(&bytes, &self.aes_config)?)
}
Err(err) => Err(ApiError::InvalidRequest(err.to_string())),
}
Expand All @@ -386,7 +364,7 @@ impl<T: UrlProvider> SekaiClient<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{api::url::test_provider::TestUrlProvider, models::api::AppVersion};
use crate::{api::url::test_provider::TestUrlProvider, enums::Server, models::api::AppVersion};

const SIGNATURE_COOKIE_VALUE: &str = "signature_cookie";

Expand All @@ -395,7 +373,7 @@ mod tests {
"3.9".to_string(),
"393939".to_string(),
Platform::Android,
Server::Japan,
Server::Japan.get_aes_config(),
TestUrlProvider::new(server_url),
)
.await
Expand Down Expand Up @@ -434,7 +412,7 @@ mod tests {
app_version_status: "available".into(),
}],
};
let mock_body = aes_msgpack::into_vec(&mock_system_info, &client.server).unwrap();
let mock_body = aes_msgpack::into_vec(&mock_system_info, &client.aes_config).unwrap();

let mock = server
.mock("GET", "/api/system")
Expand Down
1 change: 1 addition & 0 deletions src/api/url/global_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{SekaiHosts, UrlProvider};
use crate::{constants::url::sekai, models::enums::Platform};

#[derive(Clone)]
pub struct GlobalUrlProvider {
hosts: SekaiHosts,
}
Expand Down
1 change: 1 addition & 0 deletions src/api/url/japan_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{SekaiHosts, UrlProvider};
use crate::{constants::url::sekai, models::enums::Platform};

#[derive(Clone)]
pub struct JapanUrlProvider {
hosts: SekaiHosts,
}
Expand Down
3 changes: 2 additions & 1 deletion src/api/url/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod test_provider;
use crate::{constants::url::sekai, models::enums::Platform};

/// Stores the hosts that a SekaiClient should use when making requests.
#[derive(Clone)]
pub struct SekaiHosts {
issue: String,
game_version: String,
Expand Down Expand Up @@ -35,7 +36,7 @@ impl SekaiHosts {
}

/// Trait that provides urls for game endpoints.
pub trait UrlProvider {
pub trait UrlProvider: Clone {
fn issue_signature(&self) -> Option<String>;
fn game_version(&self, version: &str, hash: &str) -> String;
fn user(&self) -> String;
Expand Down
8 changes: 8 additions & 0 deletions src/api/url/server_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ use crate::models::enums::Platform;

use super::{global_provider::GlobalUrlProvider, japan_provider::JapanUrlProvider, UrlProvider};

#[derive(Clone)]
pub enum ServerUrlProvider {
Japan(JapanUrlProvider),
Global(GlobalUrlProvider),
}

impl Default for ServerUrlProvider {
/// Creates a default ServerUrlProvider using the JapanUrlProvider.
fn default() -> Self {
Self::Japan(JapanUrlProvider::default())
}
}

impl UrlProvider for ServerUrlProvider {
fn issue_signature(&self) -> Option<String> {
match self {
Expand Down
1 change: 1 addition & 0 deletions src/api/url/test_provider.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::UrlProvider;
use crate::{constants::url::sekai, models::enums::Platform};

#[derive(Clone)]
pub struct TestUrlProvider {
host: String,
}
Expand Down
6 changes: 3 additions & 3 deletions src/utils/apk_extractor.rs → src/apk_extractor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::ApkExtractError;
use crate::{error::ApkExtractError, Error};
use regex::Regex;
use std::{
fs::File,
Expand Down Expand Up @@ -43,7 +43,7 @@ where
}

/// Extracts the app hash from the APK.
pub fn extract(&mut self) -> Result<AppInfo, ApkExtractError> {
pub fn extract(&mut self) -> Result<AppInfo, Error> {
let mut zip = ZipArchive::new(&mut self.apk_buf)?;

// get the indexes where inner apks exist
Expand Down Expand Up @@ -114,7 +114,7 @@ fn extract_file_app_info(
fn extract_info_from_archive(
archive: &mut ZipArchive<impl Read + Seek>,
hash_re: &Regex,
) -> Result<AppInfo, ApkExtractError> {
) -> Result<AppInfo, Error> {
let mut version = None;
let mut hashes: Vec<String> = Vec::new();

Expand Down
91 changes: 91 additions & 0 deletions src/config/crypt_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::{models::enums::Server, utils::available_parallelism};

use super::{AesConfig, OptionalBuilder};

// constants
const DEFAULT_SERVER: Server = Server::Japan;
const DEFAULT_RECURSIVE: bool = false;
const DEFAULT_QUIET: bool = false;

/// Configuration for encryption and decryption.
pub struct CryptConfig {
pub aes_config: AesConfig,
pub concurrency: usize,
pub recursive: bool,
pub quiet: bool,
}

impl Default for CryptConfig {
fn default() -> Self {
Self {
aes_config: DEFAULT_SERVER.get_aes_config(),
concurrency: available_parallelism(),
recursive: DEFAULT_RECURSIVE,
quiet: DEFAULT_QUIET,
}
}
}

impl CryptConfig {
/// Create a default builder for the CryptConfig struct.
pub fn builder() -> CryptConfigBuilder {
CryptConfigBuilder::default()
}
}

/// Builder for CryptConfig
#[derive(Default)]
pub struct CryptConfigBuilder {
config: CryptConfig,
}

impl OptionalBuilder for CryptConfigBuilder {}

impl CryptConfigBuilder {
/// Sets the aes configuration.
///
/// By default, this will use the AesConfig for the Japan server.
pub fn aes(mut self, aes_config: AesConfig) -> Self {
self.config.aes_config = aes_config;
self
}

/// Sets the CryptConfig to use the configurations required by the provided server.
///
/// By default this will be the Japan server.
pub fn server(self, server: Server) -> Self {
self.aes(server.get_aes_config())
}

/// Sets the maximum number of tokio threads that can be used.
///
/// By default, this is the result of [`crate::utils::available_parallelism`],
/// the machine's available parallelism.
pub fn concurrency(mut self, concurrency: usize) -> Self {
self.config.concurrency = concurrency;
self
}

/// When performing operations on paths, whether to recursively operate
/// on that path.
///
/// By default, this is false.
pub fn recursive(mut self, recursive: bool) -> Self {
self.config.recursive = recursive;
self
}

/// When performing operations, whether to print information
/// regarding the progress of the operation.
///
/// By default, this is false.
pub fn quiet(mut self, quiet: bool) -> Self {
self.config.quiet = quiet;
self
}

/// Returns the CryptConfig that was constructed.
pub fn build(self) -> CryptConfig {
self.config
}
}
Loading

0 comments on commit d128333

Please sign in to comment.