mirror of
https://github.com/YuzuZensai/VRC-Circle.git
synced 2026-01-30 20:22:36 +00:00
✨ feat: Initial implementation
This commit is contained in:
771
src-tauri/src/lib.rs
Normal file
771
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,771 @@
|
||||
pub mod database_studio;
|
||||
pub mod http_common;
|
||||
pub mod log_manager;
|
||||
pub mod store;
|
||||
pub mod vrchat_api;
|
||||
pub mod vrchat_status;
|
||||
pub mod websocket;
|
||||
|
||||
use database_studio::{ColumnInfo, DatabaseStudio, QueryResult, TableInfo};
|
||||
use log::info;
|
||||
use log_manager::{LogEntry, LogManager};
|
||||
use std::sync::Arc;
|
||||
use store::{AccountStore, AppSettings, ImageCacheStore, SettingsStore, StoredAccount, UserStore};
|
||||
use tauri::{Manager, State};
|
||||
use tauri_specta::{Builder as SpectaBuilder, collect_commands};
|
||||
use tokio::sync::Mutex;
|
||||
use vrchat_api::{
|
||||
AgeVerificationStatus, AvatarPerformance, AvatarStyles, Badge, DeveloperType, DiscordDetails,
|
||||
FriendRequestStatus, GoogleDetails, LimitedAvatar, LimitedUserFriend, LimitedWorld,
|
||||
LoginCredentials, LoginResult, OrderOption, PastDisplayName, PerformanceRatings, ReleaseStatus,
|
||||
SteamDetails, TwoFactorMethod, UnityPackageSummary, UpdateStatusRequest, User, UserState,
|
||||
UserStatus, VRCError, VRChatClient,
|
||||
};
|
||||
use vrchat_status::{StatusPage, SystemStatus, VRChatStatusResponse};
|
||||
use websocket::VRChatWebSocket;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use specta_typescript::Typescript;
|
||||
|
||||
// Application State
|
||||
struct AppState {
|
||||
vrchat_client: Arc<Mutex<VRChatClient>>,
|
||||
account_store: AccountStore,
|
||||
websocket: Arc<Mutex<VRChatWebSocket>>,
|
||||
user_store: UserStore,
|
||||
settings_store: SettingsStore,
|
||||
#[allow(dead_code)]
|
||||
image_cache: Arc<ImageCacheStore>,
|
||||
}
|
||||
|
||||
// VRChat API Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_login(
|
||||
email: String,
|
||||
password: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<LoginResult, VRCError> {
|
||||
let credentials = LoginCredentials { email, password };
|
||||
let client = state.vrchat_client.lock().await;
|
||||
|
||||
client.login(&credentials).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_verify_2fa(
|
||||
code: String,
|
||||
method: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<bool, VRCError> {
|
||||
let two_fa_method = TwoFactorMethod::from_str(&method)
|
||||
.ok_or_else(|| VRCError::invalid_input(format!("Invalid 2FA method: {}", method)))?;
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
|
||||
client.verify_two_factor(&code, two_fa_method).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_get_current_user(state: State<'_, AppState>) -> Result<User, VRCError> {
|
||||
if let Some(user) = state.user_store.get_current_user().await {
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let user = client.get_current_user().await?;
|
||||
|
||||
state.user_store.set_current_user(user.clone()).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_update_status(
|
||||
status: UserStatus,
|
||||
status_description: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<User, VRCError> {
|
||||
let request = UpdateStatusRequest {
|
||||
status,
|
||||
status_description,
|
||||
};
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let user = client.update_status(&request).await?;
|
||||
drop(client);
|
||||
|
||||
state.user_store.set_current_user(user.clone()).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_logout(state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
let websocket = state.websocket.lock().await;
|
||||
websocket.stop().await;
|
||||
drop(websocket);
|
||||
|
||||
state.user_store.clear_all().await;
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client.logout().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn websocket_start(state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let (auth_cookie, two_factor_cookie) = client.export_cookies().await;
|
||||
drop(client);
|
||||
|
||||
log::debug!(
|
||||
"WebSocket starting with cookies - auth: {:?}, 2fa: {:?}",
|
||||
auth_cookie
|
||||
.as_ref()
|
||||
.map(|c| format!("{}...", &c.chars().take(20).collect::<String>())),
|
||||
two_factor_cookie
|
||||
.as_ref()
|
||||
.map(|c| format!("{}...", &c.chars().take(20).collect::<String>()))
|
||||
);
|
||||
|
||||
let websocket = state.websocket.lock().await;
|
||||
websocket.set_cookies(auth_cookie, two_factor_cookie).await;
|
||||
websocket.start().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn websocket_stop(state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
let websocket = state.websocket.lock().await;
|
||||
websocket.stop().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_get_online_friends(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<LimitedUserFriend>, VRCError> {
|
||||
let cached_friends = state.user_store.get_all_friends().await;
|
||||
|
||||
if !cached_friends.is_empty() {
|
||||
return Ok(cached_friends);
|
||||
}
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let friends = client.get_all_friends().await?;
|
||||
|
||||
state.user_store.set_friends(friends.clone()).await;
|
||||
Ok(friends)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_get_uploaded_worlds(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<LimitedWorld>, VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client.get_uploaded_worlds().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_get_uploaded_avatars(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<LimitedAvatar>, VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client.get_uploaded_avatars().await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_online_friends(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<LimitedUserFriend>, VRCError> {
|
||||
Ok(state.user_store.get_all_friends().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn check_image_cached(
|
||||
url: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Option<String>, VRCError> {
|
||||
if url.trim().is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let cache = state.image_cache.clone();
|
||||
if let Some(path) = cache.get_cached_path(&url).await {
|
||||
Ok(Some(path.to_string_lossy().to_string()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn cache_image(url: String, state: State<'_, AppState>) -> Result<String, VRCError> {
|
||||
if url.trim().is_empty() {
|
||||
return Err(VRCError::invalid_input("Image URL is empty".to_string()));
|
||||
}
|
||||
|
||||
info!("Caching image: {}", url);
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let (auth_cookie, two_factor_cookie) = client.export_cookies().await;
|
||||
drop(client);
|
||||
|
||||
let cookies = match (auth_cookie, two_factor_cookie) {
|
||||
(Some(auth), Some(two_fa)) => Some(format!("{}; {}", auth, two_fa)),
|
||||
(Some(auth), None) => Some(auth),
|
||||
(None, Some(two_fa)) => Some(two_fa),
|
||||
(None, None) => None,
|
||||
};
|
||||
|
||||
let cache = state.image_cache.clone();
|
||||
let path = cache
|
||||
.get_or_fetch(&url, cookies)
|
||||
.await
|
||||
.map_err(VRCError::unknown)?;
|
||||
info!("Image cached to: {}", path.display());
|
||||
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_cache_directory(state: State<'_, AppState>) -> Result<String, VRCError> {
|
||||
let cache = state.image_cache.clone();
|
||||
let dir = cache.get_cache_dir();
|
||||
Ok(dir.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_all_friends(state: State<'_, AppState>) -> Result<Vec<LimitedUserFriend>, VRCError> {
|
||||
Ok(state.user_store.get_all_friends().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_user(
|
||||
user_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Option<LimitedUserFriend>, VRCError> {
|
||||
Ok(state.user_store.get_user(&user_id).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_user_by_id(user_id: String, state: State<'_, AppState>) -> Result<User, VRCError> {
|
||||
if let Some(cached_user) = state.user_store.get_full_user(&user_id).await {
|
||||
log::debug!("get_user_by_id: Returning cached user for {}", user_id);
|
||||
return Ok(cached_user);
|
||||
}
|
||||
|
||||
log::info!("get_user_by_id: Fetching user {} from API", user_id);
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let user = client.get_user_by_id(&user_id).await?;
|
||||
drop(client);
|
||||
|
||||
log::info!("get_user_by_id: Successfully fetched user {}", user_id);
|
||||
|
||||
state.user_store.cache_full_user(user.clone()).await;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn is_friend(user_id: String, state: State<'_, AppState>) -> Result<bool, VRCError> {
|
||||
Ok(state.user_store.is_friend(&user_id).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn is_user_online(user_id: String, state: State<'_, AppState>) -> Result<bool, VRCError> {
|
||||
Ok(state.user_store.is_user_online(&user_id).await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_check_session(state: State<'_, AppState>) -> Result<bool, VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
Ok(client.has_valid_session().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn vrchat_clear_session(state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client.clear_cookies().await;
|
||||
drop(client);
|
||||
|
||||
state.user_store.clear_all().await;
|
||||
|
||||
state
|
||||
.account_store
|
||||
.clear_last_active_account()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Account Management Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn save_current_account(user: User, state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
let client = state.vrchat_client.lock().await;
|
||||
let (auth_cookie, two_factor_cookie) = client.export_cookies().await;
|
||||
|
||||
let avatar_override = user
|
||||
.user_icon
|
||||
.clone()
|
||||
.or_else(|| user.profile_pic_override.clone())
|
||||
.or_else(|| user.profile_pic_override_thumbnail.clone());
|
||||
|
||||
let avatar_fallback = user
|
||||
.current_avatar_thumbnail_image_url
|
||||
.clone()
|
||||
.or_else(|| user.current_avatar_image_url.clone());
|
||||
|
||||
let account = StoredAccount {
|
||||
user_id: user.id.clone(),
|
||||
username: user.username.clone(),
|
||||
display_name: user.display_name.clone(),
|
||||
avatar_url: avatar_override.clone().or_else(|| avatar_fallback.clone()),
|
||||
avatar_fallback_url: avatar_fallback,
|
||||
auth_cookie,
|
||||
two_factor_cookie,
|
||||
last_login: chrono::Utc::now().to_rfc3339(),
|
||||
};
|
||||
|
||||
state
|
||||
.account_store
|
||||
.save_account(account)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_all_accounts(state: State<'_, AppState>) -> Result<Vec<StoredAccount>, VRCError> {
|
||||
state
|
||||
.account_store
|
||||
.get_all_accounts()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn switch_account(user_id: String, state: State<'_, AppState>) -> Result<User, VRCError> {
|
||||
state.user_store.clear_all().await;
|
||||
|
||||
let account = state
|
||||
.account_store
|
||||
.get_account(&user_id)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?
|
||||
.ok_or_else(|| VRCError::invalid_input("Account not found"))?;
|
||||
|
||||
state
|
||||
.account_store
|
||||
.set_active_account(&user_id)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client
|
||||
.import_cookies(account.auth_cookie, account.two_factor_cookie)
|
||||
.await;
|
||||
|
||||
match client.get_current_user().await {
|
||||
Ok(user) => {
|
||||
state.user_store.set_current_user(user.clone()).await;
|
||||
Ok(user)
|
||||
}
|
||||
Err(err) => {
|
||||
// TODO: Handle if switching account fails
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn remove_account(user_id: String, state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
state
|
||||
.account_store
|
||||
.remove_account(&user_id)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn clear_all_accounts(state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
state
|
||||
.account_store
|
||||
.clear_all_accounts()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn load_last_account(state: State<'_, AppState>) -> Result<Option<User>, VRCError> {
|
||||
let account = match state
|
||||
.account_store
|
||||
.get_last_active_account()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?
|
||||
{
|
||||
Some(acc) => acc,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let client = state.vrchat_client.lock().await;
|
||||
client
|
||||
.import_cookies(account.auth_cookie, account.two_factor_cookie)
|
||||
.await;
|
||||
|
||||
match client.get_current_user().await {
|
||||
Ok(user) => Ok(Some(user)),
|
||||
Err(_) => {
|
||||
// TODO: Handle if last account cannot be loaded
|
||||
// client.clear_cookies().await;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_settings(state: State<'_, AppState>) -> Result<AppSettings, VRCError> {
|
||||
state
|
||||
.settings_store
|
||||
.get_settings()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn save_settings(settings: AppSettings, state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
info!(
|
||||
"Saving settings: developer_mode={}",
|
||||
settings.developer_mode
|
||||
);
|
||||
state
|
||||
.settings_store
|
||||
.save_settings(settings)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_developer_mode(state: State<'_, AppState>) -> Result<bool, VRCError> {
|
||||
state
|
||||
.settings_store
|
||||
.get_developer_mode()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn set_developer_mode(enabled: bool, state: State<'_, AppState>) -> Result<(), VRCError> {
|
||||
info!("Setting developer mode: {}", enabled);
|
||||
state
|
||||
.settings_store
|
||||
.set_developer_mode(enabled)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
// Log Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_backend_logs() -> Result<Vec<LogEntry>, VRCError> {
|
||||
LogManager::read_logs().map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn clear_backend_logs() -> Result<(), VRCError> {
|
||||
info!("Clearing backend logs");
|
||||
LogManager::clear_logs().map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn export_backend_logs() -> Result<String, VRCError> {
|
||||
LogManager::export_logs().map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
// VRChat Service Status Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn get_vrchat_status() -> Result<VRChatStatusResponse, String> {
|
||||
vrchat_status::fetch_vrchat_status().await
|
||||
}
|
||||
|
||||
// Database Studio Commands
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn db_list_tables() -> Result<Vec<TableInfo>, VRCError> {
|
||||
let studio = DatabaseStudio::new()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
studio.list_tables().await.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn db_get_table_schema(table_name: String) -> Result<Vec<ColumnInfo>, VRCError> {
|
||||
let studio = DatabaseStudio::new()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
studio
|
||||
.get_table_schema(&table_name)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn db_get_table_data(
|
||||
table_name: String,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
) -> Result<QueryResult, VRCError> {
|
||||
let studio = DatabaseStudio::new()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
studio
|
||||
.get_table_data(&table_name, limit, offset)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn db_get_table_count(table_name: String) -> Result<i32, VRCError> {
|
||||
let studio = DatabaseStudio::new()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
studio
|
||||
.get_table_count(&table_name)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
async fn db_execute_query(query: String) -> Result<QueryResult, VRCError> {
|
||||
let studio = DatabaseStudio::new()
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))?;
|
||||
|
||||
studio
|
||||
.execute_query(&query)
|
||||
.await
|
||||
.map_err(|e| VRCError::unknown(e))
|
||||
}
|
||||
|
||||
// Binding Generation
|
||||
fn create_specta_builder() -> SpectaBuilder<tauri::Wry> {
|
||||
SpectaBuilder::<tauri::Wry>::new()
|
||||
.commands(collect_commands![
|
||||
vrchat_login,
|
||||
vrchat_verify_2fa,
|
||||
vrchat_get_current_user,
|
||||
vrchat_update_status,
|
||||
vrchat_logout,
|
||||
vrchat_get_online_friends,
|
||||
vrchat_get_uploaded_worlds,
|
||||
vrchat_get_uploaded_avatars,
|
||||
get_online_friends,
|
||||
get_all_friends,
|
||||
get_user,
|
||||
get_user_by_id,
|
||||
is_friend,
|
||||
is_user_online,
|
||||
vrchat_check_session,
|
||||
vrchat_clear_session,
|
||||
websocket_start,
|
||||
websocket_stop,
|
||||
save_current_account,
|
||||
get_all_accounts,
|
||||
switch_account,
|
||||
remove_account,
|
||||
clear_all_accounts,
|
||||
load_last_account,
|
||||
get_settings,
|
||||
save_settings,
|
||||
get_developer_mode,
|
||||
set_developer_mode,
|
||||
get_backend_logs,
|
||||
clear_backend_logs,
|
||||
export_backend_logs,
|
||||
get_vrchat_status,
|
||||
db_list_tables,
|
||||
db_get_table_schema,
|
||||
db_get_table_data,
|
||||
db_get_table_count,
|
||||
db_execute_query,
|
||||
check_image_cached,
|
||||
cache_image,
|
||||
get_cache_directory,
|
||||
])
|
||||
// Core VRChat API types
|
||||
.typ::<VRCError>()
|
||||
.typ::<User>()
|
||||
.typ::<LoginResult>()
|
||||
.typ::<UpdateStatusRequest>()
|
||||
// Enum types
|
||||
.typ::<UserStatus>()
|
||||
.typ::<ReleaseStatus>()
|
||||
.typ::<DeveloperType>()
|
||||
.typ::<AgeVerificationStatus>()
|
||||
.typ::<FriendRequestStatus>()
|
||||
.typ::<UserState>()
|
||||
.typ::<PerformanceRatings>()
|
||||
.typ::<OrderOption>()
|
||||
// User-related types
|
||||
.typ::<LimitedUserFriend>()
|
||||
.typ::<PastDisplayName>()
|
||||
.typ::<Badge>()
|
||||
.typ::<DiscordDetails>()
|
||||
.typ::<GoogleDetails>()
|
||||
.typ::<SteamDetails>()
|
||||
// World types
|
||||
.typ::<LimitedWorld>()
|
||||
.typ::<UnityPackageSummary>()
|
||||
// Avatar types
|
||||
.typ::<LimitedAvatar>()
|
||||
.typ::<AvatarPerformance>()
|
||||
.typ::<AvatarStyles>()
|
||||
// Store types
|
||||
.typ::<StoredAccount>()
|
||||
.typ::<AppSettings>()
|
||||
// Log types
|
||||
.typ::<LogEntry>()
|
||||
// Database types
|
||||
.typ::<TableInfo>()
|
||||
.typ::<ColumnInfo>()
|
||||
.typ::<QueryResult>()
|
||||
// Status types
|
||||
.typ::<VRChatStatusResponse>()
|
||||
.typ::<SystemStatus>()
|
||||
.typ::<StatusPage>()
|
||||
}
|
||||
|
||||
pub fn generate_bindings() {
|
||||
use std::{fs, fs::File, io::Write};
|
||||
eprintln!("Generating TypeScript bindings...");
|
||||
|
||||
let specta_builder = create_specta_builder();
|
||||
let formatter = Typescript::default();
|
||||
let bindings = specta_builder
|
||||
.export_str(&formatter)
|
||||
.expect("Failed to generate TypeScript bindings");
|
||||
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let output_path = manifest_dir
|
||||
.join("..")
|
||||
.join("src")
|
||||
.join("types")
|
||||
.join("bindings.ts");
|
||||
|
||||
if let Some(parent) = output_path.parent() {
|
||||
fs::create_dir_all(parent).expect("Failed to create bindings directory");
|
||||
}
|
||||
|
||||
let mut file = File::create(&output_path).expect("Failed to create TypeScript bindings file");
|
||||
file.write_all(
|
||||
b"// @ts-nocheck\n// This file is auto-generated by Specta. Do not edit manually.\n\n",
|
||||
)
|
||||
.expect("Failed to write bindings header");
|
||||
file.write_all(bindings.as_bytes())
|
||||
.expect("Failed to write TypeScript bindings");
|
||||
|
||||
formatter.format(&output_path).ok();
|
||||
eprintln!(
|
||||
"Successfully generated bindings at {}",
|
||||
output_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// Application Entry Point
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let specta_builder = create_specta_builder();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
eprintln!("Auto-generating TypeScript bindings in development mode...");
|
||||
generate_bindings();
|
||||
}
|
||||
|
||||
let vrchat_client = VRChatClient::new().expect("Failed to create VRChat client");
|
||||
let account_store =
|
||||
tauri::async_runtime::block_on(AccountStore::new()).expect("Failed to create AccountStore");
|
||||
let settings_store = tauri::async_runtime::block_on(SettingsStore::new())
|
||||
.expect("Failed to create SettingsStore");
|
||||
let image_cache = Arc::new(
|
||||
tauri::async_runtime::block_on(ImageCacheStore::new())
|
||||
.expect("Failed to create ImageCacheStore"),
|
||||
);
|
||||
let user_store = UserStore::new();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.target(tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::Stdout,
|
||||
))
|
||||
.target(tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: Some("vrc-circle".to_string()),
|
||||
},
|
||||
))
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)
|
||||
.invoke_handler(specta_builder.invoke_handler())
|
||||
.setup(move |app| {
|
||||
// Initialize WebSocket with app handle and UserStore
|
||||
let websocket = VRChatWebSocket::new(app.handle().clone(), user_store.clone());
|
||||
|
||||
let app_state = AppState {
|
||||
vrchat_client: Arc::new(Mutex::new(vrchat_client)),
|
||||
account_store,
|
||||
websocket: Arc::new(Mutex::new(websocket)),
|
||||
user_store,
|
||||
settings_store,
|
||||
image_cache: image_cache.clone(),
|
||||
};
|
||||
|
||||
app.manage(app_state);
|
||||
specta_builder.mount_events(app);
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
Reference in New Issue
Block a user