One-command autoposting pipeline for a Joomla 3.x blog powered by any OpenAI-compatible LLM (DeepSeek, OpenAI, OpenRouter, local Ollama…).
It picks the next topic, writes an SEO article through an LLM, validates the HTML deterministically, publishes it to Joomla via a real browser (Playwright) and submits the URL for re-crawling to Yandex.Webmaster — all in one run.
Русская версия: README.ru.md
Filling a blog manually is monotonous: think of a topic, write the text, format it in the admin panel, publish, then request re-indexing. This project turns it into a supervised pipeline:
topic → LLM article → HTML validation → Joomla (Playwright) → log → re-crawl
It was built for a real workflow of two articles per day on an old Joomla 3.10 site. The LLM is used only to write the article — CSV handling, file management, HTML checks, the browser and the API are plain code, which keeps token usage low and makes every step deterministic.
- One command —
python run.pyruns the whole cycle. - Topic management — reads
headlines.csv, advances statuses (не использован → в работе → черновик → опубликован). - LLM copywriter — strict prompt, returns only HTML (no markdown).
- Deterministic validation — catches service-junk markers (
[citation],【,†), markdown, HTML comments, wrong wrappers, missing<hr id="system-readmore" />, link allowlist and text length; one repair pass is attempted before failing. - Joomla publisher — Playwright: logs in, duplicates the latest article via Save as Copy (inheriting all hidden fields), fills Title / Alias / CodeMirror content, forces Published and saves.
- Re-crawl — submits the URL to Yandex.Webmaster through the official REST API (no browser login to a sensitive service).
- Resumable — a failed publication stays in
черновикand can be retried with--publish-draft; drafts and logs live on disk. - Safe by default — secrets and the browser profile are kept out of the
repository (see
.gitignore).
- One-command Python pipeline (
run.py) — the recommended, token-cheap production mode described below. - Three Cherry Studio assistants + MCP — the original architecture from
the case study. Ready-made system prompts are in
prompts/: Headlines, Copywriter and Posting, connected through Desktop Commander and Playwright MCP servers. Useful when you want a human check between stages.
- Python 3.10+
- A Joomla 3.x site with an admin panel
- An OpenAI-compatible LLM endpoint + API key
- (Optional) Yandex.Webmaster with a verified host + OAuth token
- Chromium for Playwright (
python -m playwright install chromium)
# 1. Clone and install
git clone https://github.com/YanChi-pixel/joomla-ai-autoposter.git
cd joomla-ai-autoposter
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
python -m playwright install chromium
# 2. Configure
copy config.example.json config.json # Windows
# cp config.example.json config.json # macOS / Linux
# …then edit config.json and fill in your values
# 3. Prepare the secrets folder
mkdir .secrets
# Put: llm-api-key.txt, joomla-credentials.txt (login\npassword), yandex-token.txt
# 4. Prepare the topic list
copy examples\headlines.example.csv headlines.csv
# 5. Test generation only (no publishing)
python run.py --test
# 6. Full cycle
python run.pyconfig.json is the only settings file. Relative paths are resolved from the
project root, so the repository can be cloned anywhere.
| Section | Key | Meaning |
|---|---|---|
paths |
headlines |
topic list CSV (default headlines.csv) |
paths |
current_task |
hand-off file (default current-task.txt) |
paths |
published_log |
publication log CSV |
paths |
drafts |
drafts directory |
paths |
recrawl_token |
Yandex token file (default .secrets/yandex-token.txt) |
llm |
base_url |
OpenAI-compatible endpoint |
llm |
model |
model name (default deepseek-chat) |
llm |
api_key_file |
API key file (default .secrets/llm-api-key.txt) |
llm |
temperature / max_tokens |
generation params |
joomla |
base_url |
admin URL (https://site.example.com/administrator/) |
joomla |
login_url |
admin login URL |
joomla |
credentials_file |
login/password file |
joomla |
profile_dir |
persistent Playwright profile directory |
joomla |
headless |
false for the first run, then true |
joomla |
public_blog_path |
public blog URL used to build article links |
recrawl |
host |
verified host in Yandex.Webmaster |
seo |
links |
internal links the LLM may use (keywords/title/href/anchor) |
python run.py # full cycle
python run.py --test # generate only, no publishing, no CSV changes
python run.py next # show the next free topic
python run.py --title "Topic" # generate an article for a custom topic
python run.py --publish-draft # publish an existing draftTopic statuses in headlines.csv:
не использован → в работе → черновик → опубликован
│
└── ошибка
Put the key into .secrets/llm-api-key.txt (one line) or set the
LLM_API_KEY / DEEPSEEK_API_KEY environment variable.
.secrets/joomla-credentials.txt — first line login, second line password.
- Create an OAuth application in Yandex and get a token (valid ~6 months).
- Save it to
.secrets/yandex-token.txt. - Make sure the site host is verified in Webmaster.
Manual re-crawl:
python recrawl.py --host your-site.example.com "https://your-site.example.com/blog/123-post.html"The content field in Joomla is a CodeMirror editor, not a plain textarea,
so fill() is unreliable. The publisher:
- opens the admin and logs in if needed;
- opens the latest article and clicks Save as Copy (this inherits hidden fields like the "Read More" text);
- fills
#jform_title,#jform_aliasand the CodeMirror content via the editor's own API (cm.CodeMirror.setValue(...)+save()); - forces
#jform_stateto1(Published); - clicks Save & Close and verifies the article list appeared.
First run: keep
"headless": falseand log in manually once if the auto-login does not work — the persistentprofile_dirremembers the session.
LLM API key not found— fill.secrets/llm-api-key.txtor set the env var.playwright is not installed—pip install playwrightandpython -m playwright install chromium.Could not log into Joomla— check the credentials, log in manually once.Failed to insert the full HTML— the editor is not CodeMirror or the pause is too short; adjust_set_contentinbrowser/publish.py.- HTTP 429 from Yandex — the daily re-crawl quota is exhausted; retry tomorrow.
- Console shows
������— runchcp 65001before the command. - Yandex token expired — refresh
.secrets/yandex-token.txt.
joomla-ai-autoposter/
├── run.py # one-command orchestrator
├── recrawl.py # Yandex.Webmaster re-crawl
├── article_prompt.txt # universal article prompt
├── browser/
│ └── publish.py # Playwright Joomla publisher
├── prompts/ # Cherry Studio system prompts (3-assistant flow)
├── examples/ # example CSV files
├── docs/ # architecture diagram + generator
├── config.example.json
├── .github/workflows/ci.yml
├── CHANGELOG.md
└── requirements.txt
- Run
--testbefore the first production run. - Never commit
.secrets/,playwright-profile/,drafts/,headlines.csvorpublished-log/— they are ignored via.gitignore. - Review the draft before publishing at scale.
This tool writes to a production Joomla site through browser automation and calls the Yandex.Webmaster API. Test on a staging copy first, respect rate limits, and review the LLM-generated content before publishing at scale.