Skip to content

Commit 76f21e1

Browse files
author
mithmith
committed
Merge remote-tracking branch 'origin/dev'
2 parents 10266b6 + 7fb8ed7 commit 76f21e1

17 files changed

Lines changed: 314 additions & 112 deletions

.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
STORAGE_PATH = "~/youtube_storage"
2+
VIDEO_DOWNLOAD_PATH = "videos"
3+
SHORTS_DOWNLOAD_PATH = "shorts"
4+
THUMBNAIL_DOWNLOAD_PATH = "thumbnails"
5+
16
YOUTUBE_API_KEY = "youtube_api_key"
2-
YOUTUBE_SECRET_JSON = "client_secret_google.json_file_name"
7+
YOUTUBE_SECRET_JSON = "client_secret_apps.googleusercontent.com.json"
8+
YOUTUBE_SERVICE_SECRET_JSON = "service-account-secret.json"
39

410
TG_BOT_TOKEN = "tg_bot_token"
511
TG_GROUP_ID = "tg_group_id"

TODO.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [ ] Выборка статистической информацией по каналам и видео, включая просмотры, лайки, количество подписчиков, количество видео, новые видео и другие метрики.
1313

1414
- [ ] **Скачивание контента**:
15+
- [ ] Скачивание shorts видео
1516
- [ ] Скачивание видео, аудио, субтитров и миниатюр.
1617

1718
- [ ] **Редактирование видео**:
@@ -21,22 +22,32 @@
2122
- [ ] **Локализация контента**:
2223
- [ ] Перевод описаний, названий, аудио дорожек и субтитров.
2324
- [ ] Перевод аудио дорожки с помощью внешнего сервиса.
24-
- [ ] Определение языка аудио дорожки (присылает youtube API).
25+
- [x] Определение языка аудио дорожки (присылает youtube API).
2526

2627
- [ ] **Создание субтитров и краткого пересказа**:
2728
- [ ] Автоматическое создание субтитров в случае их отсутствия с помощью внешнего сервиса.
2829
- [ ] Генерация краткого пересказа содержимого видео по субтитрам через внешний сервис.
2930

3031
- [ ] **Интеграция с телеграмм**:
3132
- [x] Формирование сообщения с новым видео на канале.
32-
- [ ] Создание сообщений с самыми популярными видео.
33-
- [ ] Отправка сообщений с топ популярных каналов за неделю и т.п.
33+
- [x] Публикация сообщений с shorts видео с каналов
34+
- [ ] Создание сообщений с ТОП самыми популярными видео.
35+
- [ ] Создание сообщений с ТОП популярных каналов за неделю и т.п.
3436

3537
- [ ] **Интеграция с сервером Peertube**:
3638
- [ ] Зеркальное размещение видео на децентрализованном хостинге.
3739

38-
## Frontend:
39-
- [ ] **Сделать фронт-дашбоард для визуализации данных**
40+
## Frontend + back API для нескольких подборок каналов:
41+
- [ ] Сделать фронт-дашбоард для визуализации данных
42+
- [ ] Сделать API для вывода на фронт-дашбоард для визуализации данных с нескольких подборок каналов
43+
- [ ] Вывод состояния проекта: Online, Offline
44+
- [ ] Вывод статуса БД: Online, Offline
45+
- [ ] Вывод наличия yt-dlp и его версии
46+
- [ ] Вывод статуса подключения по Youtube API
47+
- [ ] Вывод запущенных сервисов: monitor_new, monitor_history, tg_bot, и т.д.
48+
- [ ] Мониторинг логов: вывод последних 10 ошибок
49+
- [ ] Возможность просмотра или скачивания логов
50+
- [ ] Статистика публикации сообщений в телеграмм
4051

4152
## Docker-контейнер:
42-
- [ ] **Собрать контейнер с postgres, peertube и всем необходимым для работы**
53+
- [x] Собрать контейнер с postgres, peertube и всем необходимым для работы

app/__main__.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
import logging
33
from multiprocessing import Queue
44

5-
from loguru import logger
6-
7-
from app.config import settings
5+
from app.config import logger, settings
86
from app.service.telegram import TelegramBotService
97
from app.service.yt_monitor import YTMonitorService
108

@@ -43,23 +41,28 @@ def load_channels_list(file_path: str = "channels_list.json") -> list[str]:
4341

4442
if __name__ == "__main__":
4543
# Общая очередь для передачи данных между сервисами
46-
queue = Queue()
44+
news_queue = Queue()
45+
if settings.run_tg_bot_shorts_publish:
46+
shorts_queue = Queue()
47+
else:
48+
shorts_queue = None
4749
# Загружаем список каналов
4850
channels_list = load_channels_list()
4951

