Compare commits

...

5 Commits

Author SHA1 Message Date
Sticks
3ffb8cf528 fix: fix updater not working correctly
Some checks failed
FanslySync Build & Test / FanslySync Test Runner (push) Failing after 23m7s
2025-04-30 10:51:47 -04:00
5be022c5a1 Merge pull request 'chore: Configure Team Hydra Dependency Updater' (#1) from dependencies/config into master
Some checks failed
FanslySync Build & Test / FanslySync Test Runner (push) Failing after 24m33s
Reviewed-on: #1
2025-04-28 23:55:51 +00:00
8eb8c68509 Update .github/workflows/deploy.yml
Some checks failed
FanslySync Build & Test / FanslySync Test Runner (push) Failing after 26m6s
2025-04-28 21:29:17 +00:00
147671af66 drop macos support for sync
All checks were successful
FanslySync Build & Test / FanslySync Test Runner (push) Successful in 11m47s
Signed-off-by: Tanner Sommers <tanner@teamhydra.dev>
2025-04-28 19:49:47 +00:00
Sticks
a0649911fe fix: make display_name an Option so parsing doesn't fail
Some checks failed
FanslySync Build & Test / FanslySync Test Runner (push) Failing after 24m12s
2025-04-28 10:51:08 -04:00
17 changed files with 1091 additions and 499 deletions

View File

@ -1,46 +1,45 @@
name: Release FanslySync name: Release FanslySync
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
app-slug:
type: string
description: Slug of the application
required: true
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env: env:
CN_APP_SLUG: ${{ github.event.inputs.app-slug }} CN_APPLICATION: "fansly-creator-bot/fansly-sync"
jobs: jobs:
draft: draft:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: create draft release - name: create draft release
uses: crabnebula-dev/cloud-release@v0.1.0 uses: crabnebula-dev/cloud-release@v0
with: with:
command: release draft ${{ env.CN_APP_SLUG }} --framework tauri command: release draft ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }} api-key: ${{ secrets.CN_API_KEY }}
build: build_desktop:
needs: draft needs: draft
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] include:
- os: ubuntu-22.04
- os: windows
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Install stable toolchain - name: Install stable toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
@ -48,48 +47,35 @@ jobs:
cache: true cache: true
- name: install Linux dependencies - name: install Linux dependencies
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-22.04'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y webkit2gtk-4.1
- name: Install x86_64-apple-darwin for mac and build FanslySync binaries - name: build Tauri app for Windows, Linux
if: matrix.os == 'macos-latest'
run: |
rustup target add x86_64-apple-darwin
npm i
npm run tauri build -- --target x86_64-apple-darwin
npm run tauri build -- --target aarch64-apple-darwin
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: build FanslySync app for Windows, Linux
if: matrix.os != 'macos-latest' if: matrix.os != 'macos-latest'
run: | run: |
npm i npm ci
npm run tauri build npm exec tauri build
env: env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: upload assets - name: upload assets
uses: crabnebula-dev/cloud-release@v0.1.0 uses: crabnebula-dev/cloud-release@v0
with: with:
command: release upload ${{ env.CN_APP_SLUG }} --framework tauri command: release upload ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }} api-key: ${{ secrets.CN_API_KEY }}
path: ./src-tauri
publish: publish:
needs: build needs: [build_desktop]
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: publish release - name: publish release
uses: crabnebula-dev/cloud-release@v0.1.0 uses: crabnebula-dev/cloud-release@v0
with: with:
command: release publish ${{ env.CN_APP_SLUG }} --framework tauri command: release publish ${{ env.CN_APPLICATION }} --framework tauri
api-key: ${{ secrets.CN_API_KEY }} api-key: ${{ secrets.CN_API_KEY }}

View File

@ -10,7 +10,13 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"tauri": "tauri" "tauri": "tauri",
"build_prod": "tauri build",
"publish": "npm run build_prod && npm run publish:makedraft && npm run publish:upload && npm run pubish:publishcn",
"publish_nobuild": "npm run publish:makedraft && npm run publish:upload && npm run pubish:publishcn",
"publish:makedraft": "cn release draft fansly-creator-bot/fansly-sync --framework tauri",
"publish:upload": "cn release upload fansly-creator-bot/fansly-sync --framework tauri",
"pubish:publishcn": "cn release publish fansly-creator-bot/fansly-sync --framework tauri"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.2.5", "@sveltejs/adapter-auto": "^3.2.5",
@ -44,7 +50,8 @@
"@tauri-apps/plugin-log": "^2.0.1", "@tauri-apps/plugin-log": "^2.0.1",
"@tauri-apps/plugin-notification": "^2.0.0", "@tauri-apps/plugin-notification": "^2.0.0",
"@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-updater": "^2.0.0", "@tauri-apps/plugin-process": "^2.2.1",
"@tauri-apps/plugin-updater": "^2.7.1",
"svelte-french-toast": "^1.2.0" "svelte-french-toast": "^1.2.0"
} }
} }

