Add platform poster header text settings
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user