Add platform poster header text settings

This commit is contained in:
Your Name
2026-08-04 20:02:42 +05:00
parent a8db1b379e
commit 347d1dfe2f
9 changed files with 78 additions and 5 deletions
+2
View File
@@ -62,5 +62,7 @@ The site poster is selected by `site_poster_provider`. A project can disable the
- FN-8 Coolify app `n6cnr60anmruiwkiey0pukye` points to `new_vk_parser.git`, branch `main`.
- RAA Coolify app `korokrhpoyqxf0nw2y8vk7lp` points to `new_vk_parser.git`, branch `main`.
- Both projects should deploy from the same commit SHA.
- Both current Coolify apps run on Docker destination CTID `105`, hostname `raa-fn8`.
- Old CT `107` was removed on 2026-08-04; CT `104` is a separate legacy VK reposting app.
- Keep only env and database settings different between projects.
- Do not add project-specific defaults to migrations or `.env.example`.
+12 -2
View File
@@ -1,6 +1,6 @@
# RAA deployment
Last updated: 2026-08-03.
Last updated: 2026-08-04.
RAA is not a separate code project anymore. The source of truth for code and
documentation is:
@@ -23,7 +23,7 @@ continue feature work there.
- Coolify git branch: `main`
- Postgres container: `m9v14hzfh5mk9kelxzcqvhc6`
- Postgres DB/user: `raa_parser` / `raa_parser_user`
- Destination: `vk-parser-lxc`, CTID `105`, IP `192.168.1.105`
- Destination: `raa-fn8`, CTID `105`, IP `192.168.1.105`
- Edge proxy: Caddy in CTID `110` proxies `raa.panel.f-n8.ru` to Coolify Traefik at `192.168.1.105:80`
- Health check: `https://raa.panel.f-n8.ru/health`
@@ -55,6 +55,11 @@ Database settings:
- `vk_poster_access_token` is the RAA community token
- `site_poster_enabled=false`
- `site_poster_provider=""` until a website adapter is configured
- Optional social text headers/footers:
- `tg_poster_header_text` / `tg_poster_footer_text`
- `vk_poster_header_text` / `vk_poster_footer_text`
- `max_poster_header_text` / `max_poster_footer_text`
Empty header/footer settings add nothing.
## Workers
@@ -103,6 +108,11 @@ automatic rejection of non-target content.
## Deployment Notes
Legacy CT cleanup:
- CT `107` (`raa-parser-poster`) was stopped and removed on 2026-08-04.
- CT `104` (`raa-sender`) is not an error: it still runs the legacy VK reposting app (`vk-reposter`) and can legitimately post to its configured Telegram channel.
To deploy code changes for both FN-8 and RAA:
1. Work only in `D:\DEVELOPMENT\new_vk_parser`.
+9 -1
View File
@@ -13,7 +13,7 @@ Current runtime is local infrastructure, not the old VPS.
- Deploy path: push to `gitea/main` -> Gitea webhook -> Coolify -> Docker build/deploy.
- Coolify + Gitea LXC: `192.168.1.135` (CTID 102).
- Coolify app UUID: `n6cnr60anmruiwkiey0pukye` (`FN-8 admin`). API token is only in `D:\DEVELOPMENT\.infra.md`; do not copy it into repo docs.
- Application LXC: `192.168.1.105` (CTID 105, `new-vk-parser`).
- Application LXC: `192.168.1.105` (CTID 105, `raa-fn8`).
- Public admin URL: `https://panel.f-n8.ru` (also `https://admin.f-n8.ru` when DNS is current).
- Coolify repository URL must be reachable from the deployment container; use `http://192.168.1.135:3000/exostring/new_vk_parser.git` or another resolvable Gitea URL, not `http://coolify.lan:3000/...`.
- Do not deploy this project through `sw.exostring.xyz`, `109.120.156.205`, `/opt/vk-parser`, or manual `systemctl` unless the user explicitly says the old VPS is being restored.
@@ -60,6 +60,7 @@ canonical git repository as FN-8. Code work for both projects must happen in
- Coolify app UUID: `korokrhpoyqxf0nw2y8vk7lp` (`RAA admin`).
- Public admin URL: `https://raa.panel.f-n8.ru`.
- RAA Postgres container: `m9v14hzfh5mk9kelxzcqvhc6`, DB `raa_parser`, user `raa_parser_user`.
- Docker destination/LXC for both FN-8 and RAA: CTID `105`, hostname `raa-fn8`, IP `192.168.1.105`.
- Raw Telegram media channel setting: `tg_media_channel_id=-1004312015562` (`VK RAW RAA`).
- Final Telegram publication chat setting: `tg_poster_chat_id=-1001303630155` (`Red Airsoft | Страйкбол`).
- VK publication owner: `vk_poster_owner_id=-36860851`, `vk_poster_from_group=true`.
@@ -70,6 +71,9 @@ canonical git repository as FN-8. Code work for both projects must happen in
`ai-qualifier=false`, `ai-writer=false`, `tg-poster=false`, `vk-poster=false`,
`site-poster=false`, `tg_poster_enabled=false`, `vk_poster_enabled=false`.
- Detailed RAA notes live in `RAA.md`.
- CT `107` (`raa-parser-poster`) was removed on 2026-08-04. CT `104`
(`raa-sender`) remains a valid legacy VK reposting app and is not the shared
RAA parser/writer/poster app.
## Important Text Rules
@@ -84,6 +88,10 @@ canonical git repository as FN-8. Code work for both projects must happen in
- optional project-wide common tags first, then `#category #source`
- Project-wide common tags live in DB setting `publication_common_tags`.
FN-8 value: `#новости_снаряжения`. RAA value: `#raa_news #страйкбол`.
- Optional per-platform header/footer settings:
`tg_poster_header_text`, `tg_poster_footer_text`,
`vk_poster_header_text`, `vk_poster_footer_text`,
`max_poster_header_text`, `max_poster_footer_text`.
- The editor textarea must show only clean editable text.
- The list preview in `/editor` should show the publication preview, including composed hashtags.
- `build_publication_text()` in `src/vk_parser_app/text_utils.py` is display/composition helper only. Do not use it before writing `final_text` to DB.
@@ -0,0 +1,35 @@
INSERT INTO app_settings(key, value_json, value_type, title, description, category)
VALUES
(
'tg_poster_header_text',
'""'::jsonb,
'text',
'TG текст в начале поста',
'Текст, который добавляется перед постом только для Telegram. Если пусто, ничего не добавляется. Для ссылок используй HTML: <a href="https://example.com">Текст</a>.',
'TG Poster'
),
(
'vk_poster_header_text',
'""'::jsonb,
'text',
'VK текст в начале поста',
'Текст, который добавляется перед постом только для VK. Если пусто, ничего не добавляется. Можно использовать VK-разметку ссылок: [https://example.com|Текст].',
'VK Poster'
),
(
'max_poster_header_text',
'""'::jsonb,
'text',
'MAX текст в начале поста',
'Текст, который добавляется перед постом только для MAX. Если пусто, ничего не добавляется.',
'MAX Poster'
),
(
'max_poster_footer_text',
'""'::jsonb,
'text',
'MAX текст перед хэштегами',
'Текст, который добавляется после поста и перед хэштегами только для MAX.',
'MAX Poster'
)
ON CONFLICT (key) DO NOTHING;
+4
View File
@@ -271,6 +271,7 @@ SETTING_ORDER = {
"tg_poster_recent_window",
"tg_poster_category_repeat_penalty",
"tg_poster_source_repeat_penalty",
"tg_poster_header_text",
"tg_poster_footer_text",
],
"TG Reactor": [
@@ -302,6 +303,7 @@ SETTING_ORDER = {
"vk_poster_category_repeat_penalty",
"vk_poster_source_repeat_penalty",
"vk_poster_dry_run",
"vk_poster_header_text",
"vk_poster_footer_text",
],
"Publishing": [
@@ -327,6 +329,8 @@ SETTING_ORDER = {
"max_poster_auto_reaction",
"max_poster_reaction_path_template",
"max_poster_dry_run",
"max_poster_header_text",
"max_poster_footer_text",
],
"Site Poster": [
"site_poster_provider",
+4 -2
View File
@@ -56,6 +56,7 @@ def build_publication_text(
source_tag: str,
common_tags: object = "",
footer_text: str = "",
header_text: str = "",
format_title: bool = False,
parse_mode: str | None = None,
) -> str:
@@ -76,10 +77,11 @@ def build_publication_text(
title_idx = idx
break
header = str(header_text or "").strip()
footer = str(footer_text or "").strip()
hashtags = publication_hashtags(category_tag, source_tag, common_tags)
if title_idx is None:
return "\n\n".join(part for part in [footer, hashtags] if part)
return "\n\n".join(part for part in [header, footer, hashtags] if part)
formatted_lines = []
for idx, line in enumerate(cleaned_lines):
@@ -104,7 +106,7 @@ def build_publication_text(
raw_body = "\n".join(formatted_lines)
# Collapse multiple consecutive blank lines to at most two newlines (\n\n)
normalized_body = re.sub(r"\n{3,}", "\n\n", raw_body).strip()
parts = [part for part in [normalized_body, footer, hashtags] if part]
parts = [part for part in [header, normalized_body, footer, hashtags] if part]
return "\n\n".join(parts)
+6
View File
@@ -219,6 +219,8 @@ class MAXPoster:
self.reaction_path_template = "/messages/{message_id}/reactions"
self.dry_run = False
self.common_tags = ""
self.header_text = ""
self.footer_text = ""
async def init(self) -> None:
self.pool = await get_pool()
@@ -245,6 +247,8 @@ class MAXPoster:
self.reaction_path_template = str(await fetch_setting("max_poster_reaction_path_template", self.reaction_path_template) or "").strip()
self.dry_run = await fetch_bool_setting("max_poster_dry_run", False)
self.common_tags = str(await fetch_setting("publication_common_tags", "") or "").strip()
self.header_text = str(await fetch_setting("max_poster_header_text", "") or "").strip()
self.footer_text = str(await fetch_setting("max_poster_footer_text", "") or "").strip()
if not self.token:
raise RuntimeError("max_poster_bot_token is empty")
if not self.chat_id:
@@ -378,6 +382,8 @@ class MAXPoster:
category_tag,
source_tag,
self.common_tags,
self.footer_text,
self.header_text,
)
async def download_media_to_temp(self, session: aiohttp.ClientSession, item: dict[str, Any]) -> Path | None:
+3
View File
@@ -112,6 +112,7 @@ class TelegramPoster:
self.category_repeat_penalty = 3.0
self.source_repeat_penalty = 4.0
self.common_tags = ""
self.header_text = ""
self.footer_text = ""
async def init(self) -> None:
@@ -140,6 +141,7 @@ class TelegramPoster:
self.category_repeat_penalty = max(0.0, await fetch_float_setting("tg_poster_category_repeat_penalty", 3.0))
self.source_repeat_penalty = max(0.0, await fetch_float_setting("tg_poster_source_repeat_penalty", 4.0))
self.common_tags = str(await fetch_setting("publication_common_tags", "") or "").strip()
self.header_text = str(await fetch_setting("tg_poster_header_text", "") or "").strip()
self.footer_text = str(await fetch_setting("tg_poster_footer_text", "") or "").strip()
logger.info("TG poster config: chat={} schedule={} caption_limit={}", self.chat_id, self.schedule, self.caption_limit)
@@ -335,6 +337,7 @@ class TelegramPoster:
source_tag,
self.common_tags,
self.footer_text,
self.header_text,
format_title=True,
parse_mode="html",
)
+3
View File
@@ -73,6 +73,7 @@ class VKPoster:
self.source_repeat_penalty = 4.0
self.dry_run = False
self.common_tags = ""
self.header_text = ""
self.footer_text = ""
async def init(self) -> None:
@@ -161,6 +162,7 @@ class VKPoster:
self.source_repeat_penalty = max(0.0, await fetch_float_setting("vk_poster_source_repeat_penalty", 4.0))
self.dry_run = await fetch_bool_setting("vk_poster_dry_run", False)
self.common_tags = str(await fetch_setting("publication_common_tags", "") or "").strip()
self.header_text = str(await fetch_setting("vk_poster_header_text", "") or "").strip()
self.footer_text = str(await fetch_setting("vk_poster_footer_text", "") or "").strip()
if not self.token:
raise RuntimeError("vk_poster_access_token, VK_GROUP_ACCESS_TOKEN and VK_ACCESS_TOKEN are empty")
@@ -302,6 +304,7 @@ class VKPoster:
source_tag,
self.common_tags,
self.footer_text,
self.header_text,
)[:VK_MESSAGE_LIMIT]
async def media_attachment(self, client: VKAPIClient, item: dict[str, Any]) -> str | None: