auto sync, dep updates, other stuff

This commit is contained in:
2024-08-13 18:10:30 -05:00
parent 0577a0311a
commit c76ff2ac36
19 changed files with 1217 additions and 342 deletions

391
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.3"
version = "0.1.4"
description = "A Tauri App"
authors = ["SticksDev"]
license = "MIT"
@ -12,26 +12,29 @@ rust-version = "1.80"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-rc", features = [] }
tauri-build = { version = "2.0.0-rc.2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0-rc", features = [] }
tauri = { version = "2.0.0-rc.2", features = [] }
dirs = "5.0.1"
reqwest = { version = "0.11.18", features = ["json"] }
lazy_static = "1.5.0"
tokio = { version = "1.29.1", features = ["full"] }
tokio-macros = "2.3.0"
tauri-plugin-os = "2.0.0-alpha.2"
tauri-plugin-dialog = "2.0.0-alpha.2"
tauri-plugin-clipboard-manager = "2.0.0-alpha.2"
tauri-plugin-notification = "2.0.0-alpha.3"
tauri-plugin-updater = "2.0.0-alpha.3"
tauri-plugin-log = "2.0.0-rc.0"
tauri-plugin-os = { version = "2.0.0-rc" }
tauri-plugin-dialog = { version = "2.0.0-rc" }
tauri-plugin-clipboard-manager = { version = "2.0.0-rc" }
tauri-plugin-notification = { version = "2.0.0-rc" }
tauri-plugin-updater = { version = "2.0.0-rc" }
tauri-plugin-log = { version = "2.0.0-rc" }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.0.0-rc.0"

View File

@ -1,33 +1,32 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": [
"main"
],
"permissions": [
"core:default",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm",
"notification:default",
"os:allow-platform",
"os:allow-version",
"os:allow-os-type",
"os:allow-family",
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"core:app:allow-app-show",
"core:app:allow-app-hide",
"os:default",
"dialog:default",
"clipboard-manager:default",
"notification:default",
"updater:default",
"log:default"
]
}
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm",
"notification:default",
"os:allow-platform",
"os:allow-version",
"os:allow-os-type",
"os:allow-family",
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"core:app:allow-app-show",
"core:app:allow-app-hide",
"os:default",
"dialog:default",
"clipboard-manager:default",
"notification:default",
"updater:default",
"log:default",
"autostart:default"
]
}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm","notification:default","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","core:app:allow-app-show","core:app:allow-app-hide","os:default","dialog:default","clipboard-manager:default","notification:default","updater:default","log:default"]}}
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm","notification:default","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","core:app:allow-app-show","core:app:allow-app-hide","os:default","dialog:default","clipboard-manager:default","notification:default","updater:default","log:default","autostart:default"]}}

View File

