TFLogin API Documentation
This documentation describes the available API endpoints for the TFLogin application.
Quick Start 🚀
Here are some quick examples to get you started with the TFLogin API:
SET API KEY
curl "http://127.0.0.1:5000/set-api-key?key=point_xxx"
import requests
# Set API key
response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx")
print(response.json())
fetch('http://127.0.0.1:5000/set-api-key?key=point_xxx')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Set API key
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("http://127.0.0.1:5000/set-api-key?key=point_xxx");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
Launch a browser with profile ID 1:
curl -X POST http://127.0.0.1:5000/run-trungfox \
-H "Content-Type: application/json" \
-d '{"ID_Folder": 1, "isServer": true}'
import requests
import json
# Launch browser with profile ID 1
response = requests.post(
"http://127.0.0.1:5000/run-trungfox",
json={"ID_Folder": 1, "isServer": True},
headers={"Content-Type": "application/json"}
)
print(response.json())
fetch('http://127.0.0.1:5000/run-trungfox', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ID_Folder: 1,
isServer: true
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Launch browser with profile ID 1
using var httpClient = new HttpClient();
var request = new
{
ID_Folder = 1,
isServer = true
};
var jsonContent = JsonSerializer.Serialize(request);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("http://127.0.0.1:5000/run-trungfox", content);
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseContent);
}
}
Check available profiles:
curl http://127.0.0.1:5000/list-ID_Folder
import requests
# Check available profiles
response = requests.get("http://127.0.0.1:5000/list-ID_Folder")
profiles = response.json()
print(profiles)
fetch('http://127.0.0.1:5000/list-ID_Folder')
.then(response => response.json())
.then(profiles => console.log(profiles))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Check available profiles
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("http://127.0.0.1:5000/list-ID_Folder");
var content = await response.Content.ReadAsStringAsync();
// Parse and display the profile IDs
var profiles = JsonSerializer.Deserialize(content);
Console.WriteLine("Available profiles:");
foreach (var profileId in profiles)
{
Console.WriteLine($"- {profileId}");
}
}
}
Kill a running browser:
curl http://127.0.0.1:5000/kill-process?ID_Folder=1
import requests
# Kill a running browser
response = requests.get("http://127.0.0.1:5000/kill-process?ID_Folder=1")
print(response.json())
fetch('http://127.0.0.1:5000/kill-process?ID_Folder=1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
// Kill a running browser
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync("http://127.0.0.1:5000/kill-process?ID_Folder=1");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
All API endpoints are available at: http://127.0.0.1:5000
Authentication
Currently, no authentication is required to access these APIs.
API Endpoints
POST /run-trungfox
Launch a TrungFox browser instance with specific profile settings.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| ID_Folder | String or Number | Yes | The profile ID to launch |
| isServer | Boolean | No | Whether to run in server mode (Default: false) |
| screen | String | No | Screen dimensions in format "widthxheight" (Default: "0x0") |
| on_toolbar_close | Number | No | Enable close toolbar button (0 or 1, Default: 0) |
| on_toolbar_url | Number | No | Enable URL toolbar (0 or 1, Default: 0) |
Example Request
curl -X POST http://127.0.0.1:5000/run-trungfox \
-H "Content-Type: application/json" \
-d '{
"ID_Folder": 1,
"isServer": true,
"screen": "1024x768",
"on_toolbar_close": 1,
"on_toolbar_url": 1
}'
import requests
import json
url = "http://127.0.0.1:5000/run-trungfox"
payload = {
"ID_Folder": 1,
"isServer": True,
"screen": "1024x768",
"on_toolbar_close": 1,
"on_toolbar_url": 1
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, data=json.dumps(payload), headers=headers)
print(response.json())
fetch('http://127.0.0.1:5000/run-trungfox', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ID_Folder: 1,
isServer: true,
screen: "1024x768",
on_toolbar_close: 1,
on_toolbar_url: 1
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var url = "http://127.0.0.1:5000/run-trungfox";
var payload = new
{
ID_Folder = 1,
isServer = true,
screen = "1024x768",
on_toolbar_close = 1,
on_toolbar_url = 1
};
var json = JsonSerializer.Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
using var client = new HttpClient();
var response = await client.PostAsync(url, content);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Successful Response
{
"status": "success",
"ID_Folder": "1",
"Url_WS": "ws://localhost:55242/8a5d17184bbff2fbf0f5bbf5479742a2",
"api_stop": "http://127.0.0.1:5000/kill-process?ID_Folder=1"
}
Error Response
{
"status": "error",
"error": "Invalid ID_Folder"
}
GET /kill-all-process
Kill all running browser instance.
Example Request
curl http://127.0.0.1:5000/kill-all-process
import requests url = "http://127.0.0.1:5000/kill-all-process" response = requests.get(url) print(response.json())
fetch('http://127.0.0.1:5000/kill-all-process')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var url = "http://127.0.0.1:5000/kill-all-process";
using var client = new HttpClient();
var response = await client.GetAsync(url);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Successful Response
{
"status": "success"
}
Error Response
{
"status": "error",
"error": "No process found."
}
GET /kill-process
Kill a running browser instance by profile ID.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| ID_Folder | String or Number | Yes | The profile ID to terminate |
Example Request
curl http://127.0.0.1:5000/kill-process?ID_Folder=1
import requests
url = "http://127.0.0.1:5000/kill-process"
params = {"ID_Folder": 1}
response = requests.get(url, params=params)
print(response.json())
fetch('http://127.0.0.1:5000/kill-process?ID_Folder=1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var baseUrl = "http://127.0.0.1:5000/kill-process";
var query = "?ID_Folder=1";
var url = baseUrl + query;
using var client = new HttpClient();
var response = await client.GetAsync(url);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Successful Response
{
"status": "killed",
"ID_Folder": "1"
}
Error Response
{
"status": "error",
"error": "No process found for this ID_Folder"
}
GET /list-ID_Folder
Get a list of all available profile IDs.
Example Request
curl http://127.0.0.1:5000/list-ID_Folder
import requests url = "http://127.0.0.1:5000/list-ID_Folder" response = requests.get(url) print(response.json())
fetch('http://127.0.0.1:5000/list-ID_Folder')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var url = "http://127.0.0.1:5000/list-ID_Folder";
using var client = new HttpClient();
var response = await client.GetAsync(url);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Response
[1, 2, 3, 4, 5]
GET /list-worker-id
Get a list of all workers and their assigned profile IDs.
Example Request
curl http://127.0.0.1:5000/list-worker-id
import requests url = "http://127.0.0.1:5000/list-worker-id" response = requests.get(url) print(response.json())
fetch('http://127.0.0.1:5000/list-worker-id')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var url = "http://127.0.0.1:5000/list-worker-id";
using var client = new HttpClient();
var response = await client.GetAsync(url);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Response
[
{
"worker_id": 0,
"ID_Folders": ["1", "3"]
},
{
"worker_id": 1,
"ID_Folders": ["2"]
},
{
"worker_id": 2,
"ID_Folders": []
}
]
GET /reset-worker
Reset a specific worker by ID.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| worker_id | Number | Yes | The worker ID to reset |
Example Request
curl http://127.0.0.1:5000/reset-worker?worker_id=1
import requests
url = "http://127.0.0.1:5000/reset-worker"
params = {"worker_id": 1}
response = requests.get(url, params=params)
print(response.json())
fetch('http://127.0.0.1:5000/reset-worker?worker_id=1')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var baseUrl = "http://127.0.0.1:5000/reset-worker";
var query = "?worker_id=1";
var url = baseUrl + query;
using var client = new HttpClient();
var response = await client.GetAsync(url);
var responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
}
}
Successful Response
{
"status": "restarting",
"worker_id": 1
}
Error Response
{
"status": "error",
"error": "Invalid worker_id"
}
WebSocket API
When launching a browser in server mode, a WebSocket URL is returned. This WebSocket can be used to control the browser remotely.
The WebSocket URL is available in the response of the /run-trungfox endpoint as Url_WS.
WebSocket Automation Examples
Example: Click a Button for 28 Seconds
This example demonstrates how to launch a browser, navigate to a website, and continuously click a button for 28 seconds using WebSockets.
Advanced Example: Auto-Clicking with Performance Monitoring
This more advanced example includes error handling, performance monitoring, and detailed statistics.
import time
import requests
import asyncio
import json
from datetime import datetime
import os
from playwright.async_api import async_playwright
import traceback
# Configuration
API_URL = "http://127.0.0.1:5000"
PROFILE_ID = 1
TARGET_URL = "https://mmo69.com/button-click-me.html"
BUTTON_ID = "randomButton"
DURATION_SECONDS = 28
SCREENSHOT_FOLDER = "screenshots"
TAKE_SCREENSHOTS = True
SCREENSHOT_INTERVAL = 5 # seconds
async def run_automated_task():
print(f"Starting automated clicking task for {TARGET_URL}")
print(f"Using profile ID: {PROFILE_ID}, Duration: {DURATION_SECONDS} seconds")
# Create screenshots directory if needed
if TAKE_SCREENSHOTS:
os.makedirs(SCREENSHOT_FOLDER, exist_okay=True)
# Step 1: Launch browser via TFLogin API
try:
response = requests.post(
f"{API_URL}/run-trungfox",
json={
"ID_Folder": PROFILE_ID,
"isServer": 1, # Enable WebSocket mode (1 = true in the API)
"screen": "1024x768",
"on_toolbar_close": 1,
"on_toolbar_url": 1
},
headers={"Content-Type": "application/json"}
)
response.raise_for_status()
data = response.json()
websocket_url = data.get("Url_WS")
if not websocket_url:
raise ValueError("No WebSocket URL returned in the API response")
print(f"Browser launched successfully")
print(f"WebSocket URL: {websocket_url}")
print(f"API stop URL: {data.get('api_stop')}")
except Exception as e:
print(f"Failed to launch browser: {e}")
traceback.print_exc()
return
# Step 2: Connect to browser and perform automation
try:
async with async_playwright() as p:
# Connect to browser using the WebSocket URL
browser = await p.firefox.connect(websocket_url)
print("Successfully connected to browser")
# Create a new page
page = await browser.new_page()
# Navigate to target URL
print(f"Navigating to {TARGET_URL}")
await page.goto(TARGET_URL)
# Take initial screenshot
if TAKE_SCREENSHOTS:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = f"{SCREENSHOT_FOLDER}/initial_{timestamp}.png"
await page.screenshot(path=screenshot_path)
print(f"Initial screenshot saved: {screenshot_path}")
# Verify the button exists
button = await page.query_selector(f"#{BUTTON_ID}")
if not button:
print(f"ERROR: Button with ID '{BUTTON_ID}' not found on the page")
await browser.close()
return
print(f"Button '{BUTTON_ID}' found, starting click sequence...")
# Set up click loop with performance tracking
start_time = time.time()
end_time = start_time + DURATION_SECONDS
click_count = 0
last_screenshot_time = start_time
performance_data = []
while time.time() < end_time:
current_time = time.time()
elapsed = current_time - start_time
remaining = max(0, DURATION_SECONDS - elapsed)
try:
# Click the button
await page.click(f"#{BUTTON_ID}")
click_count += 1
# Record performance data every 10 clicks
if click_count % 10 == 0:
click_rate = click_count / elapsed if elapsed > 0 else 0
performance_data.append({
"elapsed_seconds": elapsed,
"clicks": click_count,
"clicks_per_second": click_rate
})
print(f"Clicks: {click_count}, Rate: {click_rate:.2f}/sec, Remaining: {remaining:.1f}s")
# Take screenshot at intervals
if TAKE_SCREENSHOTS and (current_time - last_screenshot_time >= SCREENSHOT_INTERVAL):
last_screenshot_time = current_time
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = f"{SCREENSHOT_FOLDER}/progress_{timestamp}_{click_count}.png"
await page.screenshot(path=screenshot_path)
print(f"Progress screenshot saved: {screenshot_path}")
except Exception as e:
print(f"Error during click operation: {e}")
# Small delay between clicks
await asyncio.sleep(0.1)
# Calculate final statistics
total_time = time.time() - start_time
click_rate = click_count / total_time if total_time > 0 else 0
print("\n=== Task Complete ===")
print(f"Total clicks: {click_count}")
print(f"Total time: {total_time:.2f} seconds")
print(f"Average click rate: {click_rate:.2f} clicks per second")
# Save performance report
report_data = {
"summary": {
"url": TARGET_URL,
"button_id": BUTTON_ID,
"total_clicks": click_count,
"total_seconds": total_time,
"avg_click_rate": click_rate
},
"timeline": performance_data
}
with open(f"{SCREENSHOT_FOLDER}/performance_report.json", "w") as f:
json.dump(report_data, f, indent=2)
# Take final screenshot
if TAKE_SCREENSHOTS:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = f"{SCREENSHOT_FOLDER}/final_{timestamp}.png"
await page.screenshot(path=screenshot_path)
print(f"Final screenshot saved: {screenshot_path}")
# Close browser connection
await browser.close()
except Exception as e:
print(f"Automation error: {e}")
traceback.print_exc()
finally:
# Clean up: kill the browser process
try:
print("Terminating browser via API...")
requests.get(f"{API_URL}/kill-process?ID_Folder={PROFILE_ID}")
print("Browser terminated successfully")
except Exception as e:
print(f"Error terminating browser: {e}")
if __name__ == "__main__":
# Set API key
response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx")
print(response.json())
asyncio.run(run_automated_task())
const { firefox } = require('playwright');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
// Configuration
const API_URL = 'http://127.0.0.1:5000';
const PROFILE_ID = 1;
const TARGET_URL = 'https://mmo69.com/button-click-me.html';
const BUTTON_ID = 'randomButton';
const DURATION_SECONDS = 28;
const SCREENSHOT_FOLDER = 'screenshots';
const TAKE_SCREENSHOTS = true;
const SCREENSHOT_INTERVAL = 5; // seconds
async function runAutomatedTask() {
console.log(`Starting automated clicking task for ${TARGET_URL}`);
console.log(`Using profile ID: ${PROFILE_ID}, Duration: ${DURATION_SECONDS} seconds`);
// Create screenshots directory if needed
if (TAKE_SCREENSHOTS) {
if (!fs.existsSync(SCREENSHOT_FOLDER)) {
fs.mkdirSync(SCREENSHOT_FOLDER, { recursive: true });
}
}
// Step 1: Set API key
try {
await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
console.log('API key set successfully');
} catch (error) {
console.error('Failed to set API key:', error.message);
return;
}
let browserData;
try {
// Step 2: Launch browser via TFLogin API
const response = await axios.post(`${API_URL}/run-trungfox`, {
ID_Folder: PROFILE_ID,
isServer: true,
screen: '1024x768',
on_toolbar_close: 1,
on_toolbar_url: 1
}, {
headers: { 'Content-Type': 'application/json' }
});
browserData = response.data;
const websocketUrl = browserData.Url_WS;
if (!websocketUrl) {
throw new Error('No WebSocket URL returned in the API response');
}
console.log('Browser launched successfully');
console.log(`WebSocket URL: ${websocketUrl}`);
console.log(`API stop URL: ${browserData.api_stop}`);
// Step 3: Connect to browser and perform automation
const browser = await firefox.connect({ wsEndpoint: websocketUrl });
console.log('Successfully connected to browser');
// Create a new page
const page = await browser.newPage();
// Navigate to target URL
console.log(`Navigating to ${TARGET_URL}`);
await page.goto(TARGET_URL);
// Take initial screenshot
if (TAKE_SCREENSHOTS) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotPath = path.join(SCREENSHOT_FOLDER, `initial_${timestamp}.png`);
await page.screenshot({ path: screenshotPath });
console.log(`Initial screenshot saved: ${screenshotPath}`);
}
// Verify the button exists
const button = await page.$(`#${BUTTON_ID}`);
if (!button) {
console.error(`ERROR: Button with ID '${BUTTON_ID}' not found on the page`);
await browser.close();
return;
}
console.log(`Button '${BUTTON_ID}' found, starting click sequence...`);
// Set up click loop with performance tracking
const startTime = Date.now();
const endTime = startTime + (DURATION_SECONDS * 1000);
let clickCount = 0;
let lastScreenshotTime = startTime;
const performanceData = [];
while (Date.now() < endTime) {
const currentTime = Date.now();
const elapsed = (currentTime - startTime) / 1000;
const remaining = Math.max(0, DURATION_SECONDS - elapsed);
try {
// Click the button
await page.click(`#${BUTTON_ID}`);
clickCount++;
// Record performance data every 10 clicks
if (clickCount % 10 === 0) {
const clickRate = clickCount / elapsed;
performanceData.push({
elapsed_seconds: elapsed,
clicks: clickCount,
clicks_per_second: clickRate
});
console.log(`Clicks: ${clickCount}, Rate: ${clickRate.toFixed(2)}/sec, Remaining: ${remaining.toFixed(1)}s`);
}
// Take screenshot at intervals
if (TAKE_SCREENSHOTS && ((currentTime - lastScreenshotTime) >= SCREENSHOT_INTERVAL * 1000)) {
lastScreenshotTime = currentTime;
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotPath = path.join(SCREENSHOT_FOLDER, `progress_${timestamp}_${clickCount}.png`);
await page.screenshot({ path: screenshotPath });
console.log(`Progress screenshot saved: ${screenshotPath}`);
}
} catch (error) {
console.error(`Error during click operation: ${error.message}`);
}
// Small delay between clicks
await page.waitForTimeout(100);
}
// Calculate final statistics
const totalTime = (Date.now() - startTime) / 1000;
const clickRate = clickCount / totalTime;
console.log('\n=== Task Complete ===');
console.log(`Total clicks: ${clickCount}`);
console.log(`Total time: ${totalTime.toFixed(2)} seconds`);
console.log(`Average click rate: ${clickRate.toFixed(2)} clicks per second`);
// Save performance report
const reportData = {
summary: {
url: TARGET_URL,
button_id: BUTTON_ID,
total_clicks: clickCount,
total_seconds: totalTime,
avg_click_rate: clickRate
},
timeline: performanceData
};
fs.writeFileSync(
path.join(SCREENSHOT_FOLDER, 'performance_report.json'),
JSON.stringify(reportData, null, 2)
);
// Take final screenshot
if (TAKE_SCREENSHOTS) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const screenshotPath = path.join(SCREENSHOT_FOLDER, `final_${timestamp}.png`);
await page.screenshot({ path: screenshotPath });
console.log(`Final screenshot saved: ${screenshotPath}`);
}
// Close browser connection
await browser.close();
} catch (error) {
console.error(`Automation error: ${error.message}`);
console.error(error.stack);
} finally {
// Clean up: kill the browser process
try {
console.log('Terminating browser via API...');
await axios.get(`${API_URL}/kill-process?ID_Folder=${PROFILE_ID}`);
console.log('Browser terminated successfully');
} catch (error) {
console.error(`Error terminating browser: ${error.message}`);
}
}
}
// Run the example
runAutomatedTask().catch(console.error);
Multi-Thread Example: Managing Multiple Browser Instances with Button Clicking
This advanced example demonstrates how to run multiple browser instances with different profiles, limited by a maximum thread count.
import requests
import json
import threading
import time
import win32gui
import win32api
import win32con
import queue
import sys
import asyncio
from playwright.async_api import async_playwright
from datetime import datetime
import os
# Configuration
API_URL = "http://127.0.0.1:5000"
MAX_THREADS = 6 # Maximum number of concurrent browser instances
WINDOW_WIDTH = 222
WINDOW_HEIGHT = 345
TARGET_URL = "https://mmo69.com/button-click-me.html"
BUTTON_ID = "randomButton" # ID of the button to click
CLICK_DURATION = 28 # How many seconds to click the button
RUN_DURATION = 60 # Total time to run each browser instance
SCREENSHOT_DIR = "screenshots"
# Thread-safe queue for managing profile IDs
profile_queue = queue.Queue()
active_threads = set() # Track running threads
thread_lock = threading.Lock() # For thread-safe operations
thread_complete_event = threading.Event() # Signal when a thread completes
def enum_windows_callback(hwnd, windows):
"""Callback for EnumWindows to find Firefox windows"""
if win32gui.IsWindowVisible(hwnd) and win32gui.GetClassName(hwnd) == "MozillaWindowClass":
windows.append(hwnd)
def arrange_firefox_windows():
"""Arrange Firefox windows in a grid layout on the screen"""
windows = []
win32gui.EnumWindows(enum_windows_callback, windows)
screen_width = win32api.GetSystemMetrics(0) # SM_CXSCREEN
screen_height = win32api.GetSystemMetrics(1) # SM_CYSCREEN
if not windows:
print("Couldn't find any Firefox windows.")
return
cols = max(1, screen_width // WINDOW_WIDTH)
print(f"Arranging {len(windows)} Firefox windows...")
for idx, hwnd in enumerate(windows):
row = idx // cols
col = idx % cols
x = col * WINDOW_WIDTH
y = row * WINDOW_HEIGHT
win32gui.MoveWindow(hwnd, x, y, WINDOW_WIDTH, WINDOW_HEIGHT, True)
print("Windows arranged successfully")
def get_available_profiles():
"""Get all available profile IDs from the API"""
try:
response = requests.get(f"{API_URL}/list-ID_Folder")
if response.status_code == 200:
profiles = response.json()
print(f"Found {len(profiles)} available profiles")
return profiles
else:
print(f"Error getting profiles: {response.status_code} - {response.text}")
return []
except Exception as e:
print(f"Exception getting profiles: {e}")
return []
async def click_button_task(websocket_url, profile_id, thread_name):
"""Task to click the button on the webpage for a specific duration"""
try:
console.log(`[${thread_name}] Connecting to browser via WebSocket...`);
async with async_playwright() as p:
# Connect to the browser via WebSocket
browser = await p.firefox.connect(websocket_url);
# Create a new page
page = await browser.new_page();
# Navigate to the target URL
console.log(`[${thread_name}] Navigating to ${TARGET_URL}`);
await page.goto(TARGET_URL);
# Wait for the page to load
await page.wait_for_load_state("networkidle");
# Check if the button exists
button = await page.query_selector(f"#{BUTTON_ID}");
if not button:
console.log(`[${thread_name}] Error: Button with ID '${BUTTON_ID}' not found`);
await browser.close();
return;
}
console.log(`[${thread_name}] Found button #${BUTTON_ID}, starting to click...`);
# Set up click loop
start_time = time.time();
end_time = start_time + CLICK_DURATION;
click_count = 0;
# Create screenshot directory for this profile
profile_screenshot_dir = os.path.join(SCREENSHOT_DIR, `profile_${profile_id}`);
os.makedirs(profile_screenshot_dir, exist_ok=True);
# Take initial screenshot
await page.screenshot(path=f"{profile_screenshot_dir}/initial.png");
while time.time() < end_time:
# Click the button
await page.click(f"#{BUTTON_ID}");
click_count += 1;
# Print status every 10 clicks
if click_count % 10 == 0:
elapsed = time.time() - start_time;
remaining = max(0, CLICK_DURATION - elapsed);
console.log(`[${thread_name}] Clicks: ${click_count}, Elapsed: ${elapsed:.1f}s, Remaining: ${remaining:.1f}s`);
# Small delay between clicks
await asyncio.sleep(0.1);
console.log(`[${thread_name}] Finished clicking. Total clicks: ${click_count}`);
# Take a final screenshot
await page.screenshot(path=f"{profile_screenshot_dir}/final.png");
# Wait for any remaining time in RUN_DURATION
remaining_time = RUN_DURATION - (time.time() - start_time);
if remaining_time > 0:
console.log(`[${thread_name}] Waiting for remaining ${remaining_time:.1f} seconds...`);
await asyncio.sleep(remaining_time);
# Close browser connection
await browser.close();
} catch (Exception e) {
console.log(`[${thread_name}] Error during button clicking: ${e}`);
}
}
def run_browser_instance(profile_id):
"""Run a browser instance with the given profile ID"""
thread_name = threading.current_thread().name
console.log(`[${thread_name}] Starting browser with profile ID: ${profile_id}`);
try {
# Launch browser with WebSocket server
response = requests.post(
f"{API_URL}/run-trungfox",
json={
"ID_Folder": profile_id,
"isServer": True,
"screen": f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}",
"on_toolbar_close": 0,
"on_toolbar_url": 1
}
);
if response.status_code != 200:
console.log(`[${thread_name}] Error launching browser: ${response.text}`);
return;
data = response.json();
browser_id = data.get("ID_Folder");
websocket_url = data.get("Url_WS");
if not websocket_url:
console.log(`[${thread_name}] No WebSocket URL returned, cannot control browser.`);
return;
console.log(`[${thread_name}] Browser launched successfully with ID: ${browser_id}`);
console.log(`[${thread_name}] WebSocket URL: ${websocket_url}`);
# Run the button clicking task in a separate asyncio loop
asyncio.run(click_button_task(websocket_url, profile_id, thread_name));
# Kill browser instance
kill_url = f"{API_URL}/kill-process?ID_Folder={profile_id}";
kill_response = requests.get(kill_url);
if kill_response.status_code == 200:
console.log(`[${thread_name}] Browser killed successfully`);
else:
console.log(`[${thread_name}] Error killing browser: ${kill_response.text}`);
} catch (Exception e) {
console.log(`[${thread_name}] Exception in thread: ${e}`);
} finally {
# Signal that this thread has completed
with thread_lock:
if profile_id in active_threads:
active_threads.remove(profile_id);
thread_complete_event.set(); # Signal that a thread has completed
}
def worker_manager():
"""Main function to manage worker threads"""
# Create screenshots directory
os.makedirs(SCREENSHOT_DIR, exist_ok=True);
# Get all available profiles
profile_ids = get_available_profiles();
if not profile_ids:
console.log('No profiles available. Exiting.');
return;
# Add all profile IDs to the queue
for profile_id in profile_ids:
profile_queue.put(profile_id);
total_profiles = profile_queue.qsize();
completed_profiles = 0;
console.log(`Starting browser automation with ${MAX_THREADS} max concurrent instances`);
console.log(`Each browser will click button '${BUTTON_ID}' for ${CLICK_DURATION} seconds`);
# Process profiles until the queue is empty
while not profile_queue.empty() or active_threads:
# Start new threads if we're under the limit and have profiles waiting
while len(active_threads) < MAX_THREADS and not profile_queue.empty():
next_profile = profile_queue.get();
# Create and start thread
with thread_lock:
active_threads.add(next_profile);
thread = threading.Thread(
target=run_browser_instance,
args=(next_profile,),
name=f"Browser-{next_profile}"
);
thread.daemon = True;
thread.start();
completed_profiles += 1;
console.log(`Started ${completed_profiles}/${total_profiles} profiles. Active: ${len(active_threads)}`);
time.sleep(1); # Small delay between launching instances
# Once we've hit our thread limit, arrange the windows
if len(active_threads) == MAX_THREADS or profile_queue.empty():
time.sleep(2); # Give browsers time to launch
arrange_firefox_windows();
# Wait for a thread to complete before checking again
thread_complete_event.wait(timeout=5);
thread_complete_event.clear();
console.log('All browser instances processed successfully!');
}
if __name__ == "__main__":
# Set API key
response = requests.get("http://127.0.0.1:5000/set-api-key?key=point_xxx");
console.log(response.json());
try {
worker_manager();
} catch (KeyboardInterrupt:
console.log('\nExiting due to user interrupt');
# Attempt to clean up any remaining browsers
try {
active_ids = list(active_threads);
for profile_id in active_ids:
try {
console.log(`Cleaning up browser for profile ${profile_id}...`);
requests.get(f"{API_URL}/kill-process?ID_Folder={profile_id}");
} catch (Exception e) {
console.log(`Error during cleanup: ${e}`);
}
} catch (Exception e) {
console.log(`Error during final cleanup: ${e}`);
}
sys.exit(0);
const axios = require('axios');
const { firefox } = require('playwright');
const fs = require('fs');
const path = require('path');
const os = require('os');
// Configuration
const API_URL = 'http://127.0.0.1:5000';
const MAX_CONCURRENT_BROWSERS = 4;
const WINDOW_WIDTH = 222;
const WINDOW_HEIGHT = 345;
const TARGET_URL = 'https://mmo69.com/button-click-me.html';
const BUTTON_ID = 'randomButton';
const CLICK_DURATION = 28; // seconds
const RUN_DURATION = 60; // seconds
const SCREENSHOT_DIR = 'screenshots';
// State tracking
let activeProfiles = new Set();
let availableProfiles = [];
let completedProfiles = 0;
// Create screenshots directory
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}
// Helper function for delay
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Get available profile IDs from the API
async function getAvailableProfiles() {
try {
const response = await axios.get(`${API_URL}/list-ID_Folder`);
const profiles = response.data;
console.log(`Found ${profiles.length} available profiles`);
return profiles;
} catch (error) {
console.error(`Error getting profiles: ${error.message}`);
return [];
}
}
// Click button task for a specific browser instance
async function clickButtonTask(websocketUrl, profileId, instanceName) {
try {
console.log(`[${instanceName}] Connecting to browser via WebSocket...`);
const browser = await firefox.connect({ wsEndpoint: websocketUrl });
const page = await browser.newPage();
// Navigate to the target URL
console.log(`[${instanceName}] Navigating to ${TARGET_URL}`);
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle');
// Check if the button exists
const button = await page.$(`#${BUTTON_ID}`);
if (!button) {
console.error(`[${instanceName}] Error: Button with ID '${BUTTON_ID}' not found`);
await browser.close();
return;
}
console.log(`[${instanceName}] Found button #${BUTTON_ID}, starting to click...`);
// Create a new directory for screenshots
const profileDir = path.join(SCREENSHOT_DIR, `profile_${profileId}`);
fs.mkdirSync(profileDir, { recursive: true });
// Take initial screenshot
await page.screenshot({ path: path.join(profileDir, 'initial.png') });
// Set up click loop
const startTime = Date.now();
const endTime = startTime + (CLICK_DURATION * 1000);
let clickCount = 0;
while (Date.now() < endTime) {
// Click the button
await page.click(`#${BUTTON_ID}`);
clickCount++;
// Log status every 10 clicks
if (clickCount % 10 === 0) {
const elapsed = (Date.now() - startTime) / 1000;
const remaining = Math.max(0, CLICK_DURATION - elapsed);
console.log(`[${instanceName}] Clicks: ${clickCount}, Elapsed: ${elapsed.toFixed(1)}s, Remaining: ${remaining.toFixed(1)}s`);
}
// Small delay between clicks
await page.waitForTimeout(100);
}
console.log(`[${instanceName}] Finished clicking. Total clicks: ${clickCount}`);
// Take a final screenshot
await page.screenshot({ path: path.join(profileDir, 'final.png') });
// Wait for any remaining time in RUN_DURATION
const elapsedTotal = (Date.now() - startTime) / 1000;
const remainingTime = RUN_DURATION - elapsedTotal;
if (remainingTime > 0) {
console.log(`[${instanceName}] Waiting for remaining ${remainingTime.toFixed(1)} seconds...`);
await delay(remainingTime * 1000);
}
// Close browser connection
await browser.close();
} catch (error) {
console.error(`[${instanceName}] Error during button clicking: ${error.message}`);
}
}
// Run a browser instance with the given profile ID
async function runBrowserInstance(profileId) {
const instanceName = `Browser-${profileId}`;
console.log(`[${instanceName}] Starting browser with profile ID: ${profileId}`);
try {
// Set API key
await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
// Launch browser with WebSocket server
const response = await axios.post(`${API_URL}/run-trungfox`, {
ID_Folder: profileId,
isServer: true,
screen: `${WINDOW_WIDTH}x${WINDOW_HEIGHT}`,
on_toolbar_close: 0,
on_toolbar_url: 1
});
const data = response.data;
const browserId = data.ID_Folder;
const websocketUrl = data.Url_WS;
if (!websocketUrl) {
console.error(`[${instanceName}] No WebSocket URL returned, cannot control browser.`);
return;
}
console.log(`[${instanceName}] Browser launched successfully with ID: ${browserId}`);
console.log(`[${instanceName}] WebSocket URL: ${websocketUrl}`);
// Run the button clicking task
await clickButtonTask(websocketUrl, profileId, instanceName);
// Kill browser instance
const killResponse = await axios.get(`${API_URL}/kill-process?ID_Folder=${profileId}`);
console.log(`[${instanceName}] Browser killed successfully`);
} catch (error) {
console.error(`[${instanceName}] Exception: ${error.message}`);
} finally {
// Signal that this browser has completed
activeProfiles.delete(profileId);
process.nextTick(processQueue); // Continue processing the queue
}
}
// Process the queue of profiles
async function processQueue() {
// If we're already at max concurrency or have no more profiles, just return
if (activeProfiles.size >= MAX_CONCURRENT_BROWSERS || availableProfiles.length === 0) {
return;
}
// Start new browsers until we reach max concurrency
while (activeProfiles.size < MAX_CONCURRENT_BROWSERS && availableProfiles.length > 0) {
const nextProfile = availableProfiles.shift();
activeProfiles.add(nextProfile);
completedProfiles++;
console.log(`Started ${completedProfiles}/${totalProfiles} profiles. Active: ${activeProfiles.size}`);
// Start browser instance (don't await, let it run in parallel)
runBrowserInstance(nextProfile).catch(console.error);
// Small delay between launching instances
await delay(1000);
}
}
// Main function
async function main() {
try {
// Set API key first
await axios.get(`${API_URL}/set-api-key?key=point_xxx`);
console.log('API key set');
// Get available profiles
availableProfiles = await getAvailableProfiles();
if (availableProfiles.length === 0) {
console.log('No profiles available. Exiting.');
return;
}
// Store total for reporting
totalProfiles = availableProfiles.length;
console.log(`Starting browser automation with ${MAX_CONCURRENT_BROWSERS} max concurrent instances`);
console.log(`Each browser will click button '${BUTTON_ID}' for ${CLICK_DURATION} seconds`);
// Start processing the queue
await processQueue();
// Wait until all browsers are done
const checkInterval = setInterval(() => {
if (activeProfiles.size === 0 && availableProfiles.length === 0) {
clearInterval(checkInterval);
console.log('All browser instances processed successfully!');
}
}, 1000);
} catch (error) {
console.error(`Error in main process: ${error.message}`);
}
}
// Handle cleanup on exit
process.on('SIGINT', async () => {
console.log('\nExiting due to user interrupt');
// Clean up any remaining browsers
try {
const activeIds = Array.from(activeProfiles);
for (const profileId of activeIds) {
try {
console.log(`Cleaning up browser for profile ${profileId}...`);
await axios.get(`${API_URL}/kill-process?ID_Folder=${profileId}`);
} catch (error) {
console.error(`Error during cleanup: ${error.message}`);
}
}
} catch (error) {
console.error(`Error during final cleanup: ${error.message}`);
}
process.exit(0);
});
// Installation requirements information
console.log('Required npm packages for this script:');
console.log('npm install axios playwright');
// Run the main function
main().catch(console.error);
Installation Requirements for Examples
pip install requests pywin32 asyncio playwright==1.50.0 # Install Playwright browsers python -m playwright install firefox
npm install axios playwright@1.50.0 # Install Playwright browsers npx playwright install firefox
# Install the Microsoft.Playwright NuGet package
dotnet add package Microsoft.Playwright --version 1.50.0
# Install Playwright browsers
dotnet tool install --global Microsoft.Playwright.CLI --version 1.50.0
playwright install firefox
# Create a new console project (if needed)
dotnet new console -n TFLoginAutomation
cd TFLoginAutomation
# Example .csproj file contents
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.50.0" />
</ItemGroup>
</Project>