11
src-tauri/Cargo.lock generated
View File

@ -108,6 +108,7 @@ dependencies = [
"tauri-plugin-log", "tauri-plugin-log",
"tauri-plugin-notification", "tauri-plugin-notification",
"tauri-plugin-os", "tauri-plugin-os",
"tauri-plugin-process",
"tauri-plugin-single-instance", "tauri-plugin-single-instance",
"tauri-plugin-updater", "tauri-plugin-updater",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -4847,6 +4848,16 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
[[package]]
name = "tauri-plugin-process"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da5888533e802b6206b9685091f8714aa1f5266dc80051a82388449558b773"
dependencies = [
"tauri",
"tauri-plugin",
]
[[package]] [[package]]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.2.3" version = "2.2.3"

View File

@ -24,13 +24,14 @@ lazy_static = "1.5.0"
tokio = { version = "1.29.1", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] }
tokio-macros = "2.3.0" tokio-macros = "2.3.0"
tauri-plugin-os = { version = "2.2.1" } tauri-plugin-os = { version = "2.2.1" }
tauri-plugin-dialog = { version = "2.2.1" } tauri-plugin-dialog = "2.2.1"
tauri-plugin-clipboard-manager = { version = "2.2.1" } tauri-plugin-clipboard-manager = { version = "2.2.1" }
tauri-plugin-notification = { version = "2.2.1" } tauri-plugin-notification = { version = "2.2.1" }
tauri-plugin-updater = { version = "2.2.1" } tauri-plugin-updater = "2.2.1"
tauri-plugin-log = { version = "2.2.1" } tauri-plugin-log = { version = "2.2.1" }
log = "0.4.27" log = "0.4.27"
thiserror = "2.0.12" thiserror = "2.0.12"
tauri-plugin-process = "2"
[features] [features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
@ -41,3 +42,4 @@ custom-protocol = ["tauri/custom-protocol"]
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.3.0" tauri-plugin-autostart = "2.3.0"
tauri-plugin-single-instance = "2" tauri-plugin-single-instance = "2"
tauri-plugin-updater = "2"

View File

@ -2,7 +2,9 @@
"identifier": "migrated", "identifier": "migrated",
"description": "permissions that were migrated from v1", "description": "permissions that were migrated from v1",
"local": true, "local": true,
"windows": ["main"], "windows": [
"main"
],
"permissions": [ "permissions": [
"core:default", "core:default",
"dialog:allow-message", "dialog:allow-message",
@ -27,6 +29,9 @@
"notification:default", "notification:default",
"updater:default", "updater:default",
"log:default", "log:default",
"autostart:default" "autostart:default",
"process:default",
"process:allow-restart",
"process: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","autostart: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","process:default","process:allow-restart","process:default"]}}

View File

