The OnlyAutomator Microservice is a crucial backend component that processes OnlyFans creator accounts, collecting analytics data and storing it in the Supabase database. This service works both autonomously through scheduled cron jobs and on-demand via API endpoints.

System Architecture

The microservice consists of two primary components:
  1. Express Server: Handles API requests for manually triggering account processing
  2. Cron Job: Automatically processes all connected accounts at regular intervals
Both components share the core business logic for data collection but operate independently, allowing for flexibility in how data is gathered.

Technology Stack

The microservice leverages a modern Node.js ecosystem:

Core Technologies

  • Node.js (v18+): JavaScript runtime environment
  • TypeScript (v5.5+): Typed superset of JavaScript for robust code
  • Express.js (v4.19+): Fast, unopinionated web framework
  • PM2 (v5+): Advanced production process manager with integrated load balancer

Web Scraping & Automation

  • Puppeteer (v22+): Headless Chrome API for automated web interactions
  • Puppeteer-core: Lightweight version with reduced footprint

Database & Storage

  • Supabase: Open source Firebase alternative
  • PostgreSQL: Advanced relational database for structured data storage

Queue & Task Management

  • Better-Queue: Priority queue with concurrency control
  • Multi-Queue System: Load balancing across parallel queues

Logging & Monitoring

  • Winston: Versatile logging library with multiple transports
  • Morgan: HTTP request logger middleware for Express

Data Flow Process

The microservice collects data through the following process:

Implementation Details

Authentication Mechanism

The microservice uses browser storage data (cookies, localStorage, sessionStorage) collected from users’ devices to authenticate with OnlyFans:
// Authentication using storage data from accounts table
export async function getCreatorData(
  userId: string,
  accountId: string,
  username: string,
  userAgent: string,
  localStorage: Json | null,
  sessionStorage: Json | null,
  cookies: Json | null,
  isDebug: boolean = false
) {
  // Initialize browser with collected authentication data
  const browser = await puppeteer.launch({
    headless: !isDebug,
    args: [
      '--disable-extensions',
      '--disable-gpu',
      '--no-sandbox',
      '--disable-setuid-sandbox',
    ]
  });
  
  try {
    const page = await browser.newPage();
    await page.setUserAgent(userAgent);
    
    // Set cookies from stored data
    if (cookies) {
      for (const cookie of Object.values(cookies)) {
        await page.setCookie(cookie);
      }
    }
    
    // Navigate to OnlyFans and inject localStorage/sessionStorage
    await page.goto('https://onlyfans.com/');
    
    if (localStorage) {
      await page.evaluate((storageData) => {
        for (const [key, value] of Object.entries(storageData)) {
          window.localStorage.setItem(key, value as string);
        }
      }, localStorage);
    }
    
    if (sessionStorage) {
      await page.evaluate((storageData) => {
        for (const [key, value] of Object.entries(storageData)) {
          window.sessionStorage.setItem(key, value as string);
        }
      }, sessionStorage);
    }
    
    // Proceed with data extraction
    // ...
  } finally {
    await browser.close();
  }
}

Queue Management

The service implements a multi-queue system for concurrent processing:
const NUM_QUEUES = 3; // Default number of concurrent queues
const queues: Queue[] = [];
let currentQueueIndex = 0; // Round-robin queue selection

// Initialize multiple processing queues
for (let i = 0; i < NUM_QUEUES; i++) {
  const queue = new Queue(async function(accountData, cb) {
    logger.info(`Queue Function called in Queue ${i}`);
    
    getCreatorData(
      accountData.user_id,
      accountData.id.toString(),
      accountData.username,
      accountData.user_agent,
      accountData.web_local_storage ? accountData.web_local_storage : null,
      accountData.web_session_storage ? accountData.web_session_storage : null,
      accountData.web_cookies ? accountData.web_cookies : null,
      false
    )
      .then(() => {
        logger.info(`Finished processing account data for ${accountData.id} in Queue ${i}`);
        cb();
        return true;
      })
      .catch((error) => {
        console.error(
          'Error in account',
          accountData.name,
          accountData.username,
          error.message
        );
        cb(`Error in account: ${accountData.name}, ${accountData.username}, ${error.message}`);
      });
  });
  
  queues.push(queue);
  
  // Log queue statistics daily
  setInterval(
    () => logger.info(`Queue ${i} stats: ${queue.getStats()}`),
    24 * 60 * 60 * 1000
  );
}

