Compare commits
3 Commits
master
...
wails-rewr
Author | SHA1 | Date | |
---|---|---|---|
![]() |
96abb94f21 | ||
![]() |
7aa2dee280 | ||
![]() |
e40f82f636 |
40
.github/workflows/build.yml
vendored
@ -1,40 +0,0 @@
|
||||
# run this action when the repository is pushed to
|
||||
on: [push]
|
||||
|
||||
# the name of our workflow
|
||||
name: FanslySync Build & Test
|
||||
|
||||
jobs:
|
||||
# a single job named test
|
||||
test:
|
||||
# the display name of the test job
|
||||
name: FanslySync Test Runner
|
||||
|
||||
# we want to run on the latest linux environment
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# the steps our job runs **in order**
|
||||
steps:
|
||||
# checkout the code on the workflow runner
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# install system dependencies that Tauri needs to compile on Linux.
|
||||
# note the extra dependencies for `tauri-driver` to run which are: `webkit2gtk-driver` and `xvfb`
|
||||
- name: Tauri dependencies
|
||||
run: >-
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
# install the latest Rust stable
|
||||
- name: Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
# Run our cargo commands in `src-tauri` directory
|
||||
- name: Build And Test
|
||||
run: >-
|
||||
cd src-tauri &&
|
||||
cargo test &&
|
||||
cargo build --release
|
||||
|
81
.github/workflows/deploy.yml
vendored
@ -1,81 +0,0 @@
|
||||
name: Release FanslySync
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CN_APPLICATION: "fansly-creator-bot/fansly-sync"
|
||||
|
||||
jobs:
|
||||
draft:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: create draft release
|
||||
uses: crabnebula-dev/cloud-release@v0
|
||||
with:
|
||||
command: release draft ${{ env.CN_APPLICATION }} --framework tauri
|
||||
api-key: ${{ secrets.CN_API_KEY }}
|
||||
|
||||
build_desktop:
|
||||
needs: draft
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
- os: windows
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
cache: true
|
||||
|
||||
- name: install Linux dependencies
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.1
|
||||
|
||||
- name: build Tauri app for Windows, Linux
|
||||
if: matrix.os != 'macos-latest'
|
||||
run: |
|
||||
npm ci
|
||||
npm exec tauri build
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: upload assets
|
||||
uses: crabnebula-dev/cloud-release@v0
|
||||
with:
|
||||
command: release upload ${{ env.CN_APPLICATION }} --framework tauri
|
||||
api-key: ${{ secrets.CN_API_KEY }}
|
||||
|
||||
publish:
|
||||
needs: [build_desktop]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: publish release
|
||||
uses: crabnebula-dev/cloud-release@v0
|
||||
with:
|
||||
command: release publish ${{ env.CN_APPLICATION }} --framework tauri
|
||||
api-key: ${{ secrets.CN_API_KEY }}
|
25
.gitignore
vendored
@ -1,24 +1,3 @@
|
||||
build/bin
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Pacakge lock
|
||||
package-lock.json
|
||||
frontend/dist
|
||||
|
8
.idea/.gitignore
generated
vendored
@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
12
.idea/FanslySync.iml
generated
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/src-tauri/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
63
.idea/codeStyles/Project.xml
generated
@ -1,63 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<VueCodeStyleSettings>
|
||||
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||
</VueCodeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="SOFT_MARGINS" value="100" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="SOFT_MARGINS" value="100" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="SOFT_MARGINS" value="100" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Vue">
|
||||
<option name="SOFT_MARGINS" value="100" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
5
.idea/misc.xml
generated
@ -1,5 +0,0 @@
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/FanslySync.iml" filepath="$PROJECT_DIR$/.idea/FanslySync.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/prettier.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -1,4 +0,0 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
# FanslySync Developer Documentation
|
||||
|
||||
Welcome. We'll tell you how to integrate with FanslySync.
|
||||
|
||||
## Our JSON Schema
|
||||
|
||||
FanslySync uses a JSON schema to sync data with 3rd party services. Here's an example of the JSON schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"followers": [{ "followerId": "123456" }],
|
||||
"subscribers": [
|
||||
// An array of subscriber objects. See below for the schema.
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Subscriber Schema
|
||||
|
||||
```json
|
||||
{
|
||||
{
|
||||
"id": "0", // The ID of the subscription, usually unique
|
||||
"historyId": "<history_id>", // The ID of the subscription history
|
||||
"subscriberId": "<user_id>", // The User ID of the subscriber
|
||||
"subscriptionTierId": "<tier_id>", // The ID of the subscription tier
|
||||
"subscriptionTierName": "<tier_name>", // The name of the subscription tier
|
||||
"subscriptionTierColor": "#2699f7", // The color of the subscription tier
|
||||
"planId": "0", // The ID of the subscription plan
|
||||
"promoId": "0", // The ID of the promotion, if applicable
|
||||
"giftCodeId": null, // The ID of the gift code, if applicable
|
||||
"paymentMethodId": "0", // The ID of the payment method
|
||||
"status": 3, // The status of the subscription. 3 = active, 4 = ?
|
||||
"price": 7000, // The price of the subscription, in cents
|
||||
"renewPrice": 7000, // The price of the subscription renewal, in cents
|
||||
"renewCorrelationId": "673162822363914240", // The correlation ID of the renewal
|
||||
"autoRenew": 1, // Whether the subscription is set to auto-renew
|
||||
"billingCycle": 30, // The billing cycle of the subscription, in days
|
||||
"duration": 30, // The duration of the subscription, in days
|
||||
"renewDate": 1721988883000, // The date the subscription will renew (UNIX timestamp)
|
||||
"version": 3, // The version of the subscription schema from fansly
|
||||
"createdAt": 1721988883000, // The date the subscription was created (UNIX timestamp)
|
||||
"updatedAt": 1721988883000, // The date the subscription was last updated (UNIX timestamp)
|
||||
"endsAt": 1724667283000, // The date the subscription will end (UNIX timestamp)
|
||||
"promoPrice": null, // The price of the subscription with the promotion, in cents
|
||||
"promoDuration": null, // The duration of the subscription with the promotion, in days
|
||||
"promoStatus": null, // The status of the promotion
|
||||
"promoStartsAt": null, // The date the promotion starts (UNIX timestamp)
|
||||
"promoEndsAt": null // The date the promotion ends (UNIX timestamp)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
# Closing
|
||||
|
||||
That's it! If you have any questions, feel free to reach out to us at our [support email](mailto:tanner@fanslycreatorbot.com) if you have any questions. We're happy to help you integrate with FanslySync.
|
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Sticks
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
36
README.md
@ -1,33 +1,19 @@
|
||||
<div align="center">
|
||||
<h1>FanslySync</h1>
|
||||
|
||||
A simple tool to sync your fansly data with 3rd party services, securely.
|
||||
|
||||
</div>
|
||||
# README
|
||||
|
||||
## About
|
||||
|
||||
FanslySync was created for the [Fansly Creator Bot](https://fanslycreatorbot.com) project to securely sync Fansly data with our service and others. We wanted to create a tool that would allow creators to sync their data with our service without having to give us their Fansly credentials. This tool is open source and can be used by anyone to sync their Fansly data with any service they want -- not just ours.
|
||||
This is the official Wails React-TS template.
|
||||
|
||||
## How it works
|
||||
You can configure the project by editing `wails.json`. More information about the project settings can be found
|
||||
here: https://wails.io/docs/reference/project-config
|
||||
|
||||
We use your Fansly API key to sync your data with our service. This way, you don't have to give us your Fansly credentials. While it does provide the same access to your data as your credentials would, it's a more secure way to sync your data. **Your API key is never stored outside of your own computer.**
|
||||
## Live Development
|
||||
|
||||
## Getting Started & Installation
|
||||
To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
|
||||
server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
|
||||
and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
|
||||
to this in your browser, and you can call your Go code from devtools.
|
||||
|
||||
We have a documentation page that explains how to get started with FanslySync. You can find it [here](https://docs.fanslycreatorbot.com/docs/for-creators/sync).
|
||||
## Building
|
||||
|
||||
If you aren't using Fansly Creator Bot, you can ignore the parts about setting up the bot and just follow the FanslySync installation instructions.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read our [CONTRIBUTING.md](CONTRIBUTING.md) file for more information.
|
||||
|
||||
## For Developers
|
||||
|
||||
If you are a 3rd party service that wants to integrate with FanslySync, please read our [DEVELOPERS.md](DEVELOPERS.md) file for more information.
|
||||
To build a redistributable, production mode package, use `wails build`.
|
||||
|
122
app.go
Normal file
@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"FanslySync/handlers"
|
||||
"FanslySync/structs"
|
||||
"FanslySync/utils"
|
||||
"context"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
ConfigManager handlers.ConfigManager
|
||||
AppConfig *structs.Config
|
||||
}
|
||||
|
||||
// NewApp creates a new App application struct
|
||||
func NewApp() *App {
|
||||
// Return an empty app
|
||||
return &App{}
|
||||
}
|
||||
|
||||
// startup is called when the app starts. The context is saved
|
||||
// so we can call the runtime methods
|
||||
func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
||||
a.ctx = ctx
|
||||
|
||||
// Setup config manager
|
||||
configPath, cfgErr := handlers.GetConfigPathForRuntime()
|
||||
if cfgErr != nil {
|
||||
// Show message box and quit
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not get config path.\n\nError: "+cfgErr.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(a.ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Create our config manager
|
||||
configMgr, configMgrCreateErr := handlers.NewFileConfigManager(configPath)
|
||||
|
||||
if configMgrCreateErr != nil {
|
||||
// Show message box and quit
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not create config manager.\n\nError: "+configMgrCreateErr.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
|
||||
runtime.Quit(a.ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the config manager
|
||||
a.ConfigManager = configMgr
|
||||
|
||||
logger.Info("initializing FanslySync...")
|
||||
|
||||
// Check our config path to see if it was set correctly. Will not contain FailedConfigPathFetch
|
||||
// Do we have an old config file?
|
||||
shouldMigrate, err := a.ConfigManager.ShouldMigrateOldAppConfig()
|
||||
if err != nil {
|
||||
// Show the error in a message box
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not check for old config file.\n\nError: "+err.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(a.ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if shouldMigrate {
|
||||
logger.Info("migrating old config file...")
|
||||
// Migrate the old config file
|
||||
err := a.ConfigManager.MigrateOldAppConfig()
|
||||
if err != nil {
|
||||
// Show the error in a message box
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not migrate old config file.\n\nError: "+err.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(a.ctx)
|
||||
return
|
||||
} else {
|
||||
// Show success message
|
||||
logger.Info("old config file migrate ok")
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Notice", "We've detected an old config file (app version < 2.x and below).\n\nThe old config file has been migrated to the new format for you automatically, and the old config file has been deleted.\n\nPlease check your settings to ensure everything is correct.", utils.WithDialogType(runtime.InfoDialog))
|
||||
|
||||
// Now grab the new config
|
||||
cfg, err := a.ConfigManager.GetConfig(false)
|
||||
if err != nil {
|
||||
// Show the error in a message box
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not load config file.\n\nError: "+err.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(a.ctx)
|
||||
}
|
||||
|
||||
// Set the config
|
||||
a.AppConfig = cfg
|
||||
}
|
||||
} else {
|
||||
// Load config as normal
|
||||
logger.Info("loading config file...")
|
||||
cfg, err := a.ConfigManager.LoadConfigOrCreate()
|
||||
if err != nil {
|
||||
// Show the error in a message box
|
||||
utils.ShowMessageBox(a.ctx, "FanslySync | Initialization Error", "Could not load config file.\n\nError: "+err.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(a.ctx)
|
||||
}
|
||||
|
||||
// Set the config
|
||||
logger.Info("config file loaded ok")
|
||||
a.AppConfig = cfg
|
||||
}
|
||||
|
||||
logger.Info("FanslySync initialized successfully")
|
||||
}
|
||||
|
||||
// Greet returns a greeting for the given name
|
||||
func (a *App) Greet(token string) string {
|
||||
// Create fansly API instance
|
||||
fanslyAPI, createErr := handlers.NewFanslyAPIController(token)
|
||||
|
||||
if createErr != nil {
|
||||
return "Failed to create Fansly API instance: " + createErr.Error()
|
||||
}
|
||||
|
||||
// Sync
|
||||
fanslyAPI.Sync(a.ctx, token, false)
|
||||
|
||||
return "Sync dispatched, check the logs for more info."
|
||||
}
|
35
build/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Build Directory
|
||||
|
||||
The build directory is used to house all the build files and assets for your application.
|
||||
|
||||
The structure is:
|
||||
|
||||
* bin - Output directory
|
||||
* darwin - macOS specific files
|
||||
* windows - Windows specific files
|
||||
|
||||
## Mac
|
||||
|
||||
The `darwin` directory holds files specific to Mac builds.
|
||||
These may be customised and used as part of the build. To return these files to the default state, simply delete them
|
||||
and
|
||||
build with `wails build`.
|
||||
|
||||
The directory contains the following files:
|
||||
|
||||
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
|
||||
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
|
||||
|
||||
## Windows
|
||||
|
||||
The `windows` directory contains the manifest and rc files used when building with `wails build`.
|
||||
These may be customised for your application. To return these files to the default state, simply delete them and
|
||||
build with `wails build`.
|
||||
|
||||
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
|
||||
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
|
||||
will be created using the `appicon.png` file in the build directory.
|
||||
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
|
||||
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
|
||||
as well as the application itself (right click the exe -> properties -> details)
|
||||
- `wails.exe.manifest` - The main application manifest file.
|
BIN
build/appicon.png
Normal file
After Width: | Height: | Size: 130 KiB |
68
build/darwin/Info.dev.plist
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{.Info.ProductName}}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>{{.OutputFilename}}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.{{.Name}}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>{{.Info.Comments}}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>iconfile</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>{{.Info.Copyright}}</string>
|
||||
{{if .Info.FileAssociations}}
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
{{range .Info.FileAssociations}}
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>{{.Ext}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>{{.Name}}</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>{{.IconName}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
{{if .Info.Protocols}}
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
{{range .Info.Protocols}}
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.wails.{{.Scheme}}</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>{{.Scheme}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
63
build/darwin/Info.plist
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{{.Info.ProductName}}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>{{.OutputFilename}}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.wails.{{.Name}}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>{{.Info.Comments}}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{{.Info.ProductVersion}}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>iconfile</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>{{.Info.Copyright}}</string>
|
||||
{{if .Info.FileAssociations}}
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
{{range .Info.FileAssociations}}
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>{{.Ext}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>{{.Name}}</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>{{.IconName}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
{{if .Info.Protocols}}
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
{{range .Info.Protocols}}
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>com.wails.{{.Scheme}}</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>{{.Scheme}}</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>{{.Role}}</string>
|
||||
</dict>
|
||||
{{end}}
|
||||
</array>
|
||||
{{end}}
|
||||
</dict>
|
||||
</plist>
|
BIN
build/windows/icon.ico
Normal file
After Width: | Height: | Size: 20 KiB |
15
build/windows/info.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"fixed": {
|
||||
"file_version": "{{.Info.ProductVersion}}"
|
||||
},
|
||||
"info": {
|
||||
"0000": {
|
||||
"ProductVersion": "{{.Info.ProductVersion}}",
|
||||
"CompanyName": "{{.Info.CompanyName}}",
|
||||
"FileDescription": "{{.Info.ProductName}}",
|
||||
"LegalCopyright": "{{.Info.Copyright}}",
|
||||
"ProductName": "{{.Info.ProductName}}",
|
||||
"Comments": "{{.Info.Comments}}"
|
||||
}
|
||||
}
|
||||
}
|
114
build/windows/installer/project.nsi
Normal file
@ -0,0 +1,114 @@
|
||||
Unicode true
|
||||
|
||||
####
|
||||
## Please note: Template replacements don't work in this file. They are provided with default defines like
|
||||
## mentioned underneath.
|
||||
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
|
||||
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
|
||||
## from outside of Wails for debugging and development of the installer.
|
||||
##
|
||||
## For development first make a wails nsis build to populate the "wails_tools.nsh":
|
||||
## > wails build --target windows/amd64 --nsis
|
||||
## Then you can call makensis on this file with specifying the path to your binary:
|
||||
## For a AMD64 only installer:
|
||||
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe
|
||||
## For a ARM64 only installer:
|
||||
## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe
|
||||
## For a installer with both architectures:
|
||||
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
|
||||
####
|
||||
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
|
||||
####
|
||||
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
|
||||
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
|
||||
## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}"
|
||||
## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}"
|
||||
## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}"
|
||||
###
|
||||
## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe"
|
||||
## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
||||
####
|
||||
## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html
|
||||
####
|
||||
## Include the wails tools
|
||||
####
|
||||
!include "wails_tools.nsh"
|
||||
|
||||
# The version information for this two must consist of 4 parts
|
||||
VIProductVersion "${INFO_PRODUCTVERSION}.0"
|
||||
VIFileVersion "${INFO_PRODUCTVERSION}.0"
|
||||
|
||||
VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}"
|
||||
VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer"
|
||||
VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}"
|
||||
VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}"
|
||||
VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}"
|
||||
VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}"
|
||||
|
||||
# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware
|
||||
ManifestDPIAware true
|
||||
|
||||
!include "MUI.nsh"
|
||||
|
||||
!define MUI_ICON "..\icon.ico"
|
||||
!define MUI_UNICON "..\icon.ico"
|
||||
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
|
||||
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
|
||||
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
|
||||
|
||||
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
|
||||
# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer
|
||||
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
|
||||
!insertmacro MUI_PAGE_INSTFILES # Installing page.
|
||||
!insertmacro MUI_PAGE_FINISH # Finished installation page.
|
||||
|
||||
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
|
||||
|
||||
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
|
||||
#!uninstfinalize 'signtool --file "%1"'
|
||||
#!finalize 'signtool --file "%1"'
|
||||
|
||||
Name "${INFO_PRODUCTNAME}"
|
||||
OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
|
||||
InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder).
|
||||
ShowInstDetails show # This will always show the installation details.
|
||||
|
||||
Function .onInit
|
||||
!insertmacro wails.checkArchitecture
|
||||
FunctionEnd
|
||||
|
||||
Section
|
||||
!insertmacro wails.setShellContext
|
||||
|
||||
!insertmacro wails.webview2runtime
|
||||
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
!insertmacro wails.files
|
||||
|
||||
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
|
||||
!insertmacro wails.associateFiles
|
||||
!insertmacro wails.associateCustomProtocols
|
||||
|
||||
!insertmacro wails.writeUninstaller
|
||||
SectionEnd
|
||||
|
||||
Section "uninstall"
|
||||
!insertmacro wails.setShellContext
|
||||
|
||||
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
|
||||
|
||||
RMDir /r $INSTDIR
|
||||
|
||||
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
|
||||
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
|
||||
|
||||
!insertmacro wails.unassociateFiles
|
||||
!insertmacro wails.unassociateCustomProtocols
|
||||
|
||||
!insertmacro wails.deleteUninstaller
|
||||
SectionEnd
|
249
build/windows/installer/wails_tools.nsh
Normal file
@ -0,0 +1,249 @@
|
||||
# DO NOT EDIT - Generated automatically by `wails build`
|
||||
|
||||
!include "x64.nsh"
|
||||
!include "WinVer.nsh"
|
||||
!include "FileFunc.nsh"
|
||||
|
||||
!ifndef INFO_PROJECTNAME
|
||||
!define INFO_PROJECTNAME "{{.Name}}"
|
||||
!endif
|
||||
!ifndef INFO_COMPANYNAME
|
||||
!define INFO_COMPANYNAME "{{.Info.CompanyName}}"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTNAME
|
||||
!define INFO_PRODUCTNAME "{{.Info.ProductName}}"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "{{.Info.Copyright}}"
|
||||
!endif
|
||||
!ifndef PRODUCT_EXECUTABLE
|
||||
!define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe"
|
||||
!endif
|
||||
!ifndef UNINST_KEY_NAME
|
||||
!define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}"
|
||||
!endif
|
||||
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
|
||||
|
||||
!ifndef REQUEST_EXECUTION_LEVEL
|
||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||
!endif
|
||||
|
||||
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
|
||||
!ifdef ARG_WAILS_AMD64_BINARY
|
||||
!define SUPPORTS_AMD64
|
||||
!endif
|
||||
|
||||
!ifdef ARG_WAILS_ARM64_BINARY
|
||||
!define SUPPORTS_ARM64
|
||||
!endif
|
||||
|
||||
!ifdef SUPPORTS_AMD64
|
||||
!ifdef SUPPORTS_ARM64
|
||||
!define ARCH "amd64_arm64"
|
||||
!else
|
||||
!define ARCH "amd64"
|
||||
!endif
|
||||
!else
|
||||
!ifdef SUPPORTS_ARM64
|
||||
!define ARCH "arm64"
|
||||
!else
|
||||
!error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY"
|
||||
!endif
|
||||
!endif
|
||||
|
||||
!macro wails.checkArchitecture
|
||||
!ifndef WAILS_WIN10_REQUIRED
|
||||
!define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later."
|
||||
!endif
|
||||
|
||||
!ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED
|
||||
!define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}"
|
||||
!endif
|
||||
|
||||
${If} ${AtLeastWin10}
|
||||
!ifdef SUPPORTS_AMD64
|
||||
${if} ${IsNativeAMD64}
|
||||
Goto ok
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
!ifdef SUPPORTS_ARM64
|
||||
${if} ${IsNativeARM64}
|
||||
Goto ok
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
IfSilent silentArch notSilentArch
|
||||
silentArch:
|
||||
SetErrorLevel 65
|
||||
Abort
|
||||
notSilentArch:
|
||||
MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}"
|
||||
Quit
|
||||
${else}
|
||||
IfSilent silentWin notSilentWin
|
||||
silentWin:
|
||||
SetErrorLevel 64
|
||||
Abort
|
||||
notSilentWin:
|
||||
MessageBox MB_OK "${WAILS_WIN10_REQUIRED}"
|
||||
Quit
|
||||
${EndIf}
|
||||
|
||||
ok:
|
||||
!macroend
|
||||
|
||||
!macro wails.files
|
||||
!ifdef SUPPORTS_AMD64
|
||||
${if} ${IsNativeAMD64}
|
||||
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}"
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
!ifdef SUPPORTS_ARM64
|
||||
${if} ${IsNativeARM64}
|
||||
File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}"
|
||||
${EndIf}
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
!macro wails.writeUninstaller
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!macroend
|
||||
|
||||
!macro wails.deleteUninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
!macroend
|
||||
|
||||
!macro wails.setShellContext
|
||||
${If} ${REQUEST_EXECUTION_LEVEL} == "admin"
|
||||
SetShellVarContext all
|
||||
${else}
|
||||
SetShellVarContext current
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
# Install webview2 by launching the bootstrapper
|
||||
# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment
|
||||
!macro wails.webview2runtime
|
||||
!ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT
|
||||
!define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime"
|
||||
!endif
|
||||
|
||||
SetRegView 64
|
||||
# If the admin key exists and is not empty then webview2 is already installed
|
||||
ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||
${If} $0 != ""
|
||||
Goto ok
|
||||
${EndIf}
|
||||
|
||||
${If} ${REQUEST_EXECUTION_LEVEL} == "user"
|
||||
# If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed
|
||||
ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
|
||||
${If} $0 != ""
|
||||
Goto ok
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
SetDetailsPrint both
|
||||
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
|
||||
SetDetailsPrint listonly
|
||||
|
||||
InitPluginsDir
|
||||
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
||||
SetOutPath "$pluginsdir\webview2bootstrapper"
|
||||
File "tmp\MicrosoftEdgeWebview2Setup.exe"
|
||||
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
||||
|
||||
SetDetailsPrint both
|
||||
ok:
|
||||
!macroend
|
||||
|
||||
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
|
||||
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
||||
; Backup the previously associated file class
|
||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
||||
!macroend
|
||||
|
||||
!macro APP_UNASSOCIATE EXT FILECLASS
|
||||
; Backup the previously associated file class
|
||||
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
||||
|
||||
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
||||
!macroend
|
||||
|
||||
!macro wails.associateFiles
|
||||
; Create file associations
|
||||
{{range .Info.FileAssociations}}
|
||||
!insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||
|
||||
File "..\{{.IconName}}.ico"
|
||||
{{end}}
|
||||
!macroend
|
||||
|
||||
!macro wails.unassociateFiles
|
||||
; Delete app associations
|
||||
{{range .Info.FileAssociations}}
|
||||
!insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
|
||||
|
||||
Delete "$INSTDIR\{{.IconName}}.ico"
|
||||
{{end}}
|
||||
!macroend
|
||||
|
||||
!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND
|
||||
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}"
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}"
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" ""
|
||||
WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}"
|
||||
!macroend
|
||||
|
||||
!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL
|
||||
DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}"
|
||||
!macroend
|
||||
|
||||
!macro wails.associateCustomProtocols
|
||||
; Create custom protocols associations
|
||||
{{range .Info.Protocols}}
|
||||
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||
|
||||
{{end}}
|
||||
!macroend
|
||||
|
||||
!macro wails.unassociateCustomProtocols
|
||||
; Delete app custom protocol associations
|
||||
{{range .Info.Protocols}}
|
||||
!insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}"
|
||||
{{end}}
|
||||
!macroend
|
15
build/windows/wails.exe.manifest
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
@ -1,33 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import globals from 'globals';
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||
}
|
||||
];
|
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>FanslySync</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./src/main.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
22
frontend/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@vitejs/plugin-react": "^2.0.1",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
1
frontend/package.json.md5
Normal file
@ -0,0 +1 @@
|
||||
f26173c7304a0bf8ea5c86eb567e7db2
|
892
frontend/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,892 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.0.17
|
||||
version: 18.3.21
|
||||
'@types/react-dom':
|
||||
specifier: ^18.0.6
|
||||
version: 18.3.7(@types/react@18.3.21)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^2.0.1
|
||||
version: 2.2.0(vite@3.2.11)
|
||||
typescript:
|
||||
specifier: ^4.6.4
|
||||
version: 4.9.5
|
||||
vite:
|
||||
specifier: ^3.0.7
|
||||
version: 3.2.11
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/compat-data@7.27.2':
|
||||
resolution: {integrity: sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/core@7.27.1':
|
||||
resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/generator@7.27.1':
|
||||
resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-annotate-as-pure@7.27.1':
|
||||
resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-compilation-targets@7.27.2':
|
||||
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-module-imports@7.27.1':
|
||||
resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-module-transforms@7.27.1':
|
||||
resolution: {integrity: sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
|
||||
'@babel/helper-plugin-utils@7.27.1':
|
||||
resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-string-parser@7.27.1':
|
||||
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1':
|
||||
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1':
|
||||
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helpers@7.27.1':
|
||||
resolution: {integrity: sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.27.2':
|
||||
resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.27.1':
|
||||
resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-development@7.27.1':
|
||||
resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.27.1':
|
||||
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.27.1':
|
||||
resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.27.1':
|
||||
resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/traverse@7.27.1':
|
||||
resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.27.1':
|
||||
resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@esbuild/android-arm@0.15.18':
|
||||
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/linux-loong64@0.15.18':
|
||||
resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/set-array@1.2.1':
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0':
|
||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@types/prop-types@15.7.14':
|
||||
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||
|
||||
'@types/react-dom@18.3.7':
|
||||
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.0.0
|
||||
|
||||
'@types/react@18.3.21':
|
||||
resolution: {integrity: sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw==}
|
||||
|
||||
'@vitejs/plugin-react@2.2.0':
|
||||
resolution: {integrity: sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0
|
||||
|
||||
browserslist@4.24.5:
|
||||
resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
caniuse-lite@1.0.30001718:
|
||||
resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
electron-to-chromium@1.5.155:
|
||||
resolution: {integrity: sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==}
|
||||
|
||||
esbuild-android-64@0.15.18:
|
||||
resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
esbuild-android-arm64@0.15.18:
|
||||
resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
esbuild-darwin-64@0.15.18:
|
||||
resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
esbuild-darwin-arm64@0.15.18:
|
||||
resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
esbuild-freebsd-64@0.15.18:
|
||||
resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
esbuild-freebsd-arm64@0.15.18:
|
||||
resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
esbuild-linux-32@0.15.18:
|
||||
resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-64@0.15.18:
|
||||
resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-arm64@0.15.18:
|
||||
resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-arm@0.15.18:
|
||||
resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-mips64le@0.15.18:
|
||||
resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-ppc64le@0.15.18:
|
||||
resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-riscv64@0.15.18:
|
||||
resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
esbuild-linux-s390x@0.15.18:
|
||||
resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
esbuild-netbsd-64@0.15.18:
|
||||
resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
esbuild-openbsd-64@0.15.18:
|
||||
resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
esbuild-sunos-64@0.15.18:
|
||||
resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
esbuild-windows-32@0.15.18:
|
||||
resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
esbuild-windows-64@0.15.18:
|
||||
resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
esbuild-windows-arm64@0.15.18:
|
||||
resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==}
|
||||
engines: {node: '>=12'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
esbuild@0.15.18:
|
||||
resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
gensync@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
jsesc@3.1.0:
|
||||
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
json5@2.2.3:
|
||||
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
magic-string@0.26.7:
|
||||
resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
path-parse@1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
postcss@8.5.3:
|
||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
react-dom@18.3.1:
|
||||
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react@18.3.1:
|
||||
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resolve@1.22.10:
|
||||
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
hasBin: true
|
||||
|
||||
rollup@2.79.2:
|
||||
resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
sourcemap-codec@1.4.8:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
deprecated: Please use @jridgewell/sourcemap-codec instead
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
typescript@4.9.5:
|
||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
|
||||
update-browserslist-db@1.1.3:
|
||||
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
vite@3.2.11:
|
||||
resolution: {integrity: sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/compat-data@7.27.2': {}
|
||||
|
||||
'@babel/core@7.27.1':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.27.1
|
||||
'@babel/helper-compilation-targets': 7.27.2
|
||||
'@babel/helper-module-transforms': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/helpers': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/traverse': 7.27.1
|
||||
'@babel/types': 7.27.1
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.4.1
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/generator@7.27.1':
|
||||
dependencies:
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 3.1.0
|
||||
|
||||
'@babel/helper-annotate-as-pure@7.27.1':
|
||||
dependencies:
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@babel/helper-compilation-targets@7.27.2':
|
||||
dependencies:
|
||||
'@babel/compat-data': 7.27.2
|
||||
'@babel/helper-validator-option': 7.27.1
|
||||
browserslist: 4.24.5
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
'@babel/helper-module-imports@7.27.1':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.27.1
|
||||
'@babel/types': 7.27.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-transforms@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
'@babel/traverse': 7.27.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-plugin-utils@7.27.1': {}
|
||||
|
||||
'@babel/helper-string-parser@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-option@7.27.1': {}
|
||||
|
||||
'@babel/helpers@7.27.1':
|
||||
dependencies:
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@babel/parser@7.27.2':
|
||||
dependencies:
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.27.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/helper-annotate-as-pure': 7.27.1
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/types': 7.27.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
|
||||
'@babel/traverse@7.27.1':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/generator': 7.27.1
|
||||
'@babel/parser': 7.27.2
|
||||
'@babel/template': 7.27.2
|
||||
'@babel/types': 7.27.1
|
||||
debug: 4.4.1
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/types@7.27.1':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@esbuild/android-arm@0.15.18':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.15.18':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@types/prop-types@15.7.14': {}
|
||||
|
||||
'@types/react-dom@18.3.7(@types/react@18.3.21)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.21
|
||||
|
||||
'@types/react@18.3.21':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.14
|
||||
csstype: 3.1.3
|
||||
|
||||
'@vitejs/plugin-react@2.2.0(vite@3.2.11)':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
|
||||
magic-string: 0.26.7
|
||||
react-refresh: 0.14.2
|
||||
vite: 3.2.11
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
browserslist@4.24.5:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001718
|
||||
electron-to-chromium: 1.5.155
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.24.5)
|
||||
|
||||
caniuse-lite@1.0.30001718: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
electron-to-chromium@1.5.155: {}
|
||||
|
||||
esbuild-android-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-android-arm64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-darwin-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-darwin-arm64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-freebsd-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-freebsd-arm64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-32@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-arm64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-arm@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-mips64le@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-ppc64le@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-riscv64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-linux-s390x@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-netbsd-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-openbsd-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-sunos-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-windows-32@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-windows-64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild-windows-arm64@0.15.18:
|
||||
optional: true
|
||||
|
||||
esbuild@0.15.18:
|
||||
optionalDependencies:
|
||||
'@esbuild/android-arm': 0.15.18
|
||||
'@esbuild/linux-loong64': 0.15.18
|
||||
esbuild-android-64: 0.15.18
|
||||
esbuild-android-arm64: 0.15.18
|
||||
esbuild-darwin-64: 0.15.18
|
||||
esbuild-darwin-arm64: 0.15.18
|
||||
esbuild-freebsd-64: 0.15.18
|
||||
esbuild-freebsd-arm64: 0.15.18
|
||||
esbuild-linux-32: 0.15.18
|
||||
esbuild-linux-64: 0.15.18
|
||||
esbuild-linux-arm: 0.15.18
|
||||
esbuild-linux-arm64: 0.15.18
|
||||
esbuild-linux-mips64le: 0.15.18
|
||||
esbuild-linux-ppc64le: 0.15.18
|
||||
esbuild-linux-riscv64: 0.15.18
|
||||
esbuild-linux-s390x: 0.15.18
|
||||
esbuild-netbsd-64: 0.15.18
|
||||
esbuild-openbsd-64: 0.15.18
|
||||
esbuild-sunos-64: 0.15.18
|
||||
esbuild-windows-32: 0.15.18
|
||||
esbuild-windows-64: 0.15.18
|
||||
esbuild-windows-arm64: 0.15.18
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json5@2.2.3: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
magic-string@0.26.7:
|
||||
dependencies:
|
||||
sourcemap-codec: 1.4.8
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
path-parse@1.0.7: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
postcss@8.5.3:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
react-dom@18.3.1(react@18.3.1):
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react@18.3.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
resolve@1.22.10:
|
||||
dependencies:
|
||||
is-core-module: 2.16.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
rollup@2.79.2:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
sourcemap-codec@1.4.8: {}
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
typescript@4.9.5: {}
|
||||
|
||||
update-browserslist-db@1.1.3(browserslist@4.24.5):
|
||||
dependencies:
|
||||
browserslist: 4.24.5
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
vite@3.2.11:
|
||||
dependencies:
|
||||
esbuild: 0.15.18
|
||||
postcss: 8.5.3
|
||||
resolve: 1.22.10
|
||||
rollup: 2.79.2
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
yallist@3.1.1: {}
|
59
frontend/src/App.css
Normal file
@ -0,0 +1,59 @@
|
||||
#app {
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#logo {
|
||||
display: block;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
margin: auto;
|
||||
padding: 10% 0 0;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
background-origin: content-box;
|
||||
}
|
||||
|
||||
.result {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
|
||||
.input-box .btn {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
margin: 0 0 0 20px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-box .btn:hover {
|
||||
background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.input-box .input {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
background-color: rgba(240, 240, 240, 1);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.input-box .input:hover {
|
||||
border: none;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.input-box .input:focus {
|
||||
border: none;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
41
frontend/src/App.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { useState } from 'react';
|
||||
import logo from './assets/images/logo-universal.png';
|
||||
import './App.css';
|
||||
import { Greet } from '../wailsjs/go/main/App';
|
||||
|
||||
function App() {
|
||||
const [resultText, setResultText] = useState(
|
||||
'Enter your fansly API token, then press Go!',
|
||||
);
|
||||
const [name, setName] = useState('');
|
||||
const updateName = (e: any) => setName(e.target.value);
|
||||
const updateResultText = (result: string) => setResultText(result);
|
||||
|
||||
function greet() {
|
||||
Greet(name).then(updateResultText);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='App'>
|
||||
<img src={logo} id='logo' alt='logo' />
|
||||
<div id='result' className='result'>
|
||||
{resultText}
|
||||
</div>
|
||||
<div id='input' className='input-box'>
|
||||
<input
|
||||
id='name'
|
||||
className='input'
|
||||
onChange={updateName}
|
||||
autoComplete='off'
|
||||
name='input'
|
||||
type='text'
|
||||
/>
|
||||
<button className='btn' onClick={greet}>
|
||||
Go!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
93
frontend/src/assets/fonts/OFL.txt
Normal file
@ -0,0 +1,93 @@
|
||||
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
Normal file
BIN
frontend/src/assets/images/logo-universal.png
Normal file
After Width: | Height: | Size: 136 KiB |
14
frontend/src/main.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import {createRoot} from 'react-dom/client'
|
||||
import './style.css'
|
||||
import App from './App'
|
||||
|
||||
const container = document.getElementById('root')
|
||||
|
||||
const root = createRoot(container!)
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App/>
|
||||
</React.StrictMode>
|
||||
)
|
26
frontend/src/style.css
Normal file
@ -0,0 +1,26 @@
|
||||
html {
|
||||
background-color: rgba(27, 38, 54, 1);
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
|
||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Nunito";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local(""),
|
||||
url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
1
frontend/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
31
frontend/tsconfig.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
11
frontend/tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
7
frontend/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()]
|
||||
})
|
4
frontend/wailsjs/go/main/App.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function Greet(arg1:string):Promise<string>;
|
7
frontend/wailsjs/go/main/App.js
Normal file
@ -0,0 +1,7 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function Greet(arg1) {
|
||||
return window['go']['main']['App']['Greet'](arg1);
|
||||
}
|
24
frontend/wailsjs/runtime/package.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
249
frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
238
frontend/wailsjs/runtime/runtime.js
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
36
go.mod
Normal file
@ -0,0 +1,36 @@
|
||||
module FanslySync
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/wailsapp/wails/v2 v2.10.1
|
||||
|
||||
require (
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
|
||||
github.com/labstack/echo/v4 v4.13.3 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/gosod v1.0.4 // indirect
|
||||
github.com/leaanthony/slicer v1.6.0 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
)
|
||||
|
||||
// replace github.com/wailsapp/wails/v2 v2.10.1 => C:\Users\tsomm\go\pkg\mod
|
79
go.sum
Normal file
@ -0,0 +1,79 @@
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck=
|
||||
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
|
||||
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI=
|
||||
github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw=
|
||||
github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js=
|
||||
github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
|
||||
github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU=
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
|
||||
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
213
handlers/config.go
Normal file
@ -0,0 +1,213 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"FanslySync/structs"
|
||||
"FanslySync/utils"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
)
|
||||
|
||||
// ConfigManager defines methods for accessing and persisting application configuration.
|
||||
type ConfigManager interface {
|
||||
// GetConfig returns the current configuration. If forceReload is true,
|
||||
// it re-reads the file from disk even if a cached copy exists.
|
||||
GetConfig(forceReload bool) (*structs.Config, error)
|
||||
|
||||
// LoadConfigOrCreate loads the config or, if the file doesn't exist,
|
||||
// creates a default config, saves it, and returns it.
|
||||
LoadConfigOrCreate() (*structs.Config, error)
|
||||
|
||||
// SaveConfig writes the given config to disk and updates the in-memory cache.
|
||||
SaveConfig(config *structs.Config) error
|
||||
|
||||
// GetConfigPath returns the filesystem path where the config is stored.
|
||||
GetConfigPath() string
|
||||
|
||||
// ShouldMigrateOldAppConfig checks if the old app config file exists
|
||||
// and returns true if it should be migrated.
|
||||
ShouldMigrateOldAppConfig() (bool, error)
|
||||
|
||||
// MigrateOldAppConfig migrates the old app config file to the new format.
|
||||
MigrateOldAppConfig() error
|
||||
}
|
||||
|
||||
// FileConfigManager implements ConfigManager using a JSON file on disk
|
||||
// with in-memory caching and custom logging.
|
||||
type FileConfigManager struct {
|
||||
path string
|
||||
config *structs.Config
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
func NewFileConfigManager(path string) (ConfigManager, error) {
|
||||
// Create our logger
|
||||
fileLogger, loggerCreateErr := utils.NewLogger("ConfigManager")
|
||||
if loggerCreateErr != nil {
|
||||
// Log the error and return nil
|
||||
return nil, loggerCreateErr
|
||||
}
|
||||
|
||||
return &FileConfigManager{
|
||||
path: path,
|
||||
log: fileLogger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetConfigPath returns the path to the config file.
|
||||
func (mgr *FileConfigManager) GetConfigPath() string {
|
||||
return mgr.path
|
||||
}
|
||||
|
||||
func GetConfigPathForRuntime() (string, error) {
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, "FanslySync", "appconfig.json"), nil
|
||||
}
|
||||
|
||||
// ShouldMigrateOldAppConfig checks for an existing legacy config.json and logs the result.
|
||||
func (mgr *FileConfigManager) ShouldMigrateOldAppConfig() (bool, error) {
|
||||
mgr.log.Info("Checking for old config file")
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error getting user config dir: %v", err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
||||
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
||||
mgr.log.Info(fmt.Sprintf("No old config at %s", oldConfigPath))
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
mgr.log.Info(fmt.Sprintf("Old config exists at %s", oldConfigPath))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// MigrateOldAppConfig reads the legacy config.json, converts it, saves the new format,
|
||||
// and removes the old file, logging each step.
|
||||
func (mgr *FileConfigManager) MigrateOldAppConfig() error {
|
||||
mgr.log.Info("Migrating old config file")
|
||||
dir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error getting user config dir: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
||||
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
||||
mgr.log.Info("No old config to migrate")
|
||||
return nil
|
||||
} else if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(oldConfigPath)
|
||||
if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error reading old config: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
var oldCfg structs.OldConfig
|
||||
if err := json.Unmarshal(data, &oldCfg); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error unmarshaling old config: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
newCfg := structs.NewConfigFromOld(&oldCfg)
|
||||
if err := mgr.SaveConfig(newCfg); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error saving new config: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Remove(oldConfigPath); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error removing old config: %v", err))
|
||||
return fmt.Errorf("could not remove old config file: %w", err)
|
||||
}
|
||||
|
||||
mgr.log.Info("Migration complete; old config removed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig loads the config from disk if forceReload is true or no cache exists.
|
||||
// It logs each step and errors encountered.
|
||||
func (mgr *FileConfigManager) GetConfig(forceReload bool) (*structs.Config, error) {
|
||||
mgr.log.Debug(fmt.Sprintf("GetConfig(forceReload=%v)", forceReload))
|
||||
if mgr.config != nil && !forceReload {
|
||||
mgr.log.Debug("Returning cached config")
|
||||
return mgr.config, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(mgr.path)
|
||||
if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error reading config file: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg structs.Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error unmarshaling config: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mgr.config = &cfg
|
||||
mgr.log.Info("Config loaded from disk. Cache updated.")
|
||||
mgr.log.Debug(fmt.Sprintf("Config: %+v", cfg))
|
||||
return mgr.config, nil
|
||||
}
|
||||
|
||||
// LoadConfigOrCreate loads an existing config or creates/saves a default if none exists.
|
||||
func (mgr *FileConfigManager) LoadConfigOrCreate() (*structs.Config, error) {
|
||||
cfg, err := mgr.GetConfig(false)
|
||||
if err == nil {
|
||||
mgr.log.Info("Existing config loaded")
|
||||
return cfg, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
mgr.log.Warning("Config missing; creating default")
|
||||
defaultCfg := structs.NewConfig()
|
||||
if saveErr := mgr.SaveConfig(defaultCfg); saveErr != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error saving default config: %v", saveErr))
|
||||
return nil, saveErr
|
||||
}
|
||||
mgr.log.Info("Default config created and saved")
|
||||
return defaultCfg, nil
|
||||
}
|
||||
mgr.log.Error(fmt.Sprintf("Error loading config: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// SaveConfig writes the config to disk, updates cache, and logs the process.
|
||||
func (mgr *FileConfigManager) SaveConfig(cfg *structs.Config) error {
|
||||
mgr.log.Info(fmt.Sprintf("Saving config to %s", mgr.path))
|
||||
dir := filepath.Dir(mgr.path)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error creating config directory: %v", err))
|
||||
return fmt.Errorf("could not create config directory: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error marshaling config: %v", err))
|
||||
return fmt.Errorf("could not marshal config: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(mgr.path, data, 0o644); err != nil {
|
||||
mgr.log.Error(fmt.Sprintf("Error writing config file: %v", err))
|
||||
return fmt.Errorf("could not write config file: %w", err)
|
||||
}
|
||||
|
||||
mgr.config = cfg
|
||||
mgr.log.Info("Config saved and cache updated")
|
||||
return nil
|
||||
}
|
267
handlers/fansly.go
Normal file
@ -0,0 +1,267 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"FanslySync/structs"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"FanslySync/utils"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
type FanslyAPIController struct {
|
||||
client *http.Client
|
||||
token string
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// New creates a new Fansly client with the provided token (optional).
|
||||
func NewFanslyAPIController(token string) (*FanslyAPIController, error) {
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
apiLogger, apiLoggerCreateErr := utils.NewLogger("FanslyAPIController")
|
||||
|
||||
if apiLoggerCreateErr != nil {
|
||||
// Log the error and return nil
|
||||
fmt.Println("Failed to create logger: ", apiLoggerCreateErr)
|
||||
return nil, apiLoggerCreateErr
|
||||
}
|
||||
|
||||
return &FanslyAPIController{
|
||||
client: client,
|
||||
token: token,
|
||||
logger: apiLogger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GET issues a GET to /api/v1/{path}, optionally adding the Auth header,
|
||||
// and unmarshals the JSON response into the result parameter.
|
||||
func (f *FanslyAPIController) GET(path string, needsAuth bool, out interface{}) error {
|
||||
// build request
|
||||
url := "https://apiv3.fansly.com/api/v1/" + path
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
f.logger.Error("NewRequest GET " + path + ": " + err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Set headers
|
||||
req.Header.Set("User-Agent", "FanslySync/3.0 sticks@teamhydra.dev")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// set auth
|
||||
if needsAuth && f.token != "" {
|
||||
req.Header.Set("Authorization", f.token)
|
||||
}
|
||||
|
||||
// send
|
||||
resp, err := f.client.Do(req)
|
||||
if err != nil {
|
||||
f.logger.Error("Do GET " + path + ": " + err.Error())
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// non-200
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
f.logger.Error(fmt.Sprintf("GET %s failed: %s", path, resp.Status))
|
||||
// read body for logs
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
f.logger.Info("Response body: " + string(body))
|
||||
return fmt.Errorf("unexpected status %s", resp.Status)
|
||||
}
|
||||
|
||||
// 200 ok, log
|
||||
f.logger.Debug(fmt.Sprintf("GET %s succeeded: %s", path, resp.Status))
|
||||
|
||||
// read body and unmarshal
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
f.logger.Error("Request was OK, but failed to read body: " + err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshal into our expected response type
|
||||
err = json.Unmarshal(body, &out)
|
||||
if err != nil {
|
||||
f.logger.Error("GET " + path + " failed to unmarshal response: " + err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetToken updates the authorization token for subsequent requests.
|
||||
func (f *FanslyAPIController) SetToken(token string) {
|
||||
f.token = token
|
||||
}
|
||||
|
||||
// Returns the current user's account information from the Fansly API.
|
||||
//
|
||||
// Will error if the token is not set or the request fails.
|
||||
//
|
||||
// Returns a FanslyAccount struct containing the account information.
|
||||
func (f *FanslyAPIController) GetMe() (*structs.FanslyAccount, error) {
|
||||
var response structs.FanslyBaseResponse[structs.FanslyAccountResponse]
|
||||
|
||||
err := f.GET("account/me", true, &response)
|
||||
if err != nil {
|
||||
f.logger.Error("GetMe failed: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the account info
|
||||
return &response.Response.Account, nil
|
||||
}
|
||||
|
||||
func (f *FanslyAPIController) GetFollowersWithOffset(acctId string, offset int) ([]structs.FanslyFollowResponse, error) {
|
||||
var response structs.FanslyBaseResponseAsArray[structs.FanslyFollowResponse]
|
||||
|
||||
err := f.GET(fmt.Sprintf("account/%s/followers?limit=300&offset=%d", acctId, offset), true, &response)
|
||||
if err != nil {
|
||||
f.logger.Error("GetFollowersWithOffset failed: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return the followers
|
||||
return response.Response, nil
|
||||
}
|
||||
|
||||
func (f *FanslyAPIController) GetSubscribersWithOffset(offset int) (structs.FanslySubscriptionResponse, error) {
|
||||
var response structs.FanslyBaseResponse[structs.FanslySubscriptionResponse]
|
||||
|
||||
err := f.GET(fmt.Sprintf("subscribers?status=3,4&limit=300&offset=%d", offset), true, &response)
|
||||
if err != nil {
|
||||
f.logger.Error("GetSubscribersWithOffset failed: " + err.Error())
|
||||
return structs.FanslySubscriptionResponse{}, err
|
||||
}
|
||||
|
||||
// Return the subscribers
|
||||
return response.Response, nil
|
||||
}
|
||||
|
||||
func (f *FanslyAPIController) Sync(ctx context.Context, token string, auto bool) {
|
||||
// Start the sync worker
|
||||
go syncWorker(ctx, token, auto)
|
||||
}
|
||||
|
||||
func syncWorker(ctx context.Context, token string, auto bool) {
|
||||
runtime.EventsEmit(ctx, "sync:started", nil)
|
||||
|
||||
progress := func(step string, curr, total int, done bool) {
|
||||
p := structs.SyncProgressEvent{
|
||||
Step: step,
|
||||
PercentDone: utils.Percentage(curr, total),
|
||||
Count: curr,
|
||||
TotalCount: total,
|
||||
Complete: done,
|
||||
}
|
||||
runtime.EventsEmit(ctx, "sync:progress", p)
|
||||
}
|
||||
|
||||
// Create a logger dedicated to this sync run
|
||||
syncLogger, err := utils.NewLogger("SyncWorker")
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var startTime time.Time = time.Now()
|
||||
syncLogger.Info(fmt.Sprintf("Starting sync at %s, auto=%t", startTime.Format(time.RFC3339), auto))
|
||||
syncLogger.Info("[1/4] Fetching profile…")
|
||||
|
||||
// Instantiate API controller
|
||||
c, err := NewFanslyAPIController(token)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Profile -----------------------------------------------------------
|
||||
progress("Fetching profile", 0, 100, false)
|
||||
acct, err := c.GetMe()
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
progress("Fetched profile", 0, 100, false)
|
||||
syncLogger.Info(fmt.Sprintf("Fetched profile. %s – %d followers, %d subscribers", acct.Username, acct.FollowCount, acct.SubscriberCount))
|
||||
|
||||
// 2. Followers ---------------------------------------------------------
|
||||
syncLogger.Info("[2/4] Fetching followers…")
|
||||
followers := make([]string, 0, acct.FollowCount)
|
||||
offset := 0
|
||||
for len(followers) < acct.FollowCount {
|
||||
batch, err := c.GetFollowersWithOffset(acct.ID, offset)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
for _, f := range batch {
|
||||
followers = append(followers, f.FollowerID)
|
||||
}
|
||||
offset += 300
|
||||
progress("Fetching followers", len(followers), acct.FollowCount, false)
|
||||
syncLogger.Info(fmt.Sprintf("[followers] %d/%d (offset=%d)", len(followers), acct.FollowCount, offset))
|
||||
|
||||
if len(batch) == 0 || len(followers) >= acct.FollowCount {
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond) // gentle throttle
|
||||
}
|
||||
|
||||
// 3. Subscribers -------------------------------------------------------
|
||||
syncLogger.Info("[3/4] Fetching subscribers…")
|
||||
subs := make([]structs.Subscription, 0, acct.SubscriberCount)
|
||||
offset = 0
|
||||
for len(subs) < acct.SubscriberCount {
|
||||
res, err := c.GetSubscribersWithOffset(offset)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
subs = append(subs, res.Subscriptions...)
|
||||
offset += 300
|
||||
progress("Fetching subscribers", len(subs), acct.SubscriberCount, false)
|
||||
syncLogger.Info(fmt.Sprintf("[subs] %d/%d (offset=%d)", len(subs), acct.SubscriberCount, offset))
|
||||
|
||||
if len(res.Subscriptions) == 0 || len(subs) >= acct.SubscriberCount {
|
||||
break
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 4. Upload & finish ---------------------------------------------------
|
||||
syncLogger.Info("[4/4] Uploading data…")
|
||||
data := structs.SyncData{
|
||||
Followers: followers,
|
||||
Subscriptions: subs,
|
||||
}
|
||||
|
||||
if !auto {
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
url, err := utils.UploadPaste(payload)
|
||||
if err != nil {
|
||||
runtime.EventsEmit(ctx, "sync:error", err.Error())
|
||||
return
|
||||
}
|
||||
data.PasteURL = url
|
||||
syncLogger.Info(fmt.Sprintf("Uploaded data to %s", url))
|
||||
}
|
||||
|
||||
progress("Sync complete", 100, 100, true)
|
||||
syncLogger.Info(fmt.Sprintf("Sync done at %s. Took %s, fetched %d followers and %d subscribers", time.Now().Format(time.RFC3339), time.Since(startTime).String(), len(followers), len(subs)))
|
||||
runtime.EventsEmit(ctx, "sync:complete", data)
|
||||
}
|
57
main.go
Normal file
@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"FanslySync/utils"
|
||||
"context"
|
||||
"embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
//go:embed all:frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
// Create an instance of the app structure
|
||||
app := NewApp()
|
||||
|
||||
// Create our custom file logger
|
||||
fileLogger, loggerCreateErr := utils.NewLogger("runtime")
|
||||
startupLogger, startupLoggerCreateErr := utils.NewLogger("startup")
|
||||
|
||||
if loggerCreateErr != nil || startupLoggerCreateErr != nil {
|
||||
log.Fatal("Failed to create one or more loggers: ", loggerCreateErr, startupLoggerCreateErr)
|
||||
}
|
||||
|
||||
// Create application with options
|
||||
err := wails.Run(&options.App{
|
||||
Title: "FanslySync",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
AssetServer: &assetserver.Options{
|
||||
Assets: assets,
|
||||
},
|
||||
DisableResize: true,
|
||||
Logger: fileLogger,
|
||||
LogLevel: logger.ERROR,
|
||||
LogLevelProduction: logger.INFO,
|
||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||
OnStartup: func(ctx context.Context) {
|
||||
app.startup(ctx, startupLogger)
|
||||
},
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// Build a message box with the error
|
||||
utils.ShowMessageBox(app.ctx, "FanslySync | Initialization Error", "Could not start application.\n\nError: "+err.Error(), utils.WithDialogType(runtime.ErrorDialog))
|
||||
runtime.Quit(app.ctx)
|
||||
}
|
||||
}
|
57
package.json
@ -1,57 +0,0 @@
|
||||
{
|
||||
"name": "fanslysync-desktop",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write .",
|
||||
"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": {
|
||||
"@sveltejs/adapter-auto": "^3.2.5",
|
||||
"@sveltejs/adapter-static": "^3.0.5",
|
||||
"@sveltejs/kit": "^2.6.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tauri-apps/cli": "^2.4.1",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.44.1",
|
||||
"globals": "^15.10.0",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-check": "^4.0.4",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.8.0",
|
||||
"vite": "^5.4.8"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.1.1",
|
||||
"@tauri-apps/plugin-autostart": "^2.0.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||
"@tauri-apps/plugin-log": "^2.0.1",
|
||||
"@tauri-apps/plugin-notification": "^2.0.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0",
|
||||
"@tauri-apps/plugin-process": "^2.2.1",
|
||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||
"svelte-french-toast": "^1.2.0"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
3
src-tauri/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
6800
src-tauri/Cargo.lock
generated
@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.6"
|
||||
description = "A Tauri App"
|
||||
authors = ["SticksDev"]
|
||||
license = "MIT"
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
edition = "2021"
|
||||
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", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "2.4.1", features = ["tray-icon"] }
|
||||
dirs = "5.0.1"
|
||||
reqwest = { version = "0.11.18", features = ["json", "multipart"] }
|
||||
lazy_static = "1.5.0"
|
||||
tokio = { version = "1.29.1", features = ["full"] }
|
||||
tokio-macros = "2.3.0"
|
||||
tauri-plugin-os = { version = "2.2.1" }
|
||||
tauri-plugin-dialog = "2.2.1"
|
||||
tauri-plugin-clipboard-manager = { version = "2.2.1" }
|
||||
tauri-plugin-notification = { version = "2.2.1" }
|
||||
tauri-plugin-updater = "2.2.1"
|
||||
tauri-plugin-log = { version = "2.2.1" }
|
||||
log = "0.4.27"
|
||||
thiserror = "2.0.12"
|
||||
tauri-plugin-process = "2"
|
||||
|
||||
[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.3.0"
|
||||
tauri-plugin-single-instance = "2"
|
||||
tauri-plugin-updater = "2"
|
@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
{"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"]}}
|
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 1011 B |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -1,10 +0,0 @@
|
||||
sudo apt update
|
||||
sudo apt install libwebkit2gtk-4.1-dev \
|
||||
build-essential \
|
||||
curl \
|
||||
wget \
|
||||
file \
|
||||
libxdo-dev \
|
||||
libssl-dev \
|
||||
libayatana-appindicator3-dev \
|
||||
librsvg2-dev
|
@ -1,43 +0,0 @@
|
||||
use crate::handlers::config::{get_config_path, Config};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn init_config() -> Result<(), String> {
|
||||
log::info!("[commands::config::init_config] Initializing config...");
|
||||
let config_path = get_config_path().map_err(|e| e.to_string())?;
|
||||
|
||||
log::info!(
|
||||
"[commands::config::init_config] Config path: {}",
|
||||
config_path.display()
|
||||
);
|
||||
|
||||
Config::load_or_create(&config_path).map_err(|e| e.to_string())?;
|
||||
log::info!("[commands::config::init_config] Config initialized successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_config() -> Result<Config, String> {
|
||||
let config_path = get_config_path().map_err(|e| e.to_string())?;
|
||||
let config = Config::load_or_create(&config_path).map_err(|e| e.to_string())?;
|
||||
|
||||
log::info!(
|
||||
"[commands::config::get_config] Config loaded successfully: {:?} from path: {}",
|
||||
config,
|
||||
config_path.display()
|
||||
);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_config(config: Config) -> Result<(), String> {
|
||||
let config_path = get_config_path().map_err(|e| e.to_string())?;
|
||||
log::info!(
|
||||
"[commands::config::save_config] Saving config: {:?} to path: {}",
|
||||
config,
|
||||
config_path.display()
|
||||
);
|
||||
|
||||
config.save(&config_path).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use crate::handlers::fansly::{SyncProgress, PROGRESS};
|
||||
use crate::{
|
||||
handlers::fansly::Fansly,
|
||||
structs::{FanslyAccountResponse, FanslyBaseResponse, SyncDataResponse},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref FANSLY: Mutex<Fansly> = Mutex::new(Fansly::new(None));
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fansly_set_token(token: Option<String>) {
|
||||
FANSLY.lock().await.set_token(token);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fansly_get_me() -> Result<FanslyBaseResponse<FanslyAccountResponse>, String> {
|
||||
let fansly = FANSLY.lock().await;
|
||||
let response = fansly.get_profile().await;
|
||||
|
||||
match response {
|
||||
Ok(response) => Ok(response),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fansly_sync(auto: bool) -> Result<SyncDataResponse, String> {
|
||||
let mut fansly = FANSLY.lock().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_get_sync_status() -> SyncProgress {
|
||||
PROGRESS.lock().await.clone()
|
||||
}
|
||||
|
||||
#[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),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod fansly;
|
||||
pub mod utils;
|
@ -1,4 +0,0 @@
|
||||
#[tauri::command]
|
||||
pub fn quit(code: i32) {
|
||||
std::process::exit(code);
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::structs::{FanslyFollowersResponse, Subscription};
|
||||
|
||||
const CURRENT_VERSION: i32 = 2; // Set the current version of the config
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SyncData {
|
||||
pub followers: Vec<FanslyFollowersResponse>,
|
||||
pub subscribers: Vec<Subscription>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
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,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
version: CURRENT_VERSION, // Version is set to CURRENT_VERSION by default
|
||||
is_first_run: true, // First run is set to true by default
|
||||
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(),
|
||||
}, // Last sync data is stored as a list of followers and subscribers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load_or_create(path: &Path) -> io::Result<Self> {
|
||||
if path.exists() {
|
||||
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)?;
|
||||
|
||||
log::info!("[config::migrate] Migrating config file to latest version...");
|
||||
log::debug!(
|
||||
"[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() {
|
||||
log::error!(
|
||||
"[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 {
|
||||
log::error!(
|
||||
"[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",
|
||||
));
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"[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)?;
|
||||
|
||||
log::info!(
|
||||
"[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 {
|
||||
// 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);
|
||||
saved_config
|
||||
.and_then(|_| Config::load_or_create(path))
|
||||
.or_else(|e| Err(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate(mut self) -> io::Result<Self> {
|
||||
while self.version < CURRENT_VERSION {
|
||||
self = match 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
|
||||
}
|
||||
_ => {
|
||||
// If we don't have a migration path, return an error
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("No migration path for version {}", self.version),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn save(&self, path: &Path) -> io::Result<()> {
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(serde_json::to_string_pretty(self).unwrap().as_bytes())?;
|
||||
|
||||
// Return the saved config
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_path() -> io::Result<PathBuf> {
|
||||
let mut config_dir = dirs::config_dir().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Could not determine user's config directory",
|
||||
)
|
||||
})?;
|
||||
config_dir.push("FanslySync");
|
||||
fs::create_dir_all(&config_dir)?;
|
||||
config_dir.push("config.json");
|
||||
Ok(config_dir)
|
||||
}
|
@ -1,521 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
// Create a simple module for handling the Fansly API, using reqwest to make requests to the API.
|
||||
// This module will contain a struct Fansly, which will have a method to get the user's profile information.
|
||||
use crate::structs::{
|
||||
FanslyAccountResponse, FanslyBaseResponse, FanslyBaseResponseList, FanslyFollowersResponse,
|
||||
FanslySubscriptionsResponse, Subscription, SyncDataResponse,
|
||||
};
|
||||
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// Create a PROGRESS mutex to hold the current sync progress, lazy initialized
|
||||
lazy_static! {
|
||||
pub static ref PROGRESS: Mutex<SyncProgress> = Mutex::new(SyncProgress::default());
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SyncProgress {
|
||||
// Should contain the current progress of the sync operation
|
||||
pub current_step: String,
|
||||
pub percentage_done: u32,
|
||||
pub current_count: u32,
|
||||
pub total_count: u32,
|
||||
pub complete: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PasteData {
|
||||
id: String,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PasteResponse {
|
||||
error: Option<String>,
|
||||
payload: PasteData,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
struct PasteRequest {
|
||||
content: String,
|
||||
}
|
||||
|
||||
pub struct Fansly {
|
||||
client: reqwest::Client,
|
||||
token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UploadError {
|
||||
#[error("HTTP error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
}
|
||||
|
||||
impl Fansly {
|
||||
pub fn new(token: Option<String>) -> Self {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
// Set the user agent to the FanslySync/0.1.0 tanner@fanslycreatorbot.com
|
||||
headers.insert(
|
||||
USER_AGENT,
|
||||
HeaderValue::from_static("FanslySync/0.1.0 tanner@fanslycreatorbot.com"), // this sucks, oh well
|
||||
);
|
||||
|
||||
// If we have a token, add it to the headers\
|
||||
if let Some(token) = &token {
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("{}", token)).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Set our default base url to https://apiv3.fansly.com/api/v1/
|
||||
let client = reqwest::Client::builder()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
Self { client, token }
|
||||
}
|
||||
|
||||
// Helper function to set our token on the fly
|
||||
pub fn set_token(&mut self, token: Option<String>) {
|
||||
self.token = token;
|
||||
|
||||
// Re-create the client with the new token (if it exists)
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
headers.insert(
|
||||
USER_AGENT,
|
||||
HeaderValue::from_static("FanslySync/0.1.0 tanner@fanslycreatorbot.com"),
|
||||
);
|
||||
|
||||
// If we have a token, add it to the headers
|
||||
if let Some(token) = &self.token {
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("{}", token)).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
self.client = reqwest::Client::builder()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn get_profile(
|
||||
&self,
|
||||
) -> Result<FanslyBaseResponse<FanslyAccountResponse>, reqwest::Error> {
|
||||
let response = self
|
||||
.client
|
||||
.get("https://apiv3.fansly.com/api/v1/account/me")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
log::error!("[sync::process::get_profile] No successful response from API. Setting error state.");
|
||||
return Err(response.error_for_status().unwrap_err());
|
||||
} else {
|
||||
log::info!("[sync::process::get_profile] Successfully fetched profile data.");
|
||||
}
|
||||
|
||||
let profile = response
|
||||
.json::<FanslyBaseResponse<FanslyAccountResponse>>()
|
||||
.await?;
|
||||
|
||||
// Show the profile data
|
||||
log::info!("[sync::process::get_profile] Profile data: {:?}", profile);
|
||||
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
async fn fetch_followers(
|
||||
&self,
|
||||
account_id: &str,
|
||||
auth_token: &str,
|
||||
offset: u32,
|
||||
) -> Result<FanslyBaseResponseList<FanslyFollowersResponse>, reqwest::Error> {
|
||||
let url = format!("https://apiv3.fansly.com/api/v1/account/{}/followers?ngsw-bypass=true&limit=100&offset={}", account_id, offset);
|
||||
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
format!("{}", auth_token).parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
reqwest::header::USER_AGENT,
|
||||
"FanslySync/1.0.0 (tanner@fanslycreatorbot.com)"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
headers.insert(
|
||||
reqwest::header::CONTENT_TYPE,
|
||||
"application/json".parse().unwrap(),
|
||||
);
|
||||
|
||||
let response = self.client.get(url).headers(headers).send().await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
log::error!("[sync::process::fetch_followers] No successful response from API. Setting error state.");
|
||||
return Err(response.error_for_status().unwrap_err());
|
||||
}
|
||||
|
||||
let followers: FanslyBaseResponseList<FanslyFollowersResponse> = response.json().await?;
|
||||
log::info!(
|
||||
"[sync::process::fetch_followers] Got {} followers from API.",
|
||||
followers.response.len()
|
||||
);
|
||||
|
||||
Ok(followers)
|
||||
}
|
||||
|
||||
async fn fetch_subscribers(
|
||||
&self,
|
||||
auth_token: &str,
|
||||
offset: u32,
|
||||
) -> Result<Vec<Subscription>, reqwest::Error> {
|
||||
let url = format!("https://apiv3.fansly.com/api/v1/subscribers?status=3,4&limit=100&offset={}&ngsw-bypass=true", offset);
|
||||
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
format!("{}", auth_token).parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
reqwest::header::USER_AGENT,
|
||||
"FanslySync/1.0.0 (tanner@fanslycreatorbot.com)"
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
reqwest::header::CONTENT_TYPE,
|
||||
"application/json".parse().unwrap(),
|
||||
);
|
||||
|
||||
let response = self.client.get(url).headers(headers).send().await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
log::error!("[sync::process::fetch_subscribers] No successful response from API. Setting error state.");
|
||||
let error = response.error_for_status().unwrap_err();
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let subscriptions: FanslyBaseResponse<FanslySubscriptionsResponse> =
|
||||
response.json().await?;
|
||||
|
||||
log::info!(
|
||||
"[sync::process::fetch_subscribers] Got {} subscribers from API.",
|
||||
subscriptions.response.subscriptions.len()
|
||||
);
|
||||
|
||||
Ok(subscriptions.response.subscriptions)
|
||||
}
|
||||
|
||||
async fn update_progress(
|
||||
&self,
|
||||
current_step: impl Into<String>,
|
||||
curr_count: u32,
|
||||
total_count: u32,
|
||||
complete: bool,
|
||||
) {
|
||||
let mut p = PROGRESS.lock().await;
|
||||
p.current_step = current_step.into();
|
||||
p.current_count = curr_count;
|
||||
p.total_count = total_count;
|
||||
p.percentage_done = if total_count > 0 {
|
||||
curr_count * 100 / total_count
|
||||
} else {
|
||||
0
|
||||
};
|
||||
p.complete = complete;
|
||||
}
|
||||
|
||||
async fn upload_sync_data(&self, data: SyncDataResponse) -> Result<String, UploadError> {
|
||||
let url = "https://paste.hep.gg/api/";
|
||||
|
||||
// Make an JSON object with our raw data
|
||||
let paste_data = PasteRequest {
|
||||
content: serde_json::to_string(&data).unwrap(),
|
||||
};
|
||||
|
||||
let paste_data_str = serde_json::to_string(&paste_data).unwrap();
|
||||
let est_upload_size = paste_data_str.len() / 1024; // in KB
|
||||
|
||||
log::info!(
|
||||
"Uploading sync data to paste.hep.gg (size: {} KB)",
|
||||
est_upload_size
|
||||
);
|
||||
|
||||
// Create a new client and POST
|
||||
let response = self
|
||||
.client
|
||||
.post(url)
|
||||
.body(paste_data_str)
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let status_code = response.status();
|
||||
let err = response.error_for_status_ref().unwrap_err();
|
||||
let response_text = response
|
||||
.text()
|
||||
.await
|
||||
.unwrap_or_else(|_| "Unknown error".to_string());
|
||||
|
||||
log::error!(
|
||||
"Failed to upload sync data to paste.hep.gg. Status code: {}, Response: {}",
|
||||
status_code,
|
||||
response_text
|
||||
);
|
||||
|
||||
return Err(UploadError::Http(err));
|
||||
}
|
||||
|
||||
log::info!("Uploaded sync data successfully.");
|
||||
|
||||
// Parse the response
|
||||
let paste_response: PasteResponse = response.json().await?;
|
||||
|
||||
// Return the paste URL
|
||||
let paste_url = format!("https://paste.hep.gg/api/{}/raw", paste_response.payload.id);
|
||||
log::info!("Paste URL: {}", paste_url);
|
||||
|
||||
Ok(paste_url)
|
||||
}
|
||||
|
||||
pub async fn upload_auto_sync_data(
|
||||
&self,
|
||||
data: SyncDataResponse,
|
||||
token: String,
|
||||
) -> Result<(), reqwest::Error> {
|
||||
let url = "https://botapi.fanslycreatorbot.com/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() {
|
||||
log::error!("Failed to upload sync data...");
|
||||
log::info!("Response: {:?}", response);
|
||||
return Err(response.error_for_status().unwrap_err());
|
||||
}
|
||||
|
||||
log::info!("Uploaded sync data successfully.");
|
||||
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 = "https://botapi.fanslycreatorbot.com/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() {
|
||||
log::error!("Failed to check sync token...");
|
||||
log::info!("Response: {:?}", response);
|
||||
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(&mut self, auto: bool) -> Result<SyncDataResponse, String> {
|
||||
// Reset progress
|
||||
self.update_progress("Starting Sync".to_string(), 0, 100, false)
|
||||
.await;
|
||||
|
||||
// Fetch profile
|
||||
log::info!("[sync::process] Fetching profile...");
|
||||
let profile = self.get_profile().await.map_err(|e| e.to_string())?;
|
||||
|
||||
if !profile.success {
|
||||
return Err("Failed to fetch profile".to_string());
|
||||
}
|
||||
|
||||
log::info!("[sync::process] Syncing profile...");
|
||||
|
||||
let account = profile.response.account;
|
||||
let total_followers = account.follow_count;
|
||||
let total_subscribers = account.subscriber_count;
|
||||
|
||||
log::info!(
|
||||
"[sync::process] Account ID: {}, Followers: {}, Subscribers: {}",
|
||||
account.id,
|
||||
total_followers,
|
||||
total_subscribers
|
||||
);
|
||||
|
||||
let mut followers: Vec<String> = Vec::new();
|
||||
let mut subscribers: Vec<Subscription> = Vec::new();
|
||||
|
||||
log::info!("[sync::process] Fetching followers...");
|
||||
|
||||
// Fetch followers until we have all of them
|
||||
let mut offset = 0;
|
||||
let mut total_requests = 0;
|
||||
while followers.len() < total_followers as usize {
|
||||
log::info!(
|
||||
"[sync::process] Fetching followers for account {} with offset {} (total: {})",
|
||||
account.id,
|
||||
offset,
|
||||
total_followers
|
||||
);
|
||||
let response = self
|
||||
.fetch_followers(&account.id, &self.token.as_ref().unwrap(), offset)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
log::info!(
|
||||
"[sync::process] Got {} followers from API.",
|
||||
response.response.len()
|
||||
);
|
||||
|
||||
// Collect followers
|
||||
for follower in response.response.clone() {
|
||||
followers.push(follower.follower_id);
|
||||
}
|
||||
|
||||
offset += 100;
|
||||
total_requests += 1;
|
||||
|
||||
// Update progress
|
||||
self.update_progress(
|
||||
"Fetching Followers".to_string(),
|
||||
followers.len() as u32,
|
||||
total_followers as u32,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Every 10 requests, sleep for a bit to avoid rate limiting
|
||||
if total_requests % 50 == 0 {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
|
||||
// If we've received no followers, break the loop
|
||||
if response.clone().response.is_empty() {
|
||||
log::info!("[sync::process] No more followers found, breaking the loop.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch subscribers until we have all of them
|
||||
offset = 0;
|
||||
while subscribers.len() < total_subscribers as usize {
|
||||
log::info!(
|
||||
"[sync::process] Fetching subscribers with offset {} for account {} (total: {})",
|
||||
offset,
|
||||
account.id,
|
||||
total_subscribers
|
||||
);
|
||||
|
||||
let response = self
|
||||
.fetch_subscribers(&self.token.as_ref().unwrap(), offset)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
subscribers.extend(response.clone());
|
||||
offset += 100;
|
||||
total_requests += 1;
|
||||
|
||||
// Update progress
|
||||
self.update_progress(
|
||||
"Fetching Subscribers".to_string(),
|
||||
subscribers.len() as u32,
|
||||
total_subscribers as u32,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Every 10 requests, sleep for a bit to avoid rate limiting
|
||||
if total_requests % 50 == 0 {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
|
||||
// If we've received no subscribers, break the loop
|
||||
if response.is_empty() {
|
||||
log::info!("[sync::process] No more subscribers found, breaking the loop.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"[sync::process] Got {} followers and {} subscribers from API.",
|
||||
followers.len(),
|
||||
subscribers.len()
|
||||
);
|
||||
|
||||
log::info!("[sync::process] Sync complete.");
|
||||
|
||||
// Reset progress
|
||||
self.update_progress("Sync Complete".to_string(), 100, 100, true)
|
||||
.await;
|
||||
|
||||
log::info!("[sync::process] Uploading sync data to paste.hep.gg for processing...");
|
||||
|
||||
// Upload sync data to paste.hep.gg
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod fansly;
|
@ -1,136 +0,0 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod commands;
|
||||
mod handlers;
|
||||
mod structs;
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
|
||||
use commands::config::{get_config, init_config, save_config};
|
||||
use commands::fansly::{
|
||||
fansly_check_sync_token, fansly_get_me, fansly_get_sync_status, fansly_set_token, fansly_sync,
|
||||
fansly_upload_auto_sync_data,
|
||||
};
|
||||
use commands::utils::quit;
|
||||
use tauri::menu::Menu;
|
||||
use tauri::menu::MenuItem;
|
||||
use tauri::tray::TrayIconBuilder;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Manager;
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
use tauri_plugin_dialog::MessageDialogKind;
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
fn get_log_path() -> io::Result<String> {
|
||||
let mut config_dir = dirs::config_dir().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Could not determine user's config directory",
|
||||
)
|
||||
})?;
|
||||
config_dir.push("FanslySync");
|
||||
fs::create_dir_all(&config_dir)?;
|
||||
config_dir.push("runtime");
|
||||
|
||||
// Return the path as a string
|
||||
Ok(config_dir.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
fn handle_menu(app: &tauri::AppHandle, event: &tauri::menu::MenuEvent) {
|
||||
match event.id().as_ref() {
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
"show_window" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.setup(|app| {
|
||||
// Setup menu items for the tray
|
||||
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||
let show_window_i =
|
||||
MenuItem::with_id(app, "show_window", "Show Window", true, None::<&str>)?;
|
||||
|
||||
// Create our Menu and add the items to it
|
||||
let menu = Menu::with_items(app, &[&quit_i, &show_window_i])?;
|
||||
|
||||
// Create our Tray using TrayIconBuilder and add the menu to it
|
||||
TrayIconBuilder::new()
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.title("FanslySync")
|
||||
.tooltip("FanslySync")
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(true)
|
||||
.on_menu_event(|app: &AppHandle, event: tauri::menu::MenuEvent| {
|
||||
handle_menu(app, &event)
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|app, event| {
|
||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.hide();
|
||||
api.prevent_close();
|
||||
}
|
||||
}
|
||||
})
|
||||
.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
None,
|
||||
))
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.targets([
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(get_log_path().unwrap()),
|
||||
}),
|
||||
Target::new(TargetKind::Webview),
|
||||
])
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne)
|
||||
.max_file_size(1024 * 1024 * 5)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_single_instance::init(|app,_args,_cwd| {
|
||||
// Show a dialog if the app is already running
|
||||
app.dialog()
|
||||
.message("FanslySync is already running in the background. Please left click the tray icon -> Show Window to open the app.")
|
||||
.title("FanslySync")
|
||||
.kind(MessageDialogKind::Warning)
|
||||
.blocking_show();
|
||||
}))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
init_config,
|
||||
get_config,
|
||||
save_config,
|
||||
quit,
|
||||
fansly_set_token,
|
||||
fansly_get_me,
|
||||
fansly_sync,
|
||||
fansly_upload_auto_sync_data,
|
||||
fansly_check_sync_token,
|
||||
fansly_get_sync_status
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SyncDataResponse {
|
||||
pub followers: Vec<String>,
|
||||
pub subscribers: Vec<Subscription>,
|
||||
pub sync_data_url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FanslyBaseResponse<T> {
|
||||
pub success: bool,
|
||||
pub response: T,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FanslyBaseResponseList<T> {
|
||||
pub success: bool,
|
||||
pub response: Vec<T>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FanslyFollowersResponse {
|
||||
pub follower_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FanslySubscriptionsResponse {
|
||||
pub stats: SubscriptionsStats,
|
||||
pub subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SubscriptionsStats {
|
||||
pub total_active: i64,
|
||||
pub total_expired: i64,
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Subscription {
|
||||
pub id: String,
|
||||
pub history_id: String,
|
||||
pub subscriber_id: String,
|
||||
pub subscription_tier_id: String,
|
||||
pub subscription_tier_name: String,
|
||||
pub subscription_tier_color: String,
|
||||
pub plan_id: String,
|
||||
pub promo_id: Option<String>,
|
||||
pub gift_code_id: Value,
|
||||
pub payment_method_id: String,
|
||||
pub status: i64,
|
||||
pub price: i64,
|
||||
pub renew_price: i64,
|
||||
pub renew_correlation_id: String,
|
||||
pub auto_renew: i64,
|
||||
pub billing_cycle: i64,
|
||||
pub duration: i64,
|
||||
pub renew_date: i64,
|
||||
pub version: i64,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub ends_at: i64,
|
||||
pub promo_price: Value,
|
||||
pub promo_duration: Value,
|
||||
pub promo_status: Value,
|
||||
pub promo_starts_at: Value,
|
||||
pub promo_ends_at: Value,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FanslyAccountResponse {
|
||||
pub account: Account,
|
||||
pub correlation_id: String,
|
||||
pub check_token: Value,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Account {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub username: String,
|
||||
pub display_name: Option<String>,
|
||||
pub flags: i64,
|
||||
pub version: i64,
|
||||
pub created_at: i64,
|
||||
pub follow_count: i64,
|
||||
pub subscriber_count: i64,
|
||||
pub permissions: Permissions,
|
||||
pub timeline_stats: TimelineStats,
|
||||
pub profile_access_flags: i64,
|
||||
pub profile_flags: i64,
|
||||
pub about: String,
|
||||
pub location: String,
|
||||
pub profile_socials: Vec<Value>,
|
||||
pub status_id: i64,
|
||||
pub last_seen_at: i64,
|
||||
pub post_likes: i64,
|
||||
pub streaming: Streaming,
|
||||
pub account_media_likes: i64,
|
||||
pub subscription_tiers: Vec<SubscriptionTier>,
|
||||
pub profile_access: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Permissions {
|
||||
pub account_permission_flags: AccountPermissionFlags,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AccountPermissionFlags {
|
||||
pub flags: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TimelineStats {
|
||||
pub account_id: String,
|
||||
pub image_count: i64,
|
||||
pub video_count: i64,
|
||||
pub bundle_count: i64,
|
||||
pub bundle_image_count: i64,
|
||||
pub bundle_video_count: i64,
|
||||
pub fetched_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MainWallet {
|
||||
pub id: String,
|
||||
pub account_id: String,
|
||||
pub balance: i64,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: i64,
|
||||
pub wallet_version: i64,
|
||||
pub flags: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Streaming {
|
||||
pub account_id: String,
|
||||
pub channel: Value,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SubscriptionTier {
|
||||
pub id: String,
|
||||
pub account_id: String,
|
||||
pub name: String,
|
||||
pub color: String,
|
||||
pub pos: i64,
|
||||
pub price: i64,
|
||||
pub max_subscribers: i64,
|
||||
pub subscription_benefits: Vec<String>,
|
||||
pub included_tier_ids: Vec<Value>,
|
||||
pub plans: Vec<Plan>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Plan {
|
||||
pub id: String,
|
||||
pub status: i64,
|
||||
pub billing_cycle: i64,
|
||||
pub price: i64,
|
||||
pub use_amounts: i64,
|
||||
pub promos: Vec<Value>,
|
||||
pub uses: i64,
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"build": {
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"frontendDist": "../build",
|
||||
"devUrl": "http://localhost:5173"
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"targets": "all",
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
},
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": []
|
||||
}
|
||||
},
|
||||
"createUpdaterArtifacts": true
|
||||
},
|
||||
"productName": "FanslySync",
|
||||
"version": "0.2.0",
|
||||
"identifier": "com.fanslycreatorbot.fanslysync",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDJFODZGRDI4NjBFMDQ1RUMKUldUc1JlQmdLUDJHTGdRdSt6dWFISXE0MThsa0tvUDA2RWdMSStjQ0J6NVBhdmU4ajRMMms4a1cK",
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
"https://cdn.crabnebula.app/update/fansly-creator-bot/fansly-sync/{{target}}-{{arch}}/{{current_version}}"
|
||||
],
|
||||
"dialog": true
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
"height": 650,
|
||||
"resizable": false,
|
||||
"title": "FanslySync",
|
||||
"width": 630
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
13
src/app.d.ts
vendored
@ -1,13 +0,0 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
11
src/app.html
@ -1,11 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
252
src/lib/types.ts
@ -1,252 +0,0 @@
|
||||
export type Config = {
|
||||
version: number;
|
||||
is_first_run: boolean;
|
||||
fansly_token: string;
|
||||
auto_sync_enabled: boolean;
|
||||
sync_token: string;
|
||||
sync_interval: number;
|
||||
last_sync: number;
|
||||
last_sync_data: SyncData;
|
||||
};
|
||||
|
||||
export interface SyncData {
|
||||
followers: string[];
|
||||
subscribers: Subscriber[];
|
||||
sync_data_url: string;
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
id: string;
|
||||
historyId: string;
|
||||
subscriberId: string;
|
||||
subscriptionTierId: string;
|
||||
subscriptionTierName: string;
|
||||
subscriptionTierColor: string;
|
||||
planId: string;
|
||||
promoId: null | string;
|
||||
giftCodeId: null | string;
|
||||
paymentMethodId: string;
|
||||
status: number;
|
||||
price: number;
|
||||
renewPrice: number;
|
||||
renewCorrelationId: string;
|
||||
autoRenew: number;
|
||||
billingCycle: number;
|
||||
duration: number;
|
||||
renewDate: number;
|
||||
version: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
endsAt: number;
|
||||
promoPrice: null | number;
|
||||
promoDuration: null | number;
|
||||
promoStatus: null | number;
|
||||
promoStartsAt: null | number;
|
||||
promoEndsAt: null | number;
|
||||
}
|
||||
|
||||
export interface AccountInfoResponse {
|
||||
success: boolean;
|
||||
response: AccountInfo[];
|
||||
}
|
||||
|
||||
export interface AccountInfo {
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string | null;
|
||||
flags: number;
|
||||
version: number;
|
||||
createdAt: number;
|
||||
followCount: number;
|
||||
subscriberCount: number;
|
||||
permissions: Permissions;
|
||||
profileAccessFlags: number;
|
||||
profileFlags: number;
|
||||
about: string;
|
||||
location: string;
|
||||
profileSocials: ProfileSocial[];
|
||||
pinnedPosts: PinnedPost[];
|
||||
walls: Wall[];
|
||||
timelineStats: TimelineStats;
|
||||
statusId: number;
|
||||
lastSeenAt: number;
|
||||
mediaStoryState: MediaStoryState;
|
||||
accountMediaLikes: number;
|
||||
avatar: Avatar;
|
||||
banner: Avatar;
|
||||
postLikes: number;
|
||||
streaming: Streaming;
|
||||
}
|
||||
|
||||
export interface SubscriptionTier {
|
||||
id: string;
|
||||
accountId: string;
|
||||
name: string;
|
||||
color: string;
|
||||
pos: number;
|
||||
price: number;
|
||||
maxSubscribers: number;
|
||||
subscriptionBenefits: string[];
|
||||
includedTierIds: string[];
|
||||
plans: Plan[];
|
||||
}
|
||||
|
||||
interface Plan {
|
||||
id: string;
|
||||
status: number;
|
||||
billingCycle: number;
|
||||
price: number;
|
||||
useAmounts: number;
|
||||
promos: Promo[];
|
||||
uses: number;
|
||||
}
|
||||
|
||||
interface Promo {
|
||||
id: string;
|
||||
status: number;
|
||||
price: number;
|
||||
duration: number;
|
||||
maxUses: number;
|
||||
maxUsesBefore?: unknown;
|
||||
newSubscribersOnly: number;
|
||||
startsAt: number;
|
||||
endsAt: number;
|
||||
uses: number;
|
||||
}
|
||||
|
||||
interface Streaming {
|
||||
accountId: string;
|
||||
channel: Channel;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface Channel {
|
||||
id: string;
|
||||
accountId: string;
|
||||
playbackUrl: string;
|
||||
chatRoomId: string;
|
||||
status: number;
|
||||
version: number;
|
||||
createdAt: number;
|
||||
updatedAt?: unknown;
|
||||
stream: Stream;
|
||||
arn?: unknown;
|
||||
ingestEndpoint?: unknown;
|
||||
}
|
||||
|
||||
interface Stream {
|
||||
id: string;
|
||||
historyId: string;
|
||||
channelId: string;
|
||||
accountId: string;
|
||||
title: string;
|
||||
status: number;
|
||||
viewerCount: number;
|
||||
version: number;
|
||||
createdAt: number;
|
||||
updatedAt?: unknown;
|
||||
lastFetchedAt: number;
|
||||
startedAt: number;
|
||||
permissions: Permissions2;
|
||||
}
|
||||
|
||||
interface Permissions2 {
|
||||
permissionFlags: PermissionFlag[];
|
||||
}
|
||||
|
||||
interface PermissionFlag {
|
||||
id: string;
|
||||
streamId: string;
|
||||
type: number;
|
||||
flags: number;
|
||||
price: number;
|
||||
metadata: string;
|
||||
}
|
||||
|
||||
interface Avatar {
|
||||
id: string;
|
||||
type: number;
|
||||
status: number;
|
||||
accountId: string;
|
||||
mimetype: string;
|
||||
flags: number;
|
||||
location: string;
|
||||
width: number;
|
||||
height: number;
|
||||
metadata: string;
|
||||
updatedAt: number;
|
||||
createdAt: number;
|
||||
variants: Variant[];
|
||||
variantHash: VariantHash;
|
||||
locations: Location[];
|
||||
}
|
||||
|
||||
type VariantHash = unknown;
|
||||
|
||||
interface Variant {
|
||||
id: string;
|
||||
type: number;
|
||||
status: number;
|
||||
mimetype: string;
|
||||
flags: number;
|
||||
location: string;
|
||||
width: number;
|
||||
height: number;
|
||||
metadata: string;
|
||||
updatedAt: number;
|
||||
locations: Location[];
|
||||
}
|
||||
|
||||
interface Location {
|
||||
locationId: string;
|
||||
location: string;
|
||||
}
|
||||
|
||||
interface MediaStoryState {
|
||||
accountId: string;
|
||||
status: number;
|
||||
storyCount: number;
|
||||
version: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
hasActiveStories: boolean;
|
||||
}
|
||||
|
||||
interface TimelineStats {
|
||||
accountId: string;
|
||||
imageCount: number;
|
||||
videoCount: number;
|
||||
bundleCount: number;
|
||||
bundleImageCount: number;
|
||||
bundleVideoCount: number;
|
||||
fetchedAt: number;
|
||||
}
|
||||
|
||||
interface Wall {
|
||||
id: string;
|
||||
accountId: string;
|
||||
pos: number;
|
||||
name: string;
|
||||
description: string;
|
||||
metadata: string;
|
||||
}
|
||||
|
||||
interface PinnedPost {
|
||||
postId: string;
|
||||
accountId: string;
|
||||
pos: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
interface ProfileSocial {
|
||||
providerId: string;
|
||||
handle: string;
|
||||
}
|
||||
|
||||
interface Permissions {
|
||||
accountPermissionFlags: AccountPermissionFlags;
|
||||
}
|
||||
|
||||
interface AccountPermissionFlags {
|
||||
flags: number;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const awaiter = async <T>(promise: Promise<T>): Promise<[T | null, any | null]> => {
|
||||
try {
|
||||
const data: T = await promise;
|
||||
return [data, null];
|
||||
} catch (err) {
|
||||
return [null, err];
|
||||
}
|
||||
};
|