5052
# Инициализируем мониторинг YouTube
5153
monitor = YTMonitorService(
52-
channels_list=channels_list, new_videos_queue=queue, shorts_publish=settings.run_tg_bot_shorts_publish
54+
channels_list=channels_list, new_videos_queue=news_queue, shorts_videos_queue=shorts_queue
5355
)
5456

5557
# Запускаем процессы
58+
# logger.debug(f"Current Settings: {settings.model_dump()}")
5659
monitor_processes = monitor.run(settings.monitor_new, settings.monitor_history, settings.monitor_video_formats)
57-
logger.debug(f"TG bot started: {settings.run_tg_bot}")
5860
if settings.run_tg_bot:
5961
tg_bot = TelegramBotService(
6062
bot_token=settings.tg_bot_token,
6163
group_id=settings.tg_group_id,
62-
queue=queue,
64+
msg_queue=news_queue,
65+
shorts_queue=shorts_queue,
6366
)
6467
bot_process = tg_bot.run()
6568
bot_process.join()

app/config.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
import os
2+
import sys
13
from functools import lru_cache
24

3-
from pydantic_settings import BaseSettings
5+
from loguru import logger as log
6+
from pydantic_settings import BaseSettings, SettingsConfigDict
47

58

69
class Settings(BaseSettings):
710
app_host: str = "localhost"
811
app_port: int = 9191
912

1013
storage_path: str = "/mnt/volume"
11-
video_download_path: str = "/videos"
12-
shorts_download_path: str = "/shorts"
13-
thumbnail_download_path: str = "/videos/thumbnail"
14+
video_download_path: str = "videos"
15+
shorts_download_path: str = "shorts"
16+
thumbnail_download_path: str = "videos/thumbnail"
1417

1518
db_host: str = "localhost"
1619
db_port: int = 5432
@@ -27,6 +30,7 @@ class Settings(BaseSettings):
2730

2831
youtube_api_key: str = "youtube_key"
2932
youtube_secret_json: str = ""
33+
youtube_service_secret_json: str = ""
3034

3135
tg_bot_token: str = "TELEGRAM_BOT_TOKEN"
3236
tg_group_id: str = "group_id"
@@ -37,9 +41,15 @@ class Settings(BaseSettings):
3741
ssh_user: str = "root"
3842
ssh_private_key: str = "/root/.ssh/id_rsa"
3943

40-
class Config:
41-
env_file = ".env"
42-
env_file_encoding = "utf-8"
44+
log_lvl: str = "DEBUG"
45+
log_dir: str = "logs"
46+
log_to_file: bool = True
47+
48+
model_config = SettingsConfigDict(
49+
env_file=".env",
50+
env_file_encoding="utf-8",
51+
case_sensitive=False,
52+
)
4353

4454
@property
4555
def database_url(self) -> str:
@@ -48,9 +58,35 @@ def database_url(self) -> str:
4858
)
4959

5060

61+
@lru_cache()
62+
def get_logger(log_lvl: str, log_dir: str, log_to_file: bool):
63+
log_format_console = (
64+
"<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> "
65+
"| <level>{level:<8}</level> "
66+
"| <cyan>{file.name}:{line}</cyan> - <level>{message}</level>"
67+
)
68+
log_format_file = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level:<8} | {file.name}:{line} - {message}"
69+
70+
log.remove()
71+
log.add(sys.stderr, level=log_lvl, format=log_format_console, colorize=True, enqueue=True)
72+
if log_to_file:
73+
os.makedirs(log_dir, exist_ok=True)
74+
log.add( # Example log file name: logs/log_2024-02-13.log
75+
f"{log_dir}/log_{{time:YYYY-MM-DD}}.log",
76+
level="DEBUG",
77+
format=log_format_file,
78+
rotation="1 day",
79+
retention="30 days",
80+
compression="zip",
81+
enqueue=True,
82+
)
83+
return log
84+
85+
5186
@lru_cache()
5287
def get_settings() -> Settings:
5388
return Settings()
5489

5590

5691
settings = get_settings()
92+
logger = get_logger(settings.log_lvl, settings.log_dir, settings.log_to_file)

app/db/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from typing import Generic, Type, TypeVar
22
from uuid import UUID
33

4-
from loguru import logger
54
from sqlalchemy import MetaData, create_engine, orm
65
from sqlalchemy.exc import SQLAlchemyError
76
from sqlmodel import SQLModel, select
87

9-
from app.config import settings
8+
from app.config import logger, settings
109

1110
engine = create_engine(settings.database_url, echo=False, pool_size=10, max_overflow=20)
1211
Session = orm.sessionmaker(engine, expire_on_commit=False)

app/db/data_table.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ class Video(Base, table=True):
9292
upload_date: Optional[datetime] = Field(default=None)
9393
defaultaudiolanguage: Optional[str] = Field(default=None)
9494
last_update: datetime = Field(default_factory=lambda: datetime.now().replace(microsecond=0))
95-
tg_post_date: Optional[datetime] = Field(default=None)
95+
# path: Optional[str] = Field(default=None)
96+
# tg_post_date: Optional[datetime] = Field(default=None)
9697

9798
channel: Channel = Relationship(back_populates="videos")
9899
thumbnails: List["Thumbnail"] = Relationship(back_populates="video")

app/db/repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from typing import Optional, Union
44
from uuid import UUID, uuid4
55

6-
from loguru import logger
76
from sqlalchemy import or_
87
from sqlalchemy.exc import IntegrityError, NoResultFound, SQLAlchemyError
98

9+
from app.config import logger
1010
from app.db.base import BaseRepository
1111
from app.db.data_table import Channel, ChannelHistory, Tag, Thumbnail, Video, VideoHistory, VideoTag, YTFormat
1212
from app.schema import ChannelAPIInfoSchema, ChannelInfoSchema, ThumbnailSchema, VideoSchema, YTFormatSchema

app/integrations/telegram/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from app.config import settings
77
from app.integrations.telegram.commands import start, start_command
8-
from app.integrations.telegram.messages import handle_message, send_test_message
8+
from app.integrations.telegram.messages import handle_message, send_test_message, send_test_shorts_video
99

1010
# set higher logging level for httpx to avoid all GET and POST requests being logged
1111
logging.getLogger("httpx").setLevel(logging.INFO)
@@ -17,7 +17,7 @@ def get_telegram_handlers(test_mode: bool = False) -> list[BaseHandler]:
1717
if test_mode:
1818
return [
1919
CommandHandler("start", start),
20-
MessageHandler(filters.TEXT & ~filters.COMMAND, send_test_message),
20+
MessageHandler(filters.TEXT & ~filters.COMMAND, send_test_shorts_video),
2121
]
2222
return [
2323
CommandHandler("start", start_command),

app/integrations/telegram/messages.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
from loguru import logger
1+
import os
2+
23
from telegram import Message, Update
34
from telegram.ext import ContextTypes
5+
from telegram.helpers import escape_markdown
46

5-
from app.config import settings
7+
from app.config import logger, settings
68
from app.integrations.telegram.utils import extract_original_user_id, format_telegram_message
79

10+
MAX_TELEGRAM_VIDEO_SIZE = 50 * 1024 * 1024 # 50 MB
11+
812

913
async def send_test_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
1014
"""Отправить тестовое сообщение в группу Telegram."""
@@ -24,6 +28,37 @@ async def send_test_message(update: Update, context: ContextTypes.DEFAULT_TYPE)
2428
await update.message.reply_text("Сообщение отправлено в группу.")
2529

2630

31+
async def send_test_shorts_video(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
32+
video_path = "../test_shorts_video_w6CxKyVLtmo.mp4"
33+
channel_name = "Это базис"
34+
channel_url = "https://www.youtube.com/@eto_basis"
35+
video_title = "Почему власть боится бумажных стаканчиков и как это связано с Богородицей?"
36+
video_url = "https://www.youtube.com/shorts/w6CxKyVLtmo"
37+
38+
# Проверяем размер видео
39+
if os.path.getsize(video_path) > MAX_TELEGRAM_VIDEO_SIZE:
40+
logger.error(
41+
f"Видео {video_url} слишком большое для Telegram! ({os.path.getsize(video_path) / (1024 * 1024):.2f} MB)"
42+
)
43+
return
44+
45+
await context.bot.send_video(
46+
chat_id=settings.tg_admin_id,
47+
video=open(video_path, "rb"),
48+
caption=format_shorts_message(channel_name, channel_url, video_title, video_url),
49+
parse_mode="Markdown",
50+
)
51+
52+
53+
def format_shorts_message(channel_name: str, channel_url: str, video_title: str, video_url: str):
54+
"""Форматирование сообщения в Markdown формате."""
55+
return (
56+
f"🎥 [{escape_markdown(video_title)}]({video_url})\n"
57+
f'📺 На канале "[{escape_markdown(channel_name)}]({channel_url})" '
58+
f"🎬🔥 *Новое видео!*\n" + escape_markdown(f'\n#Shorts #YouTube #{channel_name.replace(" ", "_")}')
59+
)
60+
61+
2762
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
2863
"""Обработка входящих сообщений от пользователей."""
2964
admin_users_ids = [settings.tg_admin_id]

app/integrations/telegram/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from loguru import logger
3+
from app.config import logger
44

55

66
def format_telegram_message(channel_name, channel_url, video_title, video_url):

0 commit comments

Comments
 (0)