Post from Telegram to WordPress Bot
Publishing quick updates, micro-blog posts, or photo logs to a WordPress website from a mobile device often involves navigating a heavy web admin dashboard. To eliminate mobile blogging friction, I built post-from-telegram-to-wordpress-an automated Python server tool that receives messages from a Telegram channel or chat and publishes them directly to WordPress via the official REST API.
This article details the system architecture, media pipeline, WordPress REST API authentication, and deployment strategies for building a reliable Telegram-to-WordPress publishing bridge.
Post from Telegram to WordPress
- Source Repository: serhii-hrekov/post-from-telegram-to-wordpress
- Tech Stack: Python 3.11+,
python-telegram-bot/requests, WordPress REST API, Docker
Architectural Workflow and System Design
The application operates as an event-driven daemon running on a cloud server or container. When an authorized user posts text, photos, or formatted content into a dedicated Telegram channel or chat, the bridge captures the event, transforms the payload, handles media uploads, and invokes WordPress endpoints.
[ Telegram Channel / Chat ]
│
│ (Telegram Bot API / Event Listener)
▼
[ Python Listener Service ]
│
├── 1. Extract Text & Parse HTML/Markdown
├── 2. Download Telegram Media Attachments
├── 3. Upload Media to WordPress (/wp/v2/media)
│
▼ (WordPress Application Password Auth)
[ WordPress REST API (/wp/v2/posts) ]
│
▼
[ Live WordPress Blog Post ]
Core System Components
1. WordPress Authentication Strategy
Rather than storing administrative session cookies or global passwords, I configured authentication using WordPress Application Passwords. Introduced natively in WordPress 5.6+, Application Passwords generate unique, revokable tokens specifically designed for REST API integrations.
Header formulation:
import base64
def get_wp_auth_header(username: str, app_password: str) -> dict:
credentials = f"{username}:{app_password}"
token = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
return {"Authorization": f"Basic {token}"}
2. Telegram Event Listener
The bot registers handlers to process incoming text and photo updates. To ensure only authorized users or channel administrators can publish to the blog, incoming message events are filtered against designated Telegram chat_id allowlists.
from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, MessageHandler, filters
ALLOWED_CHAT_IDS = {-100123456789} # Target Telegram Channel / Admin ID
async def handle_incoming_post(update: Update, context: ContextTypes.DEFAULT_TYPE):
message = update.effective_message
if message.chat.id not in ALLOWED_CHAT_IDS:
return
text_content = message.text or message.caption or ""
photo_attachments = message.photo
# Process and push to WordPress
await process_and_publish(text_content, photo_attachments, context.bot)
Media Upload Pipeline (Handling Telegram Photos)
Publishing posts with attached images requires a multi-step media pipeline:
- Telegram File Retrieval: Fetch the highest-resolution photo object from Telegram servers.
- WordPress Media Library Upload: Send a
POSTrequest with image bytes to/wp/v2/mediawith properContent-Typeheaders. - Featured Image Association: Pass the generated WordPress media ID (
featured_media) in the main post creation payload.
import requests
def upload_photo_to_wordpress(wp_site_url: str, auth_headers: dict, photo_bytes: bytes, filename: str) -> int:
media_url = f"{wp_site_url}/wp-json/wp/v2/media"
headers = {
**auth_headers,
"Content-Disposition": f'attachment; filename="{filename}"',
"Content-Type": "image/jpeg",
}
response = requests.post(media_url, headers=headers, data=photo_bytes)
response.raise_for_status()
# Return created media ID
return response.json()["id"]
Post Creation and HTML Publishing
Once media processing completes, the service constructs the WordPress post payload. Telegram HTML formatting tags (<b>, <i>, <a>, <code>) map directly into WordPress content blocks.
def create_wordpress_post(wp_site_url: str, auth_headers: dict, title: str, html_content: str, media_id: int | None = None) -> dict:
posts_url = f"{wp_site_url}/wp-json/wp/v2/posts"
payload = {
"title": title,
"content": html_content,
"status": "publish", # 'draft' or 'publish'
}
if media_id:
payload["featured_media"] = media_id
response = requests.post(posts_url, headers=auth_headers, json=payload)
response.raise_for_status()
return response.json()
Deployment with Docker
To ensure uninterrupted 24/7 background execution, I package the bridge as a lightweight container managed via Docker or docker-compose.
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Environment Configuration (.env)
TELEGRAM_BOT_TOKEN=123456789:XXXXXXXXXX
ALLOWED_CHAT_ID=-100123456789
WP_SITE_URL=https://myblog.com
WP_USER=admin
WP_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
Production Recommendations
- Webhook vs Long-Polling: For high-frequency Telegram channels, switch from long-polling (
run_polling()) to Webhook mode behind an Nginx reverse proxy. - Post Status Controls: Set
"status": "draft"in configuration if you prefer reviewing posts inside the WordPress editor prior to public indexing. - Security Boundaries: Scope WordPress Application Password capabilities and restrict IP access if running on static cloud infrastructure.