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 "image=@photo.jpg" \ -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 "image=@photo.jpg"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 "image=@photo.jpg" \ -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 "image=@photo.jpg"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.