Automatically post your Q2A content to multiple social media platforms. Supports questions, blog posts, exams, jobs, and scheduled daily posts.
- Multi-platform support: Telegram, Facebook, X (Twitter), LinkedIn, WhatsApp, Instagram, YouTube Shorts
- Multiple accounts per platform: Add as many Telegram channels or Facebook pages as you need
- Content type routing: Control which accounts receive which types of posts
- Category-specific routing: Send questions from specific categories to dedicated channels
- Auto-generated images: Creates images for Instagram and YouTube Shorts from post content
- AI-powered messages: Uses OpenAI to generate engaging social media posts
- Daily automated posts: Question of the Day and Quote of the Day features
- Token expiry warnings: Get email alerts before your API tokens expire
- Developer API: Other plugins can use the posting functionality
- Question2Answer 1.8.0+
- PHP 7.4+
- cURL extension
- GD extension (for image generation)
- Node.js 14+ (for server-side LaTeX/math rendering in QOTD images)
- wkhtmltoimage 0.12.6+ with patched Qt (for HTML-to-image conversion)
- ffmpeg (optional, for YouTube Shorts video generation)
- Download and extract to your
qa-pluginfolder - The folder should be named
social-media-poster - Create the image upload directory and set permissions:
mkdir -p /path/to/qa/qa-uploads/smp-images chown www-data:www-data /path/to/qa/qa-uploads /path/to/qa/qa-uploads/smp-images
- If your Q2A installation uses a separate document root (e.g., symlinked
qa-plugin,qa-include, etc.), ensureqa-uploadsis accessible from the public document root. For example:ln -s /path/to/qa/qa-uploads /path/to/public-docroot/qa-uploads
- Go to Admin → Plugins and you should see "Social Media Poster"
- Configure your accounts in the plugin settings
The QOTD image generator uses KaTeX to render LaTeX math expressions. Install it via npm from the plugin directory:
cd /path/to/qa-plugin/social-media-poster
npm install katexThis creates a node_modules/katex/ folder used by katex-render.js for server-side math rendering. The node_modules/ directory is in .gitignore, so you must run this after every fresh clone or deployment.
Verify it works:
echo 'Test $\frac{1}{2}$' | node katex-render.jsYou should see HTML output with <span class="katex"> tags and a summary like 1 katex span found.
Install the patched Qt version of wkhtmltoimage (the standard version doesn't support local file access for CSS/fonts):
# Ubuntu/Debian
sudo apt-get install wkhtmltopdfOr download the patched binary from wkhtmltopdf.org/downloads.html.
Verify:
wkhtmltoimage --version
# Should show 0.12.6 (with patched qt)- OpenAI API Key: Enter your OpenAI API key for AI-generated messages. This key is shared with other Q2A plugins that use the
qa-openai-api-keyoption. - System Prompt: Customize how OpenAI generates your social media posts.
Add accounts for each platform you want to post to:
| Platform | Required Credentials |
|---|---|
| Telegram | Bot Token, Chat ID |
| Page Access Token, Page ID | |
| X (Twitter) | API Key, API Secret, Access Token, Access Token Secret |
| Access Token, Author URN | |
| Access Token, Phone Number ID, Recipient Phone | |
| Access Token, Account ID | |
| YouTube | Client ID, Client Secret, Refresh Token |
You can add multiple accounts per platform. Mark one as "Default" for each platform.
Meta platform tokens (Facebook, Instagram, WhatsApp) can be auto-refreshed before they expire:
- Go to the Token Management section in plugin settings
- Enter your Meta App ID and Meta App Secret (from developers.facebook.com)
- Save the settings
The plugin will automatically exchange tokens for long-lived versions (~60 days) when they are within 7 days of expiry. You can also click "Refresh Tokens Now" to trigger a manual refresh at any time.
Note: YouTube tokens use a refresh_token that doesn't expire (handled automatically). Telegram and X tokens never expire.
For each content type (Question, Exam, Blog, Job, QOTD, Quote), select which accounts should receive posts. You can send to multiple accounts simultaneously.
If you have many categories and want certain categories to post to specific channels:
- Click "Show category settings"
- For each category, select additional accounts
- Check "Also post to default Question accounts" to post to both category-specific AND default accounts
- Question of the Day: Posts a random MCQ question from your database daily
- Quote of the Day: Posts an AI-generated motivational quote daily
Configure the hour when each should post (server time). The admin panel shows both the server time and your browser's local time for easy reference.
By default, daily posts are triggered on the first page load after the configured hour. For exact-time posting, set up a cron job:
- Go to Admin → Plugins → Social Media Poster → Daily Poster Settings
- Set a Cron Secret Key (any random string, e.g.
my-secret-key-123) - Save settings — the full cron URL will be displayed
- Add a cron entry that runs every hour:
Using curl (HTTP):
# Run every hour at minute 0
0 * * * * curl -s "https://yoursite.com/qa-plugin/social-media-poster/cron.php?key=YOUR_SECRET_KEY" > /dev/null 2>&1Using PHP CLI (no web server needed):
0 * * * * php /path/to/qa-plugin/social-media-poster/cron.php --key=YOUR_SECRET_KEY > /dev/null 2>&1The script runs every hour but only posts when the configured hour is reached and the post hasn't been made today. This ensures posts happen within the first minute of the configured hour.
Without cron, the plugin falls back to page-load triggering — posts happen on the first visitor page load after the configured hour.
The plugin auto-detects custom Q2A tables:
^exams- Shows Exam content type^blogs- Shows Blog content type^jobs- Shows Job content type
If these tables don't exist, the corresponding content types won't appear in settings.
Other Q2A plugins can use the Social Media Poster functionality. Include the API file:
require_once QA_PLUGIN_DIR . 'social-media-poster/SmpApi.php';// Post to all accounts configured for questions
$results = smp_post('question', 'Check out this question!', 'https://yoursite.com/q/123');
// Post to specific platforms
$results = smp_post_to_platforms(['telegram', 'facebook'], 'Hello from my plugin!');
// Post to a specific account
$result = smp_post_to_account('telegram_abc12345', 'Direct to this channel');// Generate an image for Instagram
$imageUrl = smp_generate_image('Your text content here', 'Optional Title');
// Generate a social media message using OpenAI
$message = smp_generate_message('Your long article content...');// Check if plugin is configured
if (smp_is_configured()) {
// Get all accounts
$accounts = smp_get_accounts();
// Get accounts for a content type
$questionAccounts = smp_get_accounts_for_content('question');
// With category routing
$physicsAccounts = smp_get_accounts_for_content('question', $categoryId);
}// Platform constants
SMP_PLATFORM_TELEGRAM
SMP_PLATFORM_FACEBOOK
SMP_PLATFORM_X
SMP_PLATFORM_LINKEDIN
SMP_PLATFORM_WHATSAPP
SMP_PLATFORM_INSTAGRAM
SMP_PLATFORM_YOUTUBE
// Content type constants
SMP_CONTENT_QUESTION
SMP_CONTENT_EXAM
SMP_CONTENT_BLOG
SMP_CONTENT_JOB
SMP_CONTENT_QOTD
SMP_CONTENT_QUOTEAll posting functions return an array with results:
[
'account_id' => [
'success' => true,
'platform' => 'telegram',
'account_name' => 'Physics Channel',
'response' => [...] // Platform-specific response
],
'another_account' => [
'success' => false,
'error' => 'Rate limit exceeded',
'platform' => 'facebook',
'account_name' => 'Main Page'
]
]The plugin listens for these Q2A events:
q_post- New question postedqa_exam_post_- New exam createdqas_blog_b_post- New blog postqa_job_post- New job listing
Posts not appearing?
- Check that the account is enabled (checkbox in account settings)
- Verify credentials are correct
- Check the content type is mapped to your account
- Look for errors in PHP error log
Token expiry warnings?
- The plugin probes API tokens daily and sends email warnings at 7 and 2 days before expiry
- Meta tokens (Facebook/Instagram/WhatsApp) are auto-refreshed when near expiry if App ID/Secret are configured
- Use the "Refresh Tokens Now" button in admin to manually trigger a refresh
- For LinkedIn, you need to manually re-authenticate when tokens expire
Images not generating?
- Ensure GD extension is installed and enabled
- Check that
qa-uploads/smp-images/exists and is writable by the web server (www-data) - If using a separate document root with symlinks, ensure
qa-uploadsis symlinked into the public document root
LaTeX/math not rendering in QOTD images?
- Ensure Node.js is installed and accessible to the web server user:
sudo -u www-data node --version - Ensure KaTeX is installed:
ls node_modules/katex/dist/katex.min.css(from the plugin directory) - If missing, run
npm install katexfrom the plugin directory - Test rendering:
echo 'Test $\frac{1}{2}$' | node katex-render.js - Ensure wkhtmltoimage is installed:
sudo -u www-data wkhtmltoimage --version - After updating PHP files, reload Apache to clear opcache:
sudo service apache2 reload
Instagram "media couldn't be fetched" error?
- The image URL must be publicly accessible to Facebook's servers
- If using Cloudflare, add a WAF rule to allow the
facebookexternalhituser-agent:Action: Allow or Skip security checks(http.user_agent contains "facebookexternalhit") or (http.user_agent contains "Facebot") - Verify the image URL returns HTTP 200 with
Content-Type: image/png
YouTube videos not uploading?
- Install ffmpeg on your server
- Ensure the refresh token has YouTube upload scope
MIT License
For issues and feature requests, please open an issue on GitHub.