API Endpoints

EndpointMethodDescription
/api/testGETHealth check endpoint to verify service status
/api/process-account/:accountIdPOSTManually trigger processing for a specific account

Environment Variables

The microservice relies on the following environment variables for configuration:
# Core configuration
PORT=3001                           # Port for the Express server
NEXT_PUBLIC_SITE_URL=http://localhost:3000/  # URL of the web application

# Security
CRON_SECRET=your_cron_secret        # Secret for securing cron routes
API_SECRET=your_api_secret          # Secret for API authentication

# Supabase connection
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
In the production environment, these variables are securely stored in PM2’s ecosystem configuration file and passed to the application at runtime.

Development Environment Setup

To set up the microservice for local development:
  1. Clone the repository
    git clone https://github.com/OnlyAutomator/only-automator-microservice.git
    cd only-automator-microservice
    
  2. Install dependencies
    npm install
    
  3. Configure environment variables Create a .env file with the required variables:
    # Core configuration
    PORT=3001
    NEXT_PUBLIC_SITE_URL=http://localhost:3000/
    
    # Supabase connection
    SUPABASE_URL=https://qyzigfweeamoatcrnpgc.supabase.co
    SUPABASE_ANON_KEY=your_anon_key
    SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
    
    # Security
    CRON_SECRET=your_cron_secret
    API_SECRET=your_api_secret
    
  4. Generate Supabase type definitions
    npm run generate-types
    
  5. Build the TypeScript code
    npm run build
    
  6. Start the service in development mode
    npm run dev
    

Production Deployment

For production deployment, we use PM2 to manage the application processes:
  1. Build the application
    npm run build
    
  2. Configure PM2 The ecosystem.config.js file contains the PM2 configuration:
    module.exports = {
      apps: [{
        name: 'express-server',
        script: 'build/app.js',
        instances: 1,
        autorestart: true,
        watch: false,
        env: {
          SUPABASE_URL: process.env.SUPABASE_URL,
          SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY,
          SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
          NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
          PORT: process.env.PORT,
          CRON_SECRET: process.env.CRON_SECRET,
          API_SECRET: process.env.API_SECRET,
        },
      },
      {
        name: 'cron-job',
        script: 'build/cron_job.js',
        cron_restart: '* * * * *', // restart every minute
        // cron_restart: '0 */6 * * *', // every 6 hours (production recommendation)
        env: {
          SUPABASE_URL: process.env.SUPABASE_URL,
          SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY,
          SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY,
          NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
          PORT: process.env.PORT,
          CRON_SECRET: process.env.CRON_SECRET,
          API_SECRET: process.env.API_SECRET,
        },
      }],
    };
    
  3. Start the application with PM2
    pm2 start ecosystem.config.js
    
  4. Configure PM2 to start on system boot
    pm2 startup
    pm2 save
    
  5. Monitor the application
    pm2 status    # Check process status
    pm2 logs      # View logs
    pm2 monit     # Monitor CPU and memory usage
    

Cron Job Configuration

The cron job automatically processes all accounts at regular intervals. The schedule is configured in ecosystem.config.js:
{
  name: 'cron-job',
  script: 'build/cron_job.js',
  cron_restart: '* * * * *', // Every minute (for testing)
  // cron_restart: '0 */6 * * *', // Every 6 hours (recommended for production)
}
To modify the schedule, update the cron_restart parameter using standard cron syntax:
* * * * *
│ │ │ │ │
│ │ │ │ └─── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ └───── month (1-12)
│ │ └─────── day of month (1-31)
│ └───────── hour (0-23)
└─────────── minute (0-59)
Common examples:
  • */30 * * * *: Every 30 minutes
  • 0 */6 * * *: Every 6 hours
  • 0 0 * * *: Once a day at midnight
  • 0 0 * * 0: Once a week on Sunday

