feat: Customizable core options
This commit is contained in:
parent
bb2d33bef0
commit
11d2ff43ed
11 changed files with 129 additions and 50 deletions
|
|
@ -1,8 +1,12 @@
|
||||||
use onelauncher_core::{api::proxy::proxy_empty::ProxyEmpty, error::LauncherResult, initialize_core, store::State};
|
use discord_rich_presence::activity::Timestamps;
|
||||||
|
use onelauncher_core::{api::proxy::proxy_empty::ProxyEmpty, error::LauncherResult, initialize_core, store::{CoreOptions, State}};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> LauncherResult<()> {
|
async fn main() -> LauncherResult<()> {
|
||||||
initialize_core(ProxyEmpty::new()).await?;
|
initialize_core(CoreOptions {
|
||||||
|
discord_client_id: Some(String::from("1274084193193955408")),
|
||||||
|
..Default::default()
|
||||||
|
}, ProxyEmpty::new()).await?;
|
||||||
|
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
let Some(rpc) = &state.rpc else {
|
let Some(rpc) = &state.rpc else {
|
||||||
|
|
@ -10,11 +14,15 @@ async fn main() -> LauncherResult<()> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
rpc.set_message("Hello, Discord!").await;
|
rpc.set_message("Hello, Discord!", None).await;
|
||||||
println!("Set Discord message");
|
println!("Set Discord message");
|
||||||
|
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
rpc.set_message("This user is testing Discord RPC!").await;
|
rpc.set_message("New timestamp.", Some(Timestamps::new())).await;
|
||||||
|
println!("Set Discord message");
|
||||||
|
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
|
rpc.set_message("Reverted back to the old timestamp!", None).await;
|
||||||
println!("Set Discord message");
|
println!("Set Discord message");
|
||||||
|
|
||||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use onelauncher_core::{api::{java, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core};
|
use onelauncher_core::{api::{java, proxy::proxy_empty::ProxyEmpty}, error::LauncherResult, initialize_core, store::CoreOptions};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> LauncherResult<()> {
|
async fn main() -> LauncherResult<()> {
|
||||||
initialize_core(ProxyEmpty::new()).await?;
|
initialize_core(CoreOptions::default(), ProxyEmpty::new()).await?;
|
||||||
|
|
||||||
let java = java::locate_java().await?;
|
let java = java::locate_java().await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! Public constant variables and strings that are used throughout the launcher.
|
//! Public constant variables and strings that are used throughout the launcher.
|
||||||
|
|
||||||
// =========== Core Metadata ===========
|
|
||||||
/// The name of the launcher.
|
|
||||||
pub const NAME: &str = match option_env!("LAUNCHER_NAME") {
|
|
||||||
Some(name) => name,
|
|
||||||
None => "OneLauncher",
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The version of the launcher (from `../Cargo.toml` env).
|
|
||||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
// =========== Default Ingress ===========
|
// =========== Default Ingress ===========
|
||||||
/// The amount of attempts to fetch a file before a request fails.
|
|
||||||
pub const FETCH_ATTEMPTS: usize = 3;
|
|
||||||
/// The total ingress capacity of the CLI, provided to [`indicatif`].
|
/// The total ingress capacity of the CLI, provided to [`indicatif`].
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub const CLI_TOTAL_INGRESS: u64 = 1000;
|
pub const CLI_TOTAL_INGRESS: u64 = 1000;
|
||||||
|
|
@ -22,27 +10,17 @@ pub const CLI_TOTAL_INGRESS: u64 = 1000;
|
||||||
pub const DUMMY_REPLACE_NEWLINE: &str = "\n";
|
pub const DUMMY_REPLACE_NEWLINE: &str = "\n";
|
||||||
|
|
||||||
// =========== Authentication ===========
|
// =========== Authentication ===========
|
||||||
/// Our Discord RPC client ID.
|
|
||||||
pub const DISCORD_RPC_CLIENT_ID: &str = "1274084193193955408";
|
|
||||||
/// Our Microsoft client ID.
|
/// Our Microsoft client ID.
|
||||||
pub const MICROSOFT_CLIENT_ID: &str = "9eac3a4e-8cdd-43ef-863e-49cd601b1f03";
|
pub const MICROSOFT_CLIENT_ID: &str = "9eac3a4e-8cdd-43ef-863e-49cd601b1f03";
|
||||||
/// Mojang/Microsoft client ID.
|
|
||||||
pub const MINECRAFT_CLIENT_ID: &str = "00000000402b5328";
|
|
||||||
/// Mojang/Microsoft login redirect URI.
|
|
||||||
pub const MINECRAFT_REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf";
|
|
||||||
/// Mojang/Microsoft login xboxlive scopes to get tokens.
|
/// Mojang/Microsoft login xboxlive scopes to get tokens.
|
||||||
pub const MINECRAFT_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
pub const MINECRAFT_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
||||||
|
|
||||||
// =========== API ===========
|
// =========== API ===========
|
||||||
// !!! URLS must NOT have a trailing slash. !!!
|
// !!! URLS must NOT have a trailing slash. !!!
|
||||||
/// The Modrinth API base url.
|
/// The Modrinth API base url.
|
||||||
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2";
|
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com";
|
||||||
/// The Modrinth V3 API base url. Used for things like fetching information about organizations.
|
|
||||||
pub const MODRINTH_V3_API_URL: &str = "https://api.modrinth.com/v3";
|
|
||||||
/// The Curseforge API base url.
|
/// The Curseforge API base url.
|
||||||
pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com";
|
pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com";
|
||||||
/// The Curseforge API key. Reserved for use in Polyfrost projects only. Do not use in other projects without permission from the Polyfrost team.
|
|
||||||
pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe";
|
|
||||||
/// The Minecraft game ID on Curseforge.
|
/// The Minecraft game ID on Curseforge.
|
||||||
pub const CURSEFORGE_GAME_ID: u32 = 432;
|
pub const CURSEFORGE_GAME_ID: u32 = 432;
|
||||||
/// Our metadata API base url.
|
/// Our metadata API base url.
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,8 @@ pub enum LauncherError {
|
||||||
JavaError(#[from] crate::api::java::JavaError),
|
JavaError(#[from] crate::api::java::JavaError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
CryptoError(#[from] crate::utils::crypto::CryptoError),
|
CryptoError(#[from] crate::utils::crypto::CryptoError),
|
||||||
|
#[error(transparent)]
|
||||||
#[error("failed to connect to discord rpc socket")]
|
DiscordError(#[from] crate::store::discord::DiscordError),
|
||||||
DiscordError,
|
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerdeError(#[from] serde_json::Error),
|
SerdeError(#[from] serde_json::Error),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use api::proxy::LauncherProxy;
|
use api::proxy::LauncherProxy;
|
||||||
use store::{proxy::ProxyState, Dirs, State};
|
use store::{proxy::ProxyState, Core, CoreOptions, Dirs, State};
|
||||||
use error::LauncherResult;
|
use error::LauncherResult;
|
||||||
use logger::start_logger;
|
use logger::start_logger;
|
||||||
|
|
||||||
|
|
@ -14,7 +14,8 @@ mod logger;
|
||||||
pub use onelauncher_entity as entity;
|
pub use onelauncher_entity as entity;
|
||||||
pub use onelauncher_migration as migration;
|
pub use onelauncher_migration as migration;
|
||||||
|
|
||||||
pub async fn initialize_core(proxy_backend: impl LauncherProxy + 'static) -> LauncherResult<()> {
|
pub async fn initialize_core(options: CoreOptions, proxy_backend: impl LauncherProxy + 'static) -> LauncherResult<()> {
|
||||||
|
Core::initialize(options).await?;
|
||||||
Dirs::get().await?;
|
Dirs::get().await?;
|
||||||
start_logger().await;
|
start_logger().await;
|
||||||
|
|
||||||
|
|
@ -22,4 +23,4 @@ pub async fn initialize_core(proxy_backend: impl LauncherProxy + 'static) -> Lau
|
||||||
State::get().await?;
|
State::get().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
packages/core/src/store/core_options.rs
Normal file
68
packages/core/src/store/core_options.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
use crate::error::{LauncherError, LauncherResult};
|
||||||
|
|
||||||
|
static CORE_STATE: OnceCell<Arc<Core>> = OnceCell::const_new();
|
||||||
|
|
||||||
|
pub struct Core(CoreOptions);
|
||||||
|
|
||||||
|
/// Used for "customizable constants" for the core
|
||||||
|
pub struct CoreOptions {
|
||||||
|
pub discord_client_id: Option<String>,
|
||||||
|
pub launcher_name: String,
|
||||||
|
pub launcher_version: String,
|
||||||
|
pub launcher_website: String,
|
||||||
|
pub fetch_attempts: u8,
|
||||||
|
/// Default client id is the same as the one used by the official launcher
|
||||||
|
pub msa_client_id: String,
|
||||||
|
pub msa_redirect_uri: String,
|
||||||
|
pub curseforge_api_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CoreOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
discord_client_id: None,
|
||||||
|
launcher_name: String::from("Launcher"),
|
||||||
|
launcher_version: String::from(env!("CARGO_PKG_VERSION")),
|
||||||
|
launcher_website: String::from("https://polyfrost.org/"),
|
||||||
|
fetch_attempts: 3,
|
||||||
|
msa_client_id: String::from("00000000402b5328"),
|
||||||
|
msa_redirect_uri: String::from("https://login.live.com/oauth20_desktop.srf"),
|
||||||
|
curseforge_api_key: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Core {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(options: CoreOptions) -> Arc<Self> {
|
||||||
|
Arc::new(Self(options))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn initialize(options: CoreOptions) -> LauncherResult<()> {
|
||||||
|
CORE_STATE.get_or_try_init(|| async move {
|
||||||
|
Ok::<_, LauncherError>(Self::new(options))
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get() -> Arc<Self> {
|
||||||
|
CORE_STATE
|
||||||
|
.get()
|
||||||
|
.expect("core was not initialized")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Core {
|
||||||
|
type Target = CoreOptions;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,7 +2,9 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
use crate::{constants, utils, LauncherResult};
|
use crate::{utils, LauncherResult};
|
||||||
|
|
||||||
|
use super::Core;
|
||||||
|
|
||||||
/// The static [`OnceCell<RwLock<Dirs>>`] for storing the global directory state.
|
/// The static [`OnceCell<RwLock<Dirs>>`] for storing the global directory state.
|
||||||
/// Should be initialized as soon as possible (preferably before logging)
|
/// Should be initialized as soon as possible (preferably before logging)
|
||||||
|
|
@ -21,7 +23,7 @@ impl Dirs {
|
||||||
|
|
||||||
fn initialize() -> LauncherResult<Self> {
|
fn initialize() -> LauncherResult<Self> {
|
||||||
let base_dir = utils::io::env_path("LAUNCHER_DIR")
|
let base_dir = utils::io::env_path("LAUNCHER_DIR")
|
||||||
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
|
.or_else(|| Some(dirs::data_dir()?.join(Core::get().launcher_name.clone())))
|
||||||
.ok_or(DirectoryError::BaseDir)?;
|
.ok_or(DirectoryError::BaseDir)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,45 @@
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
|
|
||||||
use discord_rich_presence::{activity::{Activity, Assets, Button}, DiscordIpc, DiscordIpcClient};
|
use discord_rich_presence::{activity::{Activity, Assets, Button, Timestamps}, DiscordIpc, DiscordIpcClient};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{constants, error::{LauncherError, LauncherResult}};
|
use crate::error::LauncherResult;
|
||||||
|
|
||||||
|
use super::Core;
|
||||||
|
|
||||||
pub struct DiscordRPC {
|
pub struct DiscordRPC {
|
||||||
|
started_at: i64,
|
||||||
client: Arc<RwLock<DiscordIpcClient>>,
|
client: Arc<RwLock<DiscordIpcClient>>,
|
||||||
connected: Arc<AtomicBool>,
|
connected: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiscordRPC {
|
impl DiscordRPC {
|
||||||
pub fn initialize() -> LauncherResult<Self> {
|
pub fn initialize() -> LauncherResult<Self> {
|
||||||
let mut client = DiscordIpcClient::new(constants::DISCORD_RPC_CLIENT_ID)
|
let client_id = Core::get().discord_client_id.clone()
|
||||||
.map_err(|_| LauncherError::DiscordError)?;
|
.ok_or(DiscordError::MissingClientId)?;
|
||||||
|
|
||||||
|
let mut client = DiscordIpcClient::new(client_id.as_str())
|
||||||
|
.map_err(|_| DiscordError::ConnectError)?;
|
||||||
|
|
||||||
let connected = client.connect().is_ok();
|
let connected = client.connect().is_ok();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
started_at: chrono::Utc::now().timestamp(),
|
||||||
client: Arc::new(RwLock::new(client)),
|
client: Arc::new(RwLock::new(client)),
|
||||||
connected: Arc::new(AtomicBool::new(connected)),
|
connected: Arc::new(AtomicBool::new(connected)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_message(&self, msg: &str) -> bool {
|
pub async fn set_message(&self, msg: &str, timestamp: Option<Timestamps>) -> bool {
|
||||||
self.set_activity(
|
self.set_activity(
|
||||||
Activity::new()
|
Activity::new()
|
||||||
.state(msg)
|
.state(msg)
|
||||||
.buttons(vec![Button::new("Website", "https://polyfrost.org/")])
|
.buttons(
|
||||||
|
vec![Button::new("Website", "https://polyfrost.org/")]
|
||||||
|
)
|
||||||
|
.timestamps(
|
||||||
|
timestamp.unwrap_or_else(|| Timestamps::new().start(self.started_at))
|
||||||
|
)
|
||||||
.assets(
|
.assets(
|
||||||
Assets::new()
|
Assets::new()
|
||||||
.large_image("onelauncher_logo_512")
|
.large_image("onelauncher_logo_512")
|
||||||
|
|
@ -92,4 +104,12 @@ impl DiscordRPC {
|
||||||
fn set_connected(&self, connected: bool) {
|
fn set_connected(&self, connected: bool) {
|
||||||
self.connected.store(connected, std::sync::atomic::Ordering::Relaxed);
|
self.connected.store(connected, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum DiscordError {
|
||||||
|
#[error("Discord client ID is missing")]
|
||||||
|
MissingClientId,
|
||||||
|
#[error("couldn't connect to Discord IPC")]
|
||||||
|
ConnectError,
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,12 @@ pub mod discord;
|
||||||
pub mod ingress;
|
pub mod ingress;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod java;
|
pub mod java;
|
||||||
|
mod core_options;
|
||||||
mod dirs;
|
mod dirs;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub use dirs::*;
|
pub use dirs::*;
|
||||||
pub use settings::*;
|
pub use settings::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
pub use core_options::*;
|
||||||
|
|
@ -50,7 +50,7 @@ impl State {
|
||||||
let ingress_processor = IngressProcessor::new();
|
let ingress_processor = IngressProcessor::new();
|
||||||
let settings = Settings::new().await;
|
let settings = Settings::new().await;
|
||||||
let db = super::db::create_pool().await?;
|
let db = super::db::create_pool().await?;
|
||||||
|
|
||||||
let rpc = match DiscordRPC::initialize() {
|
let rpc = match DiscordRPC::initialize() {
|
||||||
Ok(rpc) => Some(rpc),
|
Ok(rpc) => Some(rpc),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use serde::Deserialize;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
use crate::{api::ingress::send_ingress, constants, error::LauncherResult, store::{ingress::IngressId, State}};
|
use crate::{api::ingress::send_ingress, error::LauncherResult, store::{ingress::IngressId, Core, State}};
|
||||||
|
|
||||||
use super::{crypto::{CryptoError, HashAlgorithm}, io};
|
use super::{crypto::{CryptoError, HashAlgorithm}, io};
|
||||||
|
|
||||||
|
|
@ -29,9 +29,10 @@ pub fn create_client() -> LauncherResult<reqwest::Client> {
|
||||||
.default_headers({
|
.default_headers({
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
let header = reqwest::header::HeaderValue::from_str(&format!(
|
let header = reqwest::header::HeaderValue::from_str(&format!(
|
||||||
"{}/{} (https://polyfrost.org)",
|
"{}/{} ({})",
|
||||||
constants::NAME,
|
Core::get().launcher_name,
|
||||||
constants::VERSION,
|
Core::get().launcher_version,
|
||||||
|
Core::get().launcher_website,
|
||||||
))
|
))
|
||||||
.expect("failed to build reqwest headers!");
|
.expect("failed to build reqwest headers!");
|
||||||
headers.insert(reqwest::header::USER_AGENT, header);
|
headers.insert(reqwest::header::USER_AGENT, header);
|
||||||
|
|
@ -53,7 +54,7 @@ pub async fn fetch_advanced(
|
||||||
let _permit = state.fetch_semaphore.0.acquire().await?;
|
let _permit = state.fetch_semaphore.0.acquire().await?;
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
|
|
||||||
for attempt in 0..constants::FETCH_ATTEMPTS {
|
for attempt in 0..Core::get().fetch_attempts {
|
||||||
let mut req = client.request(method.clone(), url);
|
let mut req = client.request(method.clone(), url);
|
||||||
|
|
||||||
if let Some(body) = body.clone() {
|
if let Some(body) = body.clone() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue