feat: AI Press Secretary - content generation and publishing platform
Features: - AI-powered post generation from brand profiles - Image generation with templates - Content planning - Telegram publishing - Social profile analyzer - Community organization support - Markdown to HTML conversion for Telegram - Inline AI chat for post editing Deploy: - Add deployment scripts for Linux servers - Add PowerShell deploy script for Windows - Add Nginx and SSL setup scripts - Add backup and restore scripts
This commit is contained in:
1 parent
81625b2e6f
commit
c6048aeea0
37 files changed
+7037
-225
No files matched your search
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Анализатор профилей соцсетей.
|
||||
Определяет платформу по URL, извлекает посты, отправляет на AI-анализ
|
||||
для заполнения полей медиа-образа.
|
||||
"""
|
||||
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
from config import get_proxies
|
||||
|
||||
|
||||
# === Определение платформы ===
|
||||
|
||||
def detect_platform(url: str) -> dict:
|
||||
"""Определяет платформу по URL.
|
||||
Возвращает {"platform": str, "channel": str, "fetchable": bool}
|
||||
"""
|
||||
url = url.strip()
|
||||
|
||||
# Telegram: t.me/channel_name или t.me/s/channel_name
|
||||
m = re.match(r'https?://t\.me/(?:s/)?([a-zA-Z0-9_]+)', url)
|
||||
if m:
|
||||
return {"platform": "telegram", "channel": m.group(1), "fetchable": True}
|
||||
|
||||
# VK: vk.com/username or vk.ru/username
|
||||
m = re.match(r'https?://(?:vk\.com|vk\.ru)/([a-zA-Z0-9_.]+)', url)
|
||||
if m:
|
||||
return {"platform": "vk", "channel": m.group(1), "fetchable": False}
|
||||
|
||||
# Tenchat: tenchat.ru/username
|
||||
m = re.match(r'https?://tenchat\.ru/([a-zA-Z0-9_]+)', url)
|
||||
if m:
|
||||
return {"platform": "tenchat", "channel": m.group(1), "fetchable": False}
|
||||
|
||||
# Instagram
|
||||
m = re.match(r'https?://(?:www\.)?instagram\.com/([a-zA-Z0-9_.]+)/?', url)
|
||||
if m:
|
||||
return {"platform": "instagram", "channel": m.group(1), "fetchable": False}
|
||||
|
||||
return {"platform": "unknown", "channel": url, "fetchable": False}
|
||||
|
||||
|
||||
# === Парсинг Telegram ===
|
||||
|
||||
def fetch_telegram_posts(channel_name: str, limit: int = 20) -> list:
|
||||
"""Загружает посты из публичного Telegram-канала через t.me/s/.
|
||||
Возвращает список {"text": str, "date": str}.
|
||||
"""
|
||||
url = f"https://t.me/s/{channel_name}"
|
||||
proxies = get_proxies()
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
}
|
||||
try:
|
||||
r = requests.get(url, proxies=proxies, timeout=15, headers=headers)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
print(f"[SocialAnalyzer] Ошибка загрузки Telegram: {e}")
|
||||
return []
|
||||
|
||||
html = r.text
|
||||
|
||||
# Извлекаем тексты постов
|
||||
texts = re.findall(
|
||||
r'class="tgme_widget_message_text[^"]*"[^>]*>(.*?)</div>',
|
||||
html, re.DOTALL
|
||||
)
|
||||
|
||||
# Извлекаем даты
|
||||
dates = re.findall(
|
||||
r'class="tgme_widget_message_date"[^>]*>.*?<time[^>]*datetime="([^"]*)"',
|
||||
html, re.DOTALL
|
||||
)
|
||||
|
||||
posts = []
|
||||
for i, raw_text in enumerate(texts[:limit]):
|
||||
clean = re.sub(r'<[^>]+>', ' ', raw_text).strip()
|
||||
clean = re.sub(r'\s+', ' ', clean)
|
||||
if len(clean) > 10: # Пропускаем служебные сообщения
|
||||
date = dates[i] if i < len(dates) else ""
|
||||
posts.append({"text": clean, "date": date})
|
||||
|
||||
print(f"[SocialAnalyzer] Telegram: загружено {len(posts)} постов из @{channel_name}")
|
||||
return posts
|
||||
|
||||
|
||||
# === AI-анализ постов ===
|
||||
|
||||
ANALYSIS_PROMPT_TEMPLATE = """
|
||||
Ты — эксперт по брендингу и SMM. Проанализируй {count} постов из профиля/канала и извлеки параметры для медиа-образа.
|
||||
|
||||
=== ПОСТЫ ДЛЯ АНАЛИЗА ===
|
||||
{posts_text}
|
||||
|
||||
=== ЗАДАЧА ===
|
||||
Проанализируй стиль, тон, темы, подачу и структуру постов. Верни JSON со следующими полями:
|
||||
|
||||
{{
|
||||
"client_name": "Имя или название канала/профиля (если можно определить)",
|
||||
"brand_essence": "Сущность бренда одной строкой (что этот человек/бренд транслирует)",
|
||||
"tov_description": "Описание Tone of Voice (тон общения, манера подачи)",
|
||||
"tov_axes": "Оси ToV (по строкам, 3-5 шт)",
|
||||
"tov_references": "На кого/что похож стиль (референсы)",
|
||||
"prompt_directions": "Контент-направления (по строкам, какие темы затрагиваются)",
|
||||
"prompt_structure": "Типичная структура постов (как строются тексты)",
|
||||
"prompt_formats": "Типичные форматы постов (экспертный, личный, вовлекающий и т.д.)",
|
||||
"current_situation": "Краткое описание: кто этот человек/бренд, чем занимается",
|
||||
"client_role": "Роль / профессия (если определяется)",
|
||||
"positioning_who": "Для кого контент (целевая аудитория)",
|
||||
"positioning_problem": "Какую проблему решает",
|
||||
"positioning_result": "Какой результат получает аудитория"
|
||||
}}
|
||||
|
||||
Верни ТОЛЬКО валидный JSON без markdown и пояснений.
|
||||
"""
|
||||
|
||||
|
||||
def analyze_posts_with_ai(posts: list, provider_settings: dict = None) -> dict:
|
||||
"""Отправляет посты на AI-анализ и возвращает структурированные данные
|
||||
для заполнения медиа-образа.
|
||||
"""
|
||||
if not posts:
|
||||
return {"error": "Нет постов для анализа"}
|
||||
|
||||
# Склеиваем посты
|
||||
posts_text = ""
|
||||
for i, p in enumerate(posts[:20], 1):
|
||||
date_str = f" ({p['date']})" if p.get("date") else ""
|
||||
posts_text += f"\n--- Пост {i}{date_str} ---\n{p['text']}\n"
|
||||
|
||||
prompt = ANALYSIS_PROMPT_TEMPLATE.format(
|
||||
count=len(posts[:20]),
|
||||
posts_text=posts_text
|
||||
)
|
||||
|
||||
# Определяем AI-провайдер
|
||||
if provider_settings and provider_settings.get("api_key"):
|
||||
api_key = provider_settings["api_key"]
|
||||
base_url = provider_settings.get("base_url", "https://api.piapi.ai/v1")
|
||||
model = provider_settings.get("text_model", "gpt-4o")
|
||||
else:
|
||||
from config import PIAPI_API_KEY, PIAPI_MODEL
|
||||
api_key = PIAPI_API_KEY
|
||||
base_url = "https://api.piapi.ai/v1"
|
||||
model = PIAPI_MODEL
|
||||
|
||||
if not api_key:
|
||||
return {"error": "AI-провайдер не настроен. Настройте ИИ в разделе «ИИ»."}
|
||||
|
||||
chat_url = base_url.rstrip("/") + "/chat/completions"
|
||||
proxies = get_proxies()
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
chat_url,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {api_key}"
|
||||
},
|
||||
json={
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": "Ты эксперт по брендингу. Отвечаешь только валидным JSON."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"temperature": 0.3,
|
||||
"max_tokens": 2000,
|
||||
"response_format": {"type": "json_object"}
|
||||
},
|
||||
proxies=proxies,
|
||||
timeout=90
|
||||
)
|
||||
response.raise_for_status()
|
||||
content = response.json()["choices"][0]["message"]["content"].strip()
|
||||
|
||||
# Парсим JSON
|
||||
result = json.loads(content)
|
||||
print(f"[SocialAnalyzer] AI-анализ завершён, получено {len(result)} полей")
|
||||
return result
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[SocialAnalyzer] Ошибка парсинга JSON от AI: {e}")
|
||||
return {"error": "AI вернул некорректный JSON. Попробуйте ещё раз."}
|
||||
except Exception as e:
|
||||
print(f"[SocialAnalyzer] Ошибка AI: {e}")
|
||||
return {"error": f"Ошибка AI: {e}"}
|
||||
Reference in new issue
Block a user