@ -2564,6 +2564,36 @@
"const": "os:deny-version", "const": "os:deny-version",
"markdownDescription": "Denies the version command without any pre-configured scope." "markdownDescription": "Denies the version command without any pre-configured scope."
}, },
{
"description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`",
"type": "string",
"const": "process:default",
"markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`"
},
{
"description": "Enables the exit command without any pre-configured scope.",
"type": "string",
"const": "process:allow-exit",
"markdownDescription": "Enables the exit command without any pre-configured scope."
},
{
"description": "Enables the restart command without any pre-configured scope.",
"type": "string",
"const": "process:allow-restart",
"markdownDescription": "Enables the restart command without any pre-configured scope."
},
{
"description": "Denies the exit command without any pre-configured scope.",
"type": "string",
"const": "process:deny-exit",
"markdownDescription": "Denies the exit command without any pre-configured scope."
},
{
"description": "Denies the restart command without any pre-configured scope.",
"type": "string",
"const": "process:deny-restart",
"markdownDescription": "Denies the restart command without any pre-configured scope."
},
{ {
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`", "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string", "type": "string",

File diff suppressed because it is too large Load Diff

View File

@ -2564,6 +2564,36 @@
"const": "os:deny-version", "const": "os:deny-version",
"markdownDescription": "Denies the version command without any pre-configured scope." "markdownDescription": "Denies the version command without any pre-configured scope."
}, },
{
"description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`",
"type": "string",
"const": "process:default",
"markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`"
},
{
"description": "Enables the exit command without any pre-configured scope.",
"type": "string",
"const": "process:allow-exit",
"markdownDescription": "Enables the exit command without any pre-configured scope."
},
{
"description": "Enables the restart command without any pre-configured scope.",
"type": "string",
"const": "process:allow-restart",
"markdownDescription": "Enables the restart command without any pre-configured scope."
},
{
"description": "Denies the exit command without any pre-configured scope.",
"type": "string",
"const": "process:deny-exit",
"markdownDescription": "Denies the exit command without any pre-configured scope."
},
{
"description": "Denies the restart command without any pre-configured scope.",
"type": "string",
"const": "process:deny-restart",
"markdownDescription": "Denies the restart command without any pre-configured scope."
},
{ {
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`", "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string", "type": "string",

View File

@ -117,15 +117,19 @@ impl Fansly {
.await?; .await?;
if !response.status().is_success() { if !response.status().is_success() {
eprintln!("[sync::process::get_profile] No successful response from API. Setting error state."); log::error!("[sync::process::get_profile] No successful response from API. Setting error state.");
return Err(response.error_for_status().unwrap_err()); return Err(response.error_for_status().unwrap_err());
} else { } else {
println!("[sync::process::get_profile] Got successful response from API."); log::info!("[sync::process::get_profile] Successfully fetched profile data.");
} }
let profile = response let profile = response
.json::<FanslyBaseResponse<FanslyAccountResponse>>() .json::<FanslyBaseResponse<FanslyAccountResponse>>()
.await?; .await?;
// Show the profile data
log::info!("[sync::process::get_profile] Profile data: {:?}", profile);
Ok(profile) Ok(profile)
} }
@ -157,12 +161,12 @@ impl Fansly {
let response = self.client.get(url).headers(headers).send().await?; let response = self.client.get(url).headers(headers).send().await?;
if !response.status().is_success() { if !response.status().is_success() {
eprintln!("[sync::process::fetch_followers] No successful response from API. Setting error state."); log::error!("[sync::process::fetch_followers] No successful response from API. Setting error state.");
return Err(response.error_for_status().unwrap_err()); return Err(response.error_for_status().unwrap_err());
} }
let followers: FanslyBaseResponseList<FanslyFollowersResponse> = response.json().await?; let followers: FanslyBaseResponseList<FanslyFollowersResponse> = response.json().await?;
println!( log::info!(
"[sync::process::fetch_followers] Got {} followers from API.", "[sync::process::fetch_followers] Got {} followers from API.",
followers.response.len() followers.response.len()
); );
@ -423,7 +427,7 @@ impl Fansly {
.await; .await;
// Every 10 requests, sleep for a bit to avoid rate limiting // Every 10 requests, sleep for a bit to avoid rate limiting
if total_requests % 10 == 0 { if total_requests % 50 == 0 {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
} }
@ -463,7 +467,7 @@ impl Fansly {
.await; .await;
// Every 10 requests, sleep for a bit to avoid rate limiting // Every 10 requests, sleep for a bit to avoid rate limiting
if total_requests % 10 == 0 { if total_requests % 50 == 0 {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
} }

View File

@ -57,6 +57,7 @@ fn handle_menu(app: &tauri::AppHandle, event: &tauri::menu::MenuEvent) {
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_process::init())
.setup(|app| { .setup(|app| {
// Setup menu items for the tray // Setup menu items for the tray
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;

View File

@ -89,7 +89,7 @@ pub struct Account {
pub id: String, pub id: String,
pub email: String, pub email: String,
pub username: String, pub username: String,
pub display_name: String, pub display_name: Option<String>,
pub flags: i64, pub flags: i64,
pub version: i64, pub version: i64,
pub created_at: i64, pub created_at: i64,

View File

@ -47,9 +47,11 @@
"plugins": { "plugins": {
"updater": { "updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFODZGRDI4NjBFMDQ1RUMKUldUc1JlQmdLUDJHTGdRdSt6dWFISXE0MThsa0tvUDA2RWdMSStjQ0J6NVBhdmU4ajRMMms4a1cK", "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFODZGRDI4NjBFMDQ1RUMKUldUc1JlQmdLUDJHTGdRdSt6dWFISXE0MThsa0tvUDA2RWdMSStjQ0J6NVBhdmU4ajRMMms4a1cK",
"active": true,
"endpoints": [ "endpoints": [
"https://cdn.crabnebula.app/update/fansly-creator-bot/fansly-sync/{{target}}-{{arch}}/{{current_version}}" "https://cdn.crabnebula.app/update/fansly-creator-bot/fansly-sync/{{target}}-{{arch}}/{{current_version}}"
] ],
"dialog": true
} }
}, },
"app": { "app": {

View File

@ -53,7 +53,7 @@ export interface AccountInfoResponse {
export interface AccountInfo { export interface AccountInfo {
id: string; id: string;
username: string; username: string;
displayName: string; displayName: string | null;
flags: number; flags: number;
version: number; version: number;
createdAt: number; createdAt: number;
@ -76,8 +76,6 @@ export interface AccountInfo {
banner: Avatar; banner: Avatar;
postLikes: number; postLikes: number;
streaming: Streaming; streaming: Streaming;
subscriptionTiers: SubscriptionTier[];
profileAccess: boolean;
} }
export interface SubscriptionTier { export interface SubscriptionTier {

View File

@ -97,9 +97,15 @@
); );
if (uploadErr) { if (uploadErr) {
error('[AutoSync] Upload failed'); error('[AutoSync] Upload failed');
sendNotification({ title: 'Auto Sync Failed', body: `Retry at ${nextTime}` }); sendNotification({
title: 'Auto Sync Failed',
body: `We failed to upload your data. ${uploadErr}. We will try again in ${config.sync_interval} hours.`
});
} else { } else {
sendNotification({ title: 'Auto Sync Successful', body: `Next at ${nextTime}` }); sendNotification({
title: 'Auto Sync Successful',
body: `Your data has been uploaded successfully. Next sync will be at ${nextTime}.`
});
} }
} }
@ -171,7 +177,13 @@
async function enableAutoSync() { async function enableAutoSync() {
if (!(await isEnabled())) { if (!(await isEnabled())) {
const confirm = await ask('Enable auto-start?'); const confirm = await ask(
"We've detected that auto-start is disabled. It's required for auto-sync to work. Do you want to enable it?",
{
title: 'Auto Sync Setup',
kind: 'info'
}
);
if (!confirm) return; if (!confirm) return;
await toast.promise(enable(), { await toast.promise(enable(), {
loading: 'Enabling...', loading: 'Enabling...',
@ -179,7 +191,11 @@
error: 'Failed' error: 'Failed'
}); });
} }
if (!config?.sync_token) return message('Set a sync token first.', { kind: 'error' }); if (!config?.sync_token)
return message('Set a sync token first.', {
kind: 'error',
title: 'Auto Sync Setup | Error'
});
config!.auto_sync_enabled = !config.auto_sync_enabled; config!.auto_sync_enabled = !config.auto_sync_enabled;
await saveConfig(); await saveConfig();
config.auto_sync_enabled ? scheduleAutoSync() : syncInterval && clearInterval(syncInterval); config.auto_sync_enabled ? scheduleAutoSync() : syncInterval && clearInterval(syncInterval);
@ -307,8 +323,8 @@
</span> </span>
</div> </div>
<p class="text-zinc-400 mb-4"> <p class="text-zinc-400 mb-4">
We'll automatically sync your data {config?.sync_interval} Your data will be automatically synced to your configured Discord server every {config?.sync_interval}
{config?.sync_interval === 1 ? 'hour' : 'hours'} to your configured discord server. {config?.sync_interval === 1 ? 'hour' : 'hours'}
</p> </p>
<div class="flex space-x-4"> <div class="flex space-x-4">
<button <button

View File

@ -54,6 +54,9 @@
unknown unknown
]; ];
if (err || !me?.success) { if (err || !me?.success) {
if (err) {
console.error('Error fetching account info:', err);
}
validationErrors.fanslyToken = validationErrors.fanslyToken =
'Authentication failed. Please check your token and try again.'; 'Authentication failed. Please check your token and try again.';
step = 1; step = 1;