manul sync backend works now
This commit is contained in:
parent
7aa2dee280
commit
96abb94f21
42
app.go
42
app.go
@ -5,7 +5,6 @@ import (
|
|||||||
"FanslySync/structs"
|
"FanslySync/structs"
|
||||||
"FanslySync/utils"
|
"FanslySync/utils"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
@ -16,7 +15,6 @@ type App struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
ConfigManager handlers.ConfigManager
|
ConfigManager handlers.ConfigManager
|
||||||
AppConfig *structs.Config
|
AppConfig *structs.Config
|
||||||
Logger logger.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new App application struct
|
// NewApp creates a new App application struct
|
||||||
@ -40,10 +38,20 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create our config manager
|
// Create our config manager
|
||||||
a.ConfigManager = handlers.NewFileConfigManager(configPath, logger)
|
configMgr, configMgrCreateErr := handlers.NewFileConfigManager(configPath)
|
||||||
a.Logger = logger
|
|
||||||
|
|
||||||
logger.Info("[startup] initializing FanslySync...")
|
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
|
// Check our config path to see if it was set correctly. Will not contain FailedConfigPathFetch
|
||||||
// Do we have an old config file?
|
// Do we have an old config file?
|
||||||
@ -56,7 +64,7 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldMigrate {
|
if shouldMigrate {
|
||||||
logger.Info("[startup] migrating old config file...")
|
logger.Info("migrating old config file...")
|
||||||
// Migrate the old config file
|
// Migrate the old config file
|
||||||
err := a.ConfigManager.MigrateOldAppConfig()
|
err := a.ConfigManager.MigrateOldAppConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,7 +74,7 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// Show success message
|
// Show success message
|
||||||
logger.Info("[startup] old config file migrate ok")
|
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))
|
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
|
// Now grab the new config
|
||||||
@ -82,7 +90,7 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load config as normal
|
// Load config as normal
|
||||||
logger.Info("[startup] loading config file...")
|
logger.Info("loading config file...")
|
||||||
cfg, err := a.ConfigManager.LoadConfigOrCreate()
|
cfg, err := a.ConfigManager.LoadConfigOrCreate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Show the error in a message box
|
// Show the error in a message box
|
||||||
@ -91,26 +99,24 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the config
|
// Set the config
|
||||||
logger.Info("[startup] config file loaded ok")
|
logger.Info("config file loaded ok")
|
||||||
a.AppConfig = cfg
|
a.AppConfig = cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("FanslySync initialized successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Greet returns a greeting for the given name
|
// Greet returns a greeting for the given name
|
||||||
func (a *App) Greet(token string) string {
|
func (a *App) Greet(token string) string {
|
||||||
// Create fansly API instance
|
// Create fansly API instance
|
||||||
fanslyAPI := handlers.NewFanslyAPIController(token, a.Logger)
|
fanslyAPI, createErr := handlers.NewFanslyAPIController(token)
|
||||||
|
|
||||||
// Get the user info
|
if createErr != nil {
|
||||||
account, accountErr := fanslyAPI.GetMe()
|
return "Failed to create Fansly API instance: " + createErr.Error()
|
||||||
if accountErr != nil {
|
|
||||||
return "Failed to get account info: " + accountErr.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the response we got
|
// Sync
|
||||||
a.Logger.Info(fmt.Sprintf("[Greet] Account info: %+v", account))
|
fanslyAPI.Sync(a.ctx, token, false)
|
||||||
|
|
||||||
// Return the greeting
|
return "Sync dispatched, check the logs for more info."
|
||||||
return fmt.Sprintf("Hello %s! You have %d fans and %d posts likes.", account.Username, account.FollowCount, account.PostLikes)
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"FanslySync/structs"
|
"FanslySync/structs"
|
||||||
|
"FanslySync/utils"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
)
|
)
|
||||||
@ -43,11 +44,18 @@ type FileConfigManager struct {
|
|||||||
log logger.Logger
|
log logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileConfigManager(path string, log logger.Logger) ConfigManager {
|
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{
|
return &FileConfigManager{
|
||||||
path: path,
|
path: path,
|
||||||
log: log,
|
log: fileLogger,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigPath returns the path to the config file.
|
// GetConfigPath returns the path to the config file.
|
||||||
@ -65,96 +73,96 @@ func GetConfigPathForRuntime() (string, error) {
|
|||||||
|
|
||||||
// ShouldMigrateOldAppConfig checks for an existing legacy config.json and logs the result.
|
// ShouldMigrateOldAppConfig checks for an existing legacy config.json and logs the result.
|
||||||
func (mgr *FileConfigManager) ShouldMigrateOldAppConfig() (bool, error) {
|
func (mgr *FileConfigManager) ShouldMigrateOldAppConfig() (bool, error) {
|
||||||
mgr.log.Info("[ConfigManager::ShouldMigrateOldAppConfig] Checking for old config file")
|
mgr.log.Info("Checking for old config file")
|
||||||
dir, err := os.UserConfigDir()
|
dir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Error getting user config dir: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error getting user config dir: %v", err))
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
||||||
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
||||||
mgr.log.Info(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] No old config at %s", oldConfigPath))
|
mgr.log.Info(fmt.Sprintf("No old config at %s", oldConfigPath))
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Error checking old config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err))
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr.log.Info(fmt.Sprintf("[ConfigManager::ShouldMigrateOldAppConfig] Old config exists at %s", oldConfigPath))
|
mgr.log.Info(fmt.Sprintf("Old config exists at %s", oldConfigPath))
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MigrateOldAppConfig reads the legacy config.json, converts it, saves the new format,
|
// MigrateOldAppConfig reads the legacy config.json, converts it, saves the new format,
|
||||||
// and removes the old file, logging each step.
|
// and removes the old file, logging each step.
|
||||||
func (mgr *FileConfigManager) MigrateOldAppConfig() error {
|
func (mgr *FileConfigManager) MigrateOldAppConfig() error {
|
||||||
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] Migrating old config file")
|
mgr.log.Info("Migrating old config file")
|
||||||
dir, err := os.UserConfigDir()
|
dir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error getting user config dir: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error getting user config dir: %v", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
oldConfigPath := filepath.Join(dir, "FanslySync", "config.json")
|
||||||
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
if _, err := os.Stat(oldConfigPath); os.IsNotExist(err) {
|
||||||
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] No old config to migrate")
|
mgr.log.Info("No old config to migrate")
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error checking old config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error checking old config: %v", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(oldConfigPath)
|
data, err := os.ReadFile(oldConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error reading old config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error reading old config: %v", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldCfg structs.OldConfig
|
var oldCfg structs.OldConfig
|
||||||
if err := json.Unmarshal(data, &oldCfg); err != nil {
|
if err := json.Unmarshal(data, &oldCfg); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error unmarshaling old config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error unmarshaling old config: %v", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newCfg := structs.NewConfigFromOld(&oldCfg)
|
newCfg := structs.NewConfigFromOld(&oldCfg)
|
||||||
if err := mgr.SaveConfig(newCfg); err != nil {
|
if err := mgr.SaveConfig(newCfg); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error saving new config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error saving new config: %v", err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Remove(oldConfigPath); err != nil {
|
if err := os.Remove(oldConfigPath); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::MigrateOldAppConfig] Error removing old config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error removing old config: %v", err))
|
||||||
return fmt.Errorf("could not remove old config file: %w", err)
|
return fmt.Errorf("could not remove old config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr.log.Info("[ConfigManager::MigrateOldAppConfig] Migration complete; old config removed")
|
mgr.log.Info("Migration complete; old config removed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConfig loads the config from disk if forceReload is true or no cache exists.
|
// GetConfig loads the config from disk if forceReload is true or no cache exists.
|
||||||
// It logs each step and errors encountered.
|
// It logs each step and errors encountered.
|
||||||
func (mgr *FileConfigManager) GetConfig(forceReload bool) (*structs.Config, error) {
|
func (mgr *FileConfigManager) GetConfig(forceReload bool) (*structs.Config, error) {
|
||||||
mgr.log.Debug(fmt.Sprintf("[ConfigManager] GetConfig(forceReload=%v)", forceReload))
|
mgr.log.Debug(fmt.Sprintf("GetConfig(forceReload=%v)", forceReload))
|
||||||
if mgr.config != nil && !forceReload {
|
if mgr.config != nil && !forceReload {
|
||||||
mgr.log.Debug("[ConfigManager::GetConfig] Returning cached config")
|
mgr.log.Debug("Returning cached config")
|
||||||
return mgr.config, nil
|
return mgr.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(mgr.path)
|
data, err := os.ReadFile(mgr.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::GetConfig] Error reading config file: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error reading config file: %v", err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg structs.Config
|
var cfg structs.Config
|
||||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::GetConfig] Error unmarshaling config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error unmarshaling config: %v", err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr.config = &cfg
|
mgr.config = &cfg
|
||||||
mgr.log.Info("[ConfigManager::GetConfig] Config loaded from disk. Cache updated.")
|
mgr.log.Info("Config loaded from disk. Cache updated.")
|
||||||
mgr.log.Debug(fmt.Sprintf("[ConfigManager::GetConfig] Config: %+v", cfg))
|
mgr.log.Debug(fmt.Sprintf("Config: %+v", cfg))
|
||||||
return mgr.config, nil
|
return mgr.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,44 +170,44 @@ func (mgr *FileConfigManager) GetConfig(forceReload bool) (*structs.Config, erro
|
|||||||
func (mgr *FileConfigManager) LoadConfigOrCreate() (*structs.Config, error) {
|
func (mgr *FileConfigManager) LoadConfigOrCreate() (*structs.Config, error) {
|
||||||
cfg, err := mgr.GetConfig(false)
|
cfg, err := mgr.GetConfig(false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mgr.log.Info("[ConfigManager::LoadConfigOrCreate] Existing config loaded")
|
mgr.log.Info("Existing config loaded")
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
mgr.log.Warning("[ConfigManager::LoadConfigOrCreate] Config missing; creating default")
|
mgr.log.Warning("Config missing; creating default")
|
||||||
defaultCfg := structs.NewConfig()
|
defaultCfg := structs.NewConfig()
|
||||||
if saveErr := mgr.SaveConfig(defaultCfg); saveErr != nil {
|
if saveErr := mgr.SaveConfig(defaultCfg); saveErr != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::LoadConfigOrCreate] Error saving default config: %v", saveErr))
|
mgr.log.Error(fmt.Sprintf("Error saving default config: %v", saveErr))
|
||||||
return nil, saveErr
|
return nil, saveErr
|
||||||
}
|
}
|
||||||
mgr.log.Info("[ConfigManager::LoadConfigOrCreate] Default config created and saved")
|
mgr.log.Info("Default config created and saved")
|
||||||
return defaultCfg, nil
|
return defaultCfg, nil
|
||||||
}
|
}
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::LoadConfigOrCreate] Error loading config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error loading config: %v", err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfig writes the config to disk, updates cache, and logs the process.
|
// SaveConfig writes the config to disk, updates cache, and logs the process.
|
||||||
func (mgr *FileConfigManager) SaveConfig(cfg *structs.Config) error {
|
func (mgr *FileConfigManager) SaveConfig(cfg *structs.Config) error {
|
||||||
mgr.log.Info(fmt.Sprintf("[ConfigManager::SaveConfig] Saving config to %s", mgr.path))
|
mgr.log.Info(fmt.Sprintf("Saving config to %s", mgr.path))
|
||||||
dir := filepath.Dir(mgr.path)
|
dir := filepath.Dir(mgr.path)
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error creating config directory: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error creating config directory: %v", err))
|
||||||
return fmt.Errorf("could not create config directory: %w", err)
|
return fmt.Errorf("could not create config directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(cfg, "", " ")
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error marshaling config: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error marshaling config: %v", err))
|
||||||
return fmt.Errorf("could not marshal config: %w", err)
|
return fmt.Errorf("could not marshal config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(mgr.path, data, 0o644); err != nil {
|
if err := os.WriteFile(mgr.path, data, 0o644); err != nil {
|
||||||
mgr.log.Error(fmt.Sprintf("[ConfigManager::SaveConfig] Error writing config file: %v", err))
|
mgr.log.Error(fmt.Sprintf("Error writing config file: %v", err))
|
||||||
return fmt.Errorf("could not write config file: %w", err)
|
return fmt.Errorf("could not write config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr.config = cfg
|
mgr.config = cfg
|
||||||
mgr.log.Info("[ConfigManager::SaveConfig] Config saved and cache updated")
|
mgr.log.Info("Config saved and cache updated")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,17 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"FanslySync/structs"
|
"FanslySync/structs"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"FanslySync/utils"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FanslyAPIController struct {
|
type FanslyAPIController struct {
|
||||||
@ -18,16 +22,24 @@ type FanslyAPIController struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Fansly client with the provided token (optional).
|
// New creates a new Fansly client with the provided token (optional).
|
||||||
func NewFanslyAPIController(token string, log logger.Logger) *FanslyAPIController {
|
func NewFanslyAPIController(token string) (*FanslyAPIController, error) {
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
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{
|
return &FanslyAPIController{
|
||||||
client: client,
|
client: client,
|
||||||
token: token,
|
token: token,
|
||||||
logger: log,
|
logger: apiLogger,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET issues a GET to /api/v1/{path}, optionally adding the Auth header,
|
// GET issues a GET to /api/v1/{path}, optionally adding the Auth header,
|
||||||
@ -37,7 +49,7 @@ func (f *FanslyAPIController) GET(path string, needsAuth bool, out interface{})
|
|||||||
url := "https://apiv3.fansly.com/api/v1/" + path
|
url := "https://apiv3.fansly.com/api/v1/" + path
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("[FanslyAPIController] NewRequest GET " + path + ": " + err.Error())
|
f.logger.Error("NewRequest GET " + path + ": " + err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,34 +65,34 @@ func (f *FanslyAPIController) GET(path string, needsAuth bool, out interface{})
|
|||||||
// send
|
// send
|
||||||
resp, err := f.client.Do(req)
|
resp, err := f.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("[FanslyAPIController] Do GET " + path + ": " + err.Error())
|
f.logger.Error("Do GET " + path + ": " + err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// non-200
|
// non-200
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
f.logger.Error(fmt.Sprintf("[FanslyAPIController] GET %s failed: %s", path, resp.Status))
|
f.logger.Error(fmt.Sprintf("GET %s failed: %s", path, resp.Status))
|
||||||
// read body for logs
|
// read body for logs
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
f.logger.Info("[FanslyAPIController] Response body: " + string(body))
|
f.logger.Info("Response body: " + string(body))
|
||||||
return fmt.Errorf("unexpected status %s", resp.Status)
|
return fmt.Errorf("unexpected status %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 200 ok, log
|
// 200 ok, log
|
||||||
f.logger.Debug(fmt.Sprintf("[FanslyAPIController] GET %s succeeded: %s", path, resp.Status))
|
f.logger.Debug(fmt.Sprintf("GET %s succeeded: %s", path, resp.Status))
|
||||||
|
|
||||||
// read body and unmarshal
|
// read body and unmarshal
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("[FanslyAPIController] Request was OK, but failed to read body: " + err.Error())
|
f.logger.Error("Request was OK, but failed to read body: " + err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal into our expected response type
|
// unmarshal into our expected response type
|
||||||
err = json.Unmarshal(body, &out)
|
err = json.Unmarshal(body, &out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("[FanslyAPIController] GET " + path + " failed to unmarshal response: " + err.Error())
|
f.logger.Error("GET " + path + " failed to unmarshal response: " + err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,10 +114,154 @@ func (f *FanslyAPIController) GetMe() (*structs.FanslyAccount, error) {
|
|||||||
|
|
||||||
err := f.GET("account/me", true, &response)
|
err := f.GET("account/me", true, &response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logger.Error("[FanslyAPIController] GetMe failed: " + err.Error())
|
f.logger.Error("GetMe failed: " + err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the account info
|
// Return the account info
|
||||||
return &response.Response.Account, nil
|
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)
|
||||||
|
}
|
||||||
|
10
main.go
10
main.go
@ -21,9 +21,11 @@ func main() {
|
|||||||
app := NewApp()
|
app := NewApp()
|
||||||
|
|
||||||
// Create our custom file logger
|
// Create our custom file logger
|
||||||
fileLogger, loggerCreateErr := utils.NewRuntimeFileLogger()
|
fileLogger, loggerCreateErr := utils.NewLogger("runtime")
|
||||||
if loggerCreateErr != nil {
|
startupLogger, startupLoggerCreateErr := utils.NewLogger("startup")
|
||||||
log.Fatal(loggerCreateErr)
|
|
||||||
|
if loggerCreateErr != nil || startupLoggerCreateErr != nil {
|
||||||
|
log.Fatal("Failed to create one or more loggers: ", loggerCreateErr, startupLoggerCreateErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
@ -40,7 +42,7 @@ func main() {
|
|||||||
LogLevelProduction: logger.INFO,
|
LogLevelProduction: logger.INFO,
|
||||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||||
OnStartup: func(ctx context.Context) {
|
OnStartup: func(ctx context.Context) {
|
||||||
app.startup(ctx, fileLogger)
|
app.startup(ctx, startupLogger)
|
||||||
},
|
},
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
app,
|
app,
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package structs
|
package structs
|
||||||
|
|
||||||
type SyncData struct {
|
type SyncData struct {
|
||||||
Followers []FanslyFollowResponse `json:"followers"` // List of followers
|
Followers []string `json:"followers"` // List of followers
|
||||||
Subscriptions []Subscription `json:"subscriptions"` // List of subscriptions
|
Subscriptions []Subscription `json:"subscriptions"` // List of subscriptions
|
||||||
|
PasteURL string `json:"paste_url"` // URL of the paste
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -35,7 +36,7 @@ func NewConfig() *Config {
|
|||||||
SyncInterval: 8,
|
SyncInterval: 8,
|
||||||
LastSyncTime: "",
|
LastSyncTime: "",
|
||||||
LastSyncData: SyncData{
|
LastSyncData: SyncData{
|
||||||
Followers: []FanslyFollowResponse{},
|
Followers: []string{},
|
||||||
Subscriptions: []Subscription{},
|
Subscriptions: []Subscription{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -51,7 +52,7 @@ func NewConfigFromOld(oldConfig *OldConfig) *Config {
|
|||||||
SyncInterval: oldConfig.SyncInterval,
|
SyncInterval: oldConfig.SyncInterval,
|
||||||
LastSyncTime: "",
|
LastSyncTime: "",
|
||||||
LastSyncData: SyncData{
|
LastSyncData: SyncData{
|
||||||
Followers: []FanslyFollowResponse{},
|
Followers: []string{},
|
||||||
Subscriptions: []Subscription{},
|
Subscriptions: []Subscription{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,17 @@ type FanslyBaseResponse[T any] struct {
|
|||||||
Response T `json:"response"` // The response data, type of T
|
Response T `json:"response"` // The response data, type of T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FanslyBaseResponseAsArray[T any] struct {
|
||||||
|
Success bool `json:"success"` // Indicates if the request was successful
|
||||||
|
Response []T `json:"response"` // The response data, type of T
|
||||||
|
}
|
||||||
|
|
||||||
type FanslyAccountResponse struct {
|
type FanslyAccountResponse struct {
|
||||||
Account FanslyAccount `json:"account"`
|
Account FanslyAccount `json:"account"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FanslyFollowResponse struct {
|
type FanslyFollowResponse struct {
|
||||||
Followers []struct {
|
FollowerID string `json:"followerId"` // The ID of the follower
|
||||||
FollowerID string `json:"followerId"` // The ID of the follower
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FanslySubscriptionResponse struct {
|
type FanslySubscriptionResponse struct {
|
||||||
|
@ -4,7 +4,7 @@ type SyncProgressEvent struct {
|
|||||||
// The current step of the sync process.
|
// The current step of the sync process.
|
||||||
Step string `json:"step"`
|
Step string `json:"step"`
|
||||||
// The current percent done of the sync process.
|
// The current percent done of the sync process.
|
||||||
PercentDone int `json:"percent_done"`
|
PercentDone float64 `json:"percent_done"`
|
||||||
// The current count of the current step of the sync process.
|
// The current count of the current step of the sync process.
|
||||||
Count int `json:"current_count"`
|
Count int `json:"current_count"`
|
||||||
// The total count of the current step of the sync process.
|
// The total count of the current step of the sync process.
|
||||||
|
139
utils/logger.go
139
utils/logger.go
@ -4,79 +4,40 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// multiLogger fans every log call out to multiple logger.Logger targets with timestamps.
|
var (
|
||||||
type multiLogger struct {
|
baseTargets []logger.Logger
|
||||||
|
once sync.Once
|
||||||
|
initErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstancedLogger prefixes each message with its instance-specific prefix and a timestamp,
|
||||||
|
// while writing to shared log outputs.
|
||||||
|
type InstancedLogger struct {
|
||||||
|
prefix string
|
||||||
targets []logger.Logger
|
targets []logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// timestamped prefixes each message with a timestamp.
|
// NewLogger returns a logger instance that writes to the shared logs
|
||||||
func timestamped(message string) string {
|
//
|
||||||
return time.Now().Format("2006-01-02 15:04:05") + " " + message
|
// but prefixes every message with [prefix].
|
||||||
|
func NewLogger(prefix string) (*InstancedLogger, error) {
|
||||||
|
once.Do(func() {
|
||||||
|
baseTargets, initErr = createBaseTargets()
|
||||||
|
})
|
||||||
|
if initErr != nil {
|
||||||
|
return nil, initErr
|
||||||
|
}
|
||||||
|
return &InstancedLogger{prefix: prefix, targets: baseTargets}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *multiLogger) Print(message string) {
|
// createBaseTargets initializes the shared log file and console loggers once.
|
||||||
msg := timestamped(message)
|
func createBaseTargets() ([]logger.Logger, error) {
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Print(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Trace(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Trace(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Debug(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Debug(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Info(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Info(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Warning(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Warning(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Error(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Error(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *multiLogger) Fatal(message string) {
|
|
||||||
msg := timestamped(message)
|
|
||||||
for _, l := range m.targets {
|
|
||||||
l.Fatal(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRuntimeFileLogger returns a logger that writes all output both to
|
|
||||||
//
|
|
||||||
// $XDG_CONFIG_HOME/FanslySync/logs/runtime_latest.log (or OS equivalent)
|
|
||||||
//
|
|
||||||
// and to a timestamped file
|
|
||||||
//
|
|
||||||
// $XDG_CONFIG_HOME/FanslySync/logs/runtime_YYYY-MM-DD_HH-MM-SS.log (or OS equivalent)
|
|
||||||
//
|
|
||||||
// It also deletes any timestamped logs older than 14 days.
|
|
||||||
//
|
|
||||||
// The returned logger implements github.com/wailsapp/wails/v2/pkg/logger.Logger
|
|
||||||
// and will be used by Wails for all Go-side logging.
|
|
||||||
func NewRuntimeFileLogger() (logger.Logger, error) {
|
|
||||||
// Make sure the log directory exists
|
|
||||||
// We use $XDG_CONFIG_HOME/FanslySync/logs/runtime_latest.log or OS equivalent for $XDG_CONFIG_HOME
|
|
||||||
cfgDir, err := os.UserConfigDir()
|
cfgDir, err := os.UserConfigDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot determine user config dir: %w", err)
|
return nil, fmt.Errorf("cannot determine user config dir: %w", err)
|
||||||
@ -86,8 +47,7 @@ func NewRuntimeFileLogger() (logger.Logger, error) {
|
|||||||
return nil, fmt.Errorf("cannot create log directory: %w", err)
|
return nil, fmt.Errorf("cannot create log directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune old logs
|
// Prune logs older than 14 days
|
||||||
// We keep logs for 14 days, so delete any logs older than that
|
|
||||||
cutoff := time.Now().Add(-14 * 24 * time.Hour)
|
cutoff := time.Now().Add(-14 * 24 * time.Hour)
|
||||||
entries, _ := os.ReadDir(logDir)
|
entries, _ := os.ReadDir(logDir)
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
@ -103,20 +63,53 @@ func NewRuntimeFileLogger() (logger.Logger, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove old runtime_latest.log if it exists
|
||||||
|
runtimePath := filepath.Join(logDir, "runtime_latest.log")
|
||||||
|
if _, err := os.Stat(runtimePath); err == nil {
|
||||||
|
_ = os.Remove(runtimePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare file paths
|
||||||
ts := time.Now().Format("2006-01-02_15-04-05")
|
ts := time.Now().Format("2006-01-02_15-04-05")
|
||||||
tsPath := filepath.Join(logDir, fmt.Sprintf("runtime_%s.log", ts))
|
tsPath := filepath.Join(logDir, fmt.Sprintf("runtime_%s.log", ts))
|
||||||
latestPath := filepath.Join(logDir, "runtime_latest.log")
|
latestPath := filepath.Join(logDir, "runtime_latest.log")
|
||||||
|
|
||||||
// Create loggers to attach to the multiLogger
|
// Create loggers
|
||||||
tsLogger := logger.NewFileLogger(tsPath)
|
tsLogger := logger.NewFileLogger(tsPath)
|
||||||
latestLogger := logger.NewFileLogger(latestPath)
|
latestLogger := logger.NewFileLogger(latestPath)
|
||||||
termLogger := logger.NewDefaultLogger()
|
termLogger := logger.NewDefaultLogger()
|
||||||
|
|
||||||
// Spread into a multiLogger
|
return []logger.Logger{tsLogger, latestLogger, termLogger}, nil
|
||||||
// This will fan out all log messages to all three loggers
|
|
||||||
multi := &multiLogger{
|
|
||||||
targets: []logger.Logger{tsLogger, latestLogger, termLogger},
|
|
||||||
}
|
|
||||||
|
|
||||||
return multi, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log dispatches a timestamped, prefixed message to all shared targets.
|
||||||
|
func (l *InstancedLogger) log(level, message string) {
|
||||||
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
fullMsg := fmt.Sprintf("%s [%s] %s", timestamp, l.prefix, message)
|
||||||
|
for _, t := range l.targets {
|
||||||
|
switch level {
|
||||||
|
case "Print":
|
||||||
|
t.Print(fullMsg)
|
||||||
|
case "Trace":
|
||||||
|
t.Trace(fullMsg)
|
||||||
|
case "Debug":
|
||||||
|
t.Debug(fullMsg)
|
||||||
|
case "Info":
|
||||||
|
t.Info(fullMsg)
|
||||||
|
case "Warning":
|
||||||
|
t.Warning(fullMsg)
|
||||||
|
case "Error":
|
||||||
|
t.Error(fullMsg)
|
||||||
|
case "Fatal":
|
||||||
|
t.Fatal(fullMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *InstancedLogger) Print(message string) { l.log("Print", message) }
|
||||||
|
func (l *InstancedLogger) Trace(message string) { l.log("Trace", message) }
|
||||||
|
func (l *InstancedLogger) Debug(message string) { l.log("Debug", message) }
|
||||||
|
func (l *InstancedLogger) Info(message string) { l.log("Info", message) }
|
||||||
|
func (l *InstancedLogger) Warning(message string) { l.log("Warning", message) }
|
||||||
|
func (l *InstancedLogger) Error(message string) { l.log("Error", message) }
|
||||||
|
func (l *InstancedLogger) Fatal(message string) { l.log("Fatal", message) }
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"FanslySync/structs"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,3 +62,76 @@ func ShowMessageBox(ctx context.Context, title, message string, opts ...MessageB
|
|||||||
// Show the message box
|
// Show the message box
|
||||||
runtime.MessageDialog(ctx, options)
|
runtime.MessageDialog(ctx, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UploadPaste(payload []byte) (string, error) {
|
||||||
|
// Create HTTP client
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
// Max request duration is 30s
|
||||||
|
client.Timeout = 30 * time.Second
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
var payloadStruct structs.PastePayload
|
||||||
|
payloadStruct.Content = string(payload)
|
||||||
|
|
||||||
|
// Marshal the payload to JSON
|
||||||
|
payloadJSON, err := json.Marshal(payloadStruct)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request (POST to https://paste.hep.gg/api/)
|
||||||
|
req, err := http.NewRequest("POST", "https://paste.hep.gg/api/", nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "FanslySync/3.0 sticks@teamhydra.dev")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
req.Header.Set("Accept-Encoding", "gzip")
|
||||||
|
|
||||||
|
// Set the body
|
||||||
|
req.Body = io.NopCloser(bytes.NewBuffer(payloadJSON))
|
||||||
|
|
||||||
|
// Send the request
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for non-200 status code
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the response body into our struct
|
||||||
|
var pasteResponse structs.PastePutResponse
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the response into our struct
|
||||||
|
err = json.Unmarshal(data, &pasteResponse)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for error in response
|
||||||
|
if pasteResponse.Error != "" {
|
||||||
|
return "", fmt.Errorf("error from paste server: %s", pasteResponse.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the paste ID
|
||||||
|
return fmt.Sprintf("https://paste.hep.gg/api/%s/raw", pasteResponse.Payload.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// percentage calculates the percentage of curr out of total.
|
||||||
|
func Percentage(curr, total int) float64 {
|
||||||
|
if total == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(curr) / float64(total) * 100
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user