Scaling Considerations

Vertical Scaling

  • Increase server resources (CPU/RAM) to handle more concurrent processes
  • Adjust Node.js memory limits with --max-old-space-size flag:
    NODE_OPTIONS="--max-old-space-size=4096" npm run start
    

Horizontal Scaling

  • Deploy multiple instances behind a load balancer
  • Use PM2 cluster mode for the Express server:
    {
      name: 'express-server',
      script: 'build/app.js',
      instances: 'max', // Use maximum available CPUs
      exec_mode: 'cluster',
    }
    

Queue Optimization

  • Adjust the number of concurrent queues based on server capacity:
    const NUM_QUEUES = 3; // Default is 3, increase for higher concurrency
    
  • Implement priority queueing for critical accounts:
    const queue = new Queue({
      priority: function(task, cb) {
        // Determine task priority (lower number = higher priority)
        if (task.isPremium) return cb(null, 1);
        return cb(null, 10);
      }
    });
    

Data Extraction Process

The microservice extracts several types of data from OnlyFans:
  1. User Statistics
    • Subscriber count and growth rate
    • Revenue metrics
    • Content engagement metrics
  2. Engagement Metrics
    • Message response rates
    • Fan interaction frequency
    • Content popularity analytics
  3. Fan Data
    • Active subscribers list
    • Spending patterns
    • Engagement behavior
The data extraction process uses Puppeteer to navigate through various pages on OnlyFans, executing a sequence of operations:
// Example of statistics extraction logic
async function extractStatisticsData(page) {
  await page.goto('https://onlyfans.com/my/statistics/');
  await page.waitForSelector('.stats-card');
  
  return await page.evaluate(() => {
    const stats = {};
    
    // Extract subscriber metrics
    const subCount = document.querySelector('.subscribers-count');
    if (subCount) stats.subscriberCount = parseInt(subCount.textContent.trim(), 10);
    
    // Extract revenue metrics
    const revenue = document.querySelector('.revenue-total');
    if (revenue) stats.totalRevenue = parseFloat(revenue.textContent.replace(/[^0-9.]/g, ''));
    
    // Extract other metrics...
    
    return stats;
  });
}

Troubleshooting

IssueSolution
Connection timeoutIncrease Puppeteer navigation timeout: page.setDefaultNavigationTimeout(60000);
Memory leaksEnsure Puppeteer instances are closed properly in finally blocks
Database errorsVerify Supabase credentials and check database schema compatibility
Queue bottlenecksIncrease the number of concurrent queues and monitor queue statistics
Cron job failuresCheck logs with pm2 logs cron-job and verify environment variables
For deeper debugging, enable verbose logging by adjusting the Winston logger configuration:
// Set Winston to debug level
const logger = winston.createLogger({
  level: 'debug', // Change from 'info' to 'debug'
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.printf(({ level, message, timestamp }) => {
      return `${timestamp} ${level}: ${message}`;
    })
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

Security Best Practices

  • Environment Variables: All sensitive credentials stored in environment variables
  • API Authentication: API endpoints secured with API_SECRET token validation
  • CORS Policy: Configured for specific origins in production:
    app.use(cors({
      origin: process.env.NODE_ENV === 'production' 
        ? [process.env.NEXT_PUBLIC_SITE_URL] 
        : '*',
      methods: ['GET', 'POST', 'OPTIONS'],
      credentials: true,
      allowedHeaders: ['Content-Type', 'Authorization']
    }));
    
  • Helmet.js: Secures Express app with various HTTP headers:
    app.use(helmet({
      crossOriginResourcePolicy: { policy: "cross-origin" },
      contentSecurityPolicy: false
    }));
    
  • Input Validation: All API inputs validated before processing
  • Regular Updates: Dependencies kept updated to patch security vulnerabilities

Integration with Other Components

  • Web Application: Displays data collected by the microservice through Supabase real-time subscriptions
  • Chrome Extension: Provides authentication data (cookies, localStorage, sessionStorage) for the microservice
  • Supabase Database: Stores all processed data and enables real-time updates through PostgreSQL change data capture