more changes

This commit is contained in:
Sticks 2025-05-19 18:26:52 -04:00
parent e40f82f636
commit 7aa2dee280
7 changed files with 218 additions and 22 deletions

17
app.go
View File

@ -98,6 +98,19 @@ func (a *App) startup(ctx context.Context, logger logger.Logger) {
}
// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello %s, It's show time!", name)
func (a *App) Greet(token string) string {
// Create fansly API instance
fanslyAPI := handlers.NewFanslyAPIController(token, a.Logger)
// Get the user info
account, accountErr := fanslyAPI.GetMe()
if accountErr != nil {
return "Failed to get account info: " + accountErr.Error()
}
// Print the response we got
a.Logger.Info(fmt.Sprintf("[Greet] Account info: %+v", account))
// Return the greeting
return fmt.Sprintf("Hello %s! You have %d fans and %d posts likes.", account.Username, account.FollowCount, account.PostLikes)
}

View File

@ -1,10 +1,12 @@
import { useState } from 'react';
import logo from './assets/images/logo-universal.png';
import './App.css';
import {Greet} from "../wailsjs/go/main/App";
import { Greet } from '../wailsjs/go/main/App';
function App() {
const [resultText, setResultText] = useState("Please enter your name below 👇");
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);
@ -14,15 +16,26 @@ function App() {
}
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}>Greet</button>
<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
export default App;

111
handlers/fansly.go Normal file
View File

@ -0,0 +1,111 @@
package handlers
import (
"FanslySync/structs"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/wailsapp/wails/v2/pkg/logger"
)
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, log logger.Logger) *FanslyAPIController {
client := &http.Client{
Timeout: 30 * time.Second,
}
return &FanslyAPIController{
client: client,
token: token,
logger: log,
}
}
// 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("[FanslyAPIController] 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("[FanslyAPIController] Do GET " + path + ": " + err.Error())
return err
}
defer resp.Body.Close()
// non-200
if resp.StatusCode != http.StatusOK {
f.logger.Error(fmt.Sprintf("[FanslyAPIController] GET %s failed: %s", path, resp.Status))
// read body for logs
body, _ := io.ReadAll(resp.Body)
f.logger.Info("[FanslyAPIController] Response body: " + string(body))
return fmt.Errorf("unexpected status %s", resp.Status)
}
// 200 ok, log
f.logger.Debug(fmt.Sprintf("[FanslyAPIController] GET %s succeeded: %s", path, resp.Status))
// read body and unmarshal
body, err := io.ReadAll(resp.Body)
if err != nil {
f.logger.Error("[FanslyAPIController] 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("[FanslyAPIController] 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("[FanslyAPIController] GetMe failed: " + err.Error())
return nil, err
}
// Return the account info
return &response.Response.Account, nil
}

View File

@ -5,6 +5,10 @@ type FanslyBaseResponse[T any] struct {
Response T `json:"response"` // The response data, type of T
}
type FanslyAccountResponse struct {
Account FanslyAccount `json:"account"`
}
type FanslyFollowResponse struct {
Followers []struct {
FollowerID string `json:"followerId"` // The ID of the follower
@ -51,3 +55,22 @@ type Subscription struct {
PromoStartsAt any `json:"promoStartsAt"`
PromoEndsAt any `json:"promoEndsAt"`
}
type FanslyAccount struct {
ID string `json:"id"`
Email string `json:"email"`
Username string `json:"username"`
DisplayName string `json:"displayName,omitempty"`
Flags int `json:"flags"`
Version int `json:"version"`
CreatedAt int64 `json:"createdAt"`
FollowCount int `json:"followCount"`
SubscriberCount int `json:"subscriberCount"`
AccountMediaLikes int `json:"accountMediaLikes"`
StatusID int `json:"statusId"`
LastSeenAt int `json:"lastSeenAt"`
About string `json:"about,omitempty"`
Location string `json:"location,omitempty"`
PostLikes int `json:"postLikes"`
ProfileAccess bool `json:"profileAccess"`
}

35
structs/sync.go Normal file
View File

@ -0,0 +1,35 @@
package structs
type SyncProgressEvent struct {
// The current step of the sync process.
Step string `json:"step"`
// The current percent done of the sync process.
PercentDone int `json:"percent_done"`
// The current count of the current step of the sync process.
Count int `json:"current_count"`
// The total count of the current step of the sync process.
TotalCount int `json:"total_count"`
// Are we complete?
Complete bool `json:"complete"`
}
type PasteDataReply struct {
// The ID of the paste.
Id string `json:"id"`
// The content of the paste.
Content string `json:"content"`
}
type PastePutResponse struct {
// If Error is not empty, the request failed.
Error string `json:"error"`
// If the request was successful, this will of type PasteDataReply.
// If the request was not successful, this will be empty.
Payload PasteDataReply `json:"payload"`
}
type PastePayload struct {
Content string `json:"content"`
}

View File

@ -64,18 +64,19 @@ func (m *multiLogger) Fatal(message string) {
// NewRuntimeFileLogger returns a logger that writes all output both to
//
// $XDG_CONFIG_HOME/FanslySync/logs/runtime_latest.log
// $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
// $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) {
// 1) Ensure log directory exists
// 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()
if err != nil {
return nil, fmt.Errorf("cannot determine user config dir: %w", err)
@ -85,7 +86,8 @@ func NewRuntimeFileLogger() (logger.Logger, error) {
return nil, fmt.Errorf("cannot create log directory: %w", err)
}
// 2) Prune old timestamped logs (>14 days)
// Prune old logs
// We keep logs for 14 days, so delete any logs older than that
cutoff := time.Now().Add(-14 * 24 * time.Hour)
entries, _ := os.ReadDir(logDir)
for _, e := range entries {
@ -101,17 +103,17 @@ func NewRuntimeFileLogger() (logger.Logger, error) {
}
}
// 3) Build paths for timestamped + latest
ts := time.Now().Format("2006-01-02_15-04-05")
tsPath := filepath.Join(logDir, fmt.Sprintf("runtime_%s.log", ts))
latestPath := filepath.Join(logDir, "runtime_latest.log")
// 4) Create both loggers
// Create loggers to attach to the multiLogger
tsLogger := logger.NewFileLogger(tsPath)
latestLogger := logger.NewFileLogger(latestPath)
termLogger := logger.NewDefaultLogger()
// 5) Fan-out into a multiLogger
// Spread into a multiLogger
// This will fan out all log messages to all three loggers
multi := &multiLogger{
targets: []logger.Logger{tsLogger, latestLogger, termLogger},
}

View File

@ -2,7 +2,6 @@ package utils
import (
"context"
"github.com/wailsapp/wails/v2/pkg/runtime"
)