A RESTful API server for automated social media posting using signed-in browser sessions on VMs.
This API allows you to automate social media posts (Instagram, Twitter/X) by leveraging browser sessions where you're already logged in. Perfect for VM deployments where you maintain signed-in accounts.
How it works:
- Deploy on a Linux VM (or Windows)
- Connect via RDP to sign into your social media accounts in the browser
- Use the API to automate posts with those signed-in sessions
- No credentials stored - uses your existing browser sessions
- Supports Instagram and Twitter/X with advanced features like auto-threading
βββ app.ts # Express application
βββ index.ts # Server entry point
βββ config.ts # Configuration manager
βββ controllers/ # Business logic
βββ routes/ # API endpoints
βββ middleware/ # Error handling
βββ lib/ # Core services
βββ setup-linux.sh # Initial setup script
βββ update-server.sh # Update & restart script
βββ fix-browser-display.sh # Fix browser display issues
βββ config-browser.sh # Configure browser paths
SSH into your Linux VM and run:
chmod +x setup-linux.sh
./setup-linux.shThis installs:
- Node.js (LTS)
- Brave Browser
- System dependencies
- Server packages
Install and configure RDP on your VM:
# Install RDP server (Ubuntu/Debian)
sudo apt install xrdp
sudo systemctl enable xrdp
sudo systemctl start xrdp
# Allow RDP through firewall
sudo ufw allow 3389/tcpConnect from your local machine:
- Windows: Use built-in Remote Desktop Connection
- Mac: Download Microsoft Remote Desktop from App Store
- Linux: Use Remmina or similar
Once connected via RDP:
- Open Brave browser on the VM
- Navigate to Instagram or Twitter/X
- Sign in with "Remember me" checked
- Close browser (session is saved)
# In another terminal, configure browser paths (only needed once)
./config-browser.shThe API is now ready at http://localhost:3000
Note: Browser configuration is automatically saved to browser-config.json and persists across restarts. You only need to run config-browser.sh once!
Configure which browser session to use (saved persistently):
POST /api/browser/config # Set config (saved to file)
GET /api/browser/config # Get current config
DELETE /api/browser/config # Clear config (deletes file)Post to Instagram using signed-in session:
POST /api/instagram/post # Upload image file
POST /api/instagram/post-with-path # Use image from server path
POST /api/instagram/post-with-url # Use image from external URLPost to Twitter/X using signed-in session:
POST /api/twitter/post # Upload image file (optional)
POST /api/twitter/post-with-url # Use image from external URL (optional)Twitter Features:
- β Auto-threading: Text >280 chars automatically splits into thread
- β
Manual splitting: Use
---to specify exact thread breaks - β Optional images: Post text-only or with images
- β Fast typing: Optimized for thread creation
The config-browser.sh script does this automatically, or manually:
curl -X POST http://YOUR_VM_IP:3000/api/browser/config \
-H "Content-Type: application/json" \
-d '{
"executablePath": "/usr/bin/brave-browser",
"userDataDir": "/home/YOUR_USER/.config/BraveSoftware/Brave-Browser"
}'curl -X POST http://YOUR_VM_IP:3000/api/instagram/post \
-F "[email protected]" \
-F "caption=Automated post from my VM! π"Simple tweet:
curl -X POST http://YOUR_VM_IP:3000/api/twitter/post \
-F "text=Hello Twitter! π¦"Tweet with image:
curl -X POST http://YOUR_VM_IP:3000/api/twitter/post \
-F "text=Check out this image! πΌοΈ" \
-F "[email protected]"Long tweet (auto-threaded):
curl -X POST http://YOUR_VM_IP:3000/api/twitter/post \
-F "text=This is a very long tweet that exceeds 280 characters and will automatically be split into a thread. The system intelligently breaks it at sentence boundaries to create natural thread breaks. Each tweet will be numbered like (1/3), (2/3), etc."Manual thread splitting:
curl -X POST http://YOUR_VM_IP:3000/api/twitter/post \
-F "text=First tweet in my thread.---Second tweet continues the thought.---Final tweet wraps it up."Tweet with image URL:
curl -X POST http://YOUR_VM_IP:3000/api/twitter/post-with-url \
-H "Content-Type: application/json" \
-d '{
"text": "Tweet with external image!",
"imageUrl": "https://example.com/image.jpg"
}'- β Uses existing browser sessions (no credential storage)
- β VM-friendly deployment (Linux & Windows)
- β RESTful API with TypeScript
- β Instagram & Twitter/X support
- β File upload, server path, or external URL support
- β Auto-threading for long tweets (>280 chars)
- β
Manual thread splitting with
---markers - β Automated setup script for Linux
- β Persistent browser configuration (survives restarts)
- β CORS enabled for remote access
- β Automatic file cleanup
- β Human-like typing delays and natural behavior
import requests
VM_URL = "http://your-vm-ip:3000"
# Post to Instagram
files = {'image': open('photo.jpg', 'rb')}
data = {'caption': 'Automated post! π'}
response = requests.post(f'{VM_URL}/api/instagram/post', files=files, data=data)
print(response.json())
# Post to Twitter (simple tweet)
data = {'text': 'Hello Twitter! π¦'}
response = requests.post(f'{VM_URL}/api/twitter/post', data=data)
print(response.json())
# Post to Twitter (with image)
files = {'image': open('photo.jpg', 'rb')}
data = {'text': 'Check this out! πΌοΈ'}
response = requests.post(f'{VM_URL}/api/twitter/post', files=files, data=data)
print(response.json())
# Post to Twitter (long tweet - auto-threaded)
data = {'text': 'This is a very long tweet that will automatically be split into a thread...'}
response = requests.post(f'{VM_URL}/api/twitter/post', data=data)
print(response.json())
# Post to Twitter (manual thread split)
data = {'text': 'First tweet.---Second tweet.---Third tweet.'}
response = requests.post(f'{VM_URL}/api/twitter/post', data=data)
print(response.json())const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const VM_URL = 'http://your-vm-ip:3000';
// Post to Instagram
const form = new FormData();
form.append('image', fs.createReadStream('photo.jpg'));
form.append('caption', 'Automated post! π');
const instagramResponse = await axios.post(`${VM_URL}/api/instagram/post`, form, {
headers: form.getHeaders()
});
console.log(instagramResponse.data);
// Post to Twitter (simple tweet)
const twitterResponse = await axios.post(`${VM_URL}/api/twitter/post`, {
text: 'Hello Twitter! π¦'
}, {
headers: { 'Content-Type': 'application/json' }
});
console.log(twitterResponse.data);
// Post to Twitter (with image)
const twitterForm = new FormData();
twitterForm.append('text', 'Check this out! πΌοΈ');
twitterForm.append('image', fs.createReadStream('photo.jpg'));
const twitterWithImage = await axios.post(`${VM_URL}/api/twitter/post`, twitterForm, {
headers: twitterForm.getHeaders()
});
console.log(twitterWithImage.data);
// Post to Twitter (long tweet - auto-threaded)
const longTweetResponse = await axios.post(`${VM_URL}/api/twitter/post`, {
text: 'This is a very long tweet that will automatically be split into a thread...'
}, {
headers: { 'Content-Type': 'application/json' }
});
console.log(longTweetResponse.data);Instagram:
curl -X POST http://your-vm-ip:3000/api/instagram/post \
-F "[email protected]" \
-F "caption=Automated post! π"Twitter (simple tweet):
curl -X POST http://your-vm-ip:3000/api/twitter/post \
-F "text=Hello Twitter! π¦"Twitter (with image):
curl -X POST http://your-vm-ip:3000/api/twitter/post \
-F "text=Check this out! πΌοΈ" \
-F "[email protected]"Twitter (long tweet - auto-threaded):
curl -X POST http://your-vm-ip:3000/api/twitter/post \
-F "text=This is a very long tweet that will automatically be split into a thread..."Twitter (manual thread split):
curl -X POST http://your-vm-ip:3000/api/twitter/post \
-F "text=First tweet.---Second tweet.---Third tweet."{
"success": true,
"message": "Posted to Instagram successfully",
"data": {
"caption": "Automated post! π",
"imageSize": 1234567
}
}{
"success": true,
"message": "Posted to Twitter successfully",
"data": {
"text": "Hello Twitter! π¦",
"hasImage": false
}
}{
"success": true,
"message": "Tweet posted successfully!",
"data": {
"text": "Long tweet that was auto-threaded...",
"hasImage": false
}
}{
"success": false,
"error": "Browser configuration not set"
}- No authentication by default - Add auth middleware if exposing publicly
- CORS enabled - Restrict origins in production
- No credential storage - Uses browser sessions only
- Firewall rules - Use SSH tunneling or VPN for secure access
- VM Setup: Use a dedicated VM for each social media account
- RDP Security: Use strong passwords and consider SSH tunneling
- Rate Limiting: Don't post too frequently (wait 1-2 minutes between posts)
- File Handling: Clean up uploaded files (handled automatically)
- Browser Sessions: Keep browser logged in with "Remember me"
# Check if xrdp is running
sudo systemctl status xrdp
# Check firewall
sudo ufw status# Run the config script
./config-browser.sh
# Or manually configure
curl -X POST http://localhost:3000/api/browser/config -H "Content-Type: application/json" -d '{"executablePath":"/usr/bin/brave-browser","userDataDir":"/home/YOUR_USER/.config/BraveSoftware/Brave-Browser"}'# Install virtual display support
./fix-browser-display.sh
# Or manually install Xvfb
sudo apt-get install -y xvfb
xvfb-run --auto-servernum npm start- Connect via RDP
- Open Brave browser
- Log into Instagram/Twitter again with "Remember me" checked
- Keep browser open or close it (session persists)
# Change port in the server or kill existing process
lsof -ti:3000 | xargs kill -9To update the server with latest code:
chmod +x update-server.sh
./update-server.shThis script will:
- Stop the running server
- Pull latest code (if using git)
- Install dependencies
- Rebuild the project
- Restart the server
If you get "Failed to launch browser" errors:
chmod +x fix-browser-display.sh
./fix-browser-display.shThis installs Xvfb (virtual display) so the browser can run without GUI/RDP.
# Development with auto-reload
npm run dev:watch
# Build for production
npm run build
# Run production
npm start- Built with Express, TypeScript, and Puppeteer
- Tested on Ubuntu 20.04/22.04 LTS and Windows 10/11
- Works with Brave, Chrome, or Chromium
- Twitter auto-threading: Automatically splits tweets >280 chars
- Manual thread control: Use
---to specify exact break points - Can be extended for other social media platforms
Need help? Check the setup script logs or open an issue.