@ -171,6 +171,55 @@
},
"Identifier": {
"oneOf": [
{
"description": "autostart:default -> This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n",
"type": "string",
"enum": [
"autostart:default"
]
},
{
"description": "autostart:allow-disable -> Enables the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-disable"
]
},
{
"description": "autostart:allow-enable -> Enables the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-enable"
]
},
{
"description": "autostart:allow-is-enabled -> Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-is-enabled"
]
},
{
"description": "autostart:deny-disable -> Denies the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-disable"
]
},
{
"description": "autostart:deny-enable -> Denies the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-enable"
]
},
{
"description": "autostart:deny-is-enabled -> Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-is-enabled"
]
},
{
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
"type": "string",

View File

@ -171,6 +171,55 @@
},
"Identifier": {
"oneOf": [
{
"description": "autostart:default -> This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n",
"type": "string",
"enum": [
"autostart:default"
]
},
{
"description": "autostart:allow-disable -> Enables the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-disable"
]
},
{
"description": "autostart:allow-enable -> Enables the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-enable"
]
},
{
"description": "autostart:allow-is-enabled -> Enables the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:allow-is-enabled"
]
},
{
"description": "autostart:deny-disable -> Denies the disable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-disable"
]
},
{
"description": "autostart:deny-enable -> Denies the enable command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-enable"
]
},
{
"description": "autostart:deny-is-enabled -> Denies the is_enabled command without any pre-configured scope.",
"type": "string",
"enum": [
"autostart:deny-is-enabled"
]
},
{
"description": "clipboard-manager:default -> No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
"type": "string",

View File

@ -3,6 +3,7 @@ use crate::{
structs::{FanslyAccountResponse, FanslyBaseResponse, SyncDataResponse},
};
use lazy_static::lazy_static;
use serde_json::Value;
use tokio::sync::Mutex;
lazy_static! {
@ -26,9 +27,34 @@ pub async fn fansly_get_me() -> Result<FanslyBaseResponse<FanslyAccountResponse>
}
#[tauri::command]
pub async fn fansly_sync() -> Result<SyncDataResponse, String> {
pub async fn fansly_sync(auto: bool) -> Result<SyncDataResponse, String> {
let fansly = FANSLY.lock().await;
let response = fansly.sync().await;
let response = fansly.sync(auto).await;
match response {
Ok(response) => Ok(response),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
pub async fn fansly_upload_auto_sync_data(
data: SyncDataResponse,
token: String,
) -> Result<(), String> {
let fansly: tokio::sync::MutexGuard<Fansly> = FANSLY.lock().await;
let response = fansly.upload_auto_sync_data(data, token).await;
match response {
Ok(_) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
#[tauri::command]
pub async fn fansly_check_sync_token(token: String) -> Result<Value, String> {
let fansly: tokio::sync::MutexGuard<Fansly> = FANSLY.lock().await;
let response = fansly.check_sync_token(token).await;
match response {
Ok(response) => Ok(response),

View File

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use crate::structs::{FanslyFollowersResponse, Subscription};
const CURRENT_VERSION: i32 = 1; // Set the current version of the config
const CURRENT_VERSION: i32 = 2; // Set the current version of the config
#[derive(Debug, Serialize, Deserialize)]
pub struct SyncData {
@ -18,6 +18,8 @@ pub struct Config {
pub version: i32, // Add a version field to the config (1, 2, 3, etc.)
pub is_first_run: bool,
pub fansly_token: String,
pub auto_sync_enabled: bool,
pub sync_token: String,
pub sync_interval: u64,
pub last_sync: u64,
pub last_sync_data: SyncData,
@ -31,6 +33,8 @@ impl Default for Config {
fansly_token: String::new(), // Fansly token is stored as a string
sync_interval: 1, // Every hour - sync interval is interpreted as hours
last_sync: 0, // Last sync time is stored as a UNIX timestamp
auto_sync_enabled: false, // Auto sync is disabled by default
sync_token: String::new(), // Sync token is stored as a string
last_sync_data: SyncData {
followers: Vec::new(),
subscribers: Vec::new(),
@ -42,17 +46,89 @@ impl Default for Config {
impl Config {
pub fn load_or_create(path: &Path) -> io::Result<Self> {
if path.exists() {
let mut config: Self = serde_json::from_str(std::fs::read_to_string(path)?.as_str())
.map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Could not parse config file: {}", e),
)
})?;
let config_result: Result<Self, _> =
serde_json::from_str(&std::fs::read_to_string(path)?);
let config = match config_result {
Ok(config) => config,
Err(_) => {
// Load raw JSON and attempt to parse it as a JSON object
let config_raw = std::fs::read_to_string(path)?;
let config_json: serde_json::Value = serde_json::from_str(&config_raw)?;
println!("[config::migrate] Migrating config file to latest version...");
println!(
"[config::migrate] [DEBUG] config is_object: {}",
config_json.is_object()
);
// Check if the JSON object is valid, if not, return an error
if !config_json.is_object() {
println!(
"[config::migrate] [ERROR] Found invalid JSON object in config file"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid JSON object",
));
}
// Get the version field from the JSON object
let version = config_json["version"].as_i64().unwrap_or(0) as i32;
// Check if the version field is a valid integer, if not, return an error
if version == 0 {
println!(
"[config::migrate] [ERROR] Found invalid version field in config JSON"
);
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Tried to migrate a config file, but found an invalid version field",
));
}
println!(
"[config::migrate] Found version field in config JSON: {}",
version
);
// Now create a new Config object and set the version field to the value we found
let mut config = Config::default();
config.version = version;
// Retain important fields from the JSON object
config.is_first_run = config_json["is_first_run"].as_bool().unwrap_or(true);
config.fansly_token = config_json["fansly_token"]
.as_str()
.unwrap_or("")
.to_string();
config.sync_token =
config_json["sync_token"].as_str().unwrap_or("").to_string();
config.sync_interval =
config_json["sync_interval"].as_i64().unwrap_or(1) as u64;
// Run migrations on the config object and save it
config = config.migrate()?;
config.save(path)?;
println!(
"[config::migrate] Successfully migrated config file to latest version"
);
// Recursively call load_or_create to load the migrated config
return Config::load_or_create(path);
}
};
if config.version != CURRENT_VERSION {
config = config.migrate()?;
config.save(path)?;
// Should have been migrated by now, error out because it wasn't
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Config version mismatch: expected {}, got {}. Please try removing the config file and restarting the application.",
CURRENT_VERSION, config.version
),
));
}
Ok(config)
} else {
let saved_config = Config::default().save(path);
@ -66,8 +142,12 @@ impl Config {
while self.version < CURRENT_VERSION {
self = match self.version {
1 => {
// If we're on version 1, migrate to version 2 (not implemented)
self.version += 1;
// Migrate from version 1 to version 2
self.version = 2;
self.auto_sync_enabled = false;
self.sync_token = String::new();
self.sync_interval = 1;
self
}
_ => {

View File

@ -5,6 +5,7 @@ use crate::structs::{
FanslySubscriptionsResponse, Subscription, SyncDataResponse,
};
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use serde_json::Value;
pub struct Fansly {
client: reqwest::Client,
@ -195,7 +196,73 @@ impl Fansly {
Ok(format!("https://paste.hep.gg/{}", key))
}
pub async fn sync(&self) -> Result<SyncDataResponse, String> {
pub async fn upload_auto_sync_data(
&self,
data: SyncDataResponse,
token: String,
) -> Result<(), reqwest::Error> {
let url = "http://localhost:5001/sync";
// Set our content type to application/json
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
// Add our auth token to the headers
headers.insert("Authorization", format!("{}", token).parse().unwrap());
let response = self
.client
.post(url)
.headers(headers)
.json(&data)
.send()
.await?;
if !response.status().is_success() {
eprintln!("[sync::process::upload_auto_sync_data] Failed to upload sync data.");
return Err(response.error_for_status().unwrap_err());
}
Ok(())
}
pub async fn check_sync_token(&self, token: String) -> Result<Value, reqwest::Error> {
// Check if the token is valid (GET /checkSyncToken with Authorization header)
// If it is, return the data back from the API
// If it isn't, return an error
let url = "http://localhost:5001/checkSyncToken";
// Set our content type to application/json
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::CONTENT_TYPE,
"application/json".parse().unwrap(),
);
// Add our auth token to the headers
headers.insert("Authorization", format!("{}", token).parse().unwrap());
let response = self.client.get(url).headers(headers).send().await;
// If successful, return the data, otherwise return an error
match response {
Ok(response) => {
if !response.status().is_success() {
eprintln!("[sync::process::check_sync_token] Failed to check sync token.");
return Err(response.error_for_status().unwrap_err());
}
let json: serde_json::Value = response.json().await?;
Ok(json)
}
Err(e) => Err(e),
}
}
pub async fn sync(&self, auto: bool) -> Result<SyncDataResponse, String> {
// Fetch profile
println!("[sync::process] Fetching profile...");
let profile = self.get_profile().await.map_err(|e| e.to_string())?;
@ -280,20 +347,29 @@ impl Fansly {
println!("[sync::process] Uploading sync data to paste.hep.gg for processing...");
// Upload sync data to paste.hep.gg
let paste_url = self
.upload_sync_data(SyncDataResponse {
followers: followers.clone(),
subscribers: subscribers.clone(),
if !auto {
let paste_url = self
.upload_sync_data(SyncDataResponse {
followers: followers.clone(),
subscribers: subscribers.clone(),
sync_data_url: "".to_string(),
})
.await
.map_err(|e| e.to_string())?;
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: paste_url,
})
} else {
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: "".to_string(),
})
.await
.map_err(|e| e.to_string())?;
// Return JSON of what we fetched
Ok(SyncDataResponse {
followers,
subscribers,
sync_data_url: paste_url,
})
}
}
}

View File

@ -9,8 +9,12 @@ use std::fs;
use std::io;
use commands::config::{get_config, init_config, save_config};
use commands::fansly::{fansly_get_me, fansly_set_token, fansly_sync};
use commands::fansly::{
fansly_check_sync_token, fansly_get_me, fansly_set_token, fansly_sync,
fansly_upload_auto_sync_data,
};
use commands::utils::quit;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_log::{Target, TargetKind};
fn get_log_path() -> io::Result<String> {
@ -31,6 +35,10 @@ fn get_log_path() -> io::Result<String> {
#[tokio::main]
async fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
None,
))
.plugin(tauri_plugin_log::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_clipboard_manager::init())
@ -55,7 +63,9 @@ async fn main() {
quit,
fansly_set_token,
fansly_get_me,
fansly_sync
fansly_sync,
fansly_upload_auto_sync_data,
fansly_check_sync_token
])
.run(tauri::generate_context!())
.expect("error while running tauri application");