Skip to content

Commit ac46ef1

Browse files
author
mithmith
committed
WIP: files path fixes + docker run script fixes
1 parent 157958f commit ac46ef1

8 files changed

Lines changed: 314 additions & 102 deletions

File tree

TODO.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
- [ ] Мониторинг логов: вывод последних 10 ошибок
5252
- [ ] Возможность просмотра или скачивания логов
5353
- [ ] Статистика публикации сообщений в телеграмм
54+
- [ ] Редактирование конфигов
55+
- [ ] Редактирование списка каналов
56+
- [ ] Редактирование шаблонов сообщений
5457

5558
## Docker-контейнер:
5659
- [x] Собрать контейнер со всем необходимым для работы

app/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class Settings(BaseSettings):
3939
tg_admin_id: int = 0
4040
tg_new_video_template: Path = "./templates/new_video.md"
4141
tg_shorts_template: Path = "./templates/shorts.md"
42+
tg_new_video_template_default: Path = "./templates/new_video.md"
43+
tg_shorts_template_default: Path = "./templates/shorts.md"
4244

4345
use_proxy: bool = False
4446
use_ssh_tunnel: bool = False
@@ -67,6 +69,8 @@ def __init__(self, **kwargs):
6769
super().__init__(**kwargs)
6870
self.tg_new_video_template = Path(self.tg_new_video_template).resolve()
6971
self.tg_shorts_template = Path(self.tg_shorts_template).resolve()
72+
self.tg_new_video_template_default = Path(self.tg_new_video_template_default).resolve()
73+
self.tg_shorts_template_default = Path(self.tg_shorts_template_default).resolve()
7074

7175

7276
@lru_cache()

app/service/telegram.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,13 @@ def _format_newvideo_message(self, channel_name: str, channel_url: str, video_ti
249249
all_hashtags = f"#Videos #{main_hashtag} {additional_hashtags}"
250250
else:
251251
all_hashtags = f"#Videos #{main_hashtag} #YouTube"
252+
if settings.tg_new_video_template.exists():
253+
new_video_template_path = settings.tg_new_video_template
254+
else:
255+
logger.warning("New video template not found. Using default template!")
256+
new_video_template_path = settings.tg_new_video_template_default
252257
return self.render_template(
253-
settings.tg_new_video_template,
258+
new_video_template_path,
254259
video_title=cleaned_title,
255260
video_url=video_url,
256261
channel_name=channel_name,
@@ -266,8 +271,13 @@ def _format_shorts_message(self, channel_name: str, channel_url: str, video_titl
266271
all_hashtags = f"#Shorts #{main_hashtag} {additional_hashtags}"
267272
else:
268273
all_hashtags = f"#Shorts #{main_hashtag}"
274+
if settings.tg_shorts_template.exists():
275+
shorts_template_path = settings.tg_shorts_template
276+
else:
277+
logger.warning("Shorts template not found. Using default template!")
278+
shorts_template_path = settings.tg_shorts_template_default
269279
return self.render_template(
270-
settings.tg_shorts_template,
280+
shorts_template_path,
271281
video_title=cleaned_title,
272282
video_url=video_url,
273283
channel_name=channel_name,

app/service/yt_monitor.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def __init__(
3333
self._shorts_publish_queue = shorts_videos_queue
3434
self._download_queue = Queue()
3535
self._shorts_publish = settings.run_tg_bot_shorts_publish
36+
self._short_download_path = Path(settings.storage_path).expanduser().resolve() / settings.shorts_download_path
37+
self._video_download_path = Path(settings.storage_path).expanduser().resolve() / settings.video_download_path
3638

3739
def run(
3840
self, monitor_new: bool = True, monitor_history: bool = True, monitor_video_formats: bool = True
@@ -226,7 +228,7 @@ async def _process_channel_videos(self, channel_url: str, process_new: bool = Fa
226228
video_title=video.title,
227229
video_url=video.url,
228230
video_id=video.id,
229-
video_file_download_path=new_shorts_path,
231+
video_file_download_path=str(new_shorts_path),
230232
)
231233
)
232234
if process_old and old_videos:
@@ -330,12 +332,10 @@ def _process_old_videos(self, old_videos: list[VideoSchema]) -> None:
330332
repository.update_video(video_schema)
331333
repository.add_video_history(video_schema)
332334

333-
def _generate_shorts_download_path(self, channel_name: str, video_id: str, format: str = "mp4") -> str:
335+
def _generate_shorts_download_path(self, channel_name: str, video_id: str, format: str = "mp4") -> Path:
334336
video_file_name = f"{channel_name}_{video_id}.{format}"
335-
short_download_path = Path(settings.storage_path).expanduser().resolve() / settings.shorts_download_path
336-
return str(short_download_path / video_file_name)
337+
return (self._short_download_path / video_file_name)
337338

338-
def _generate_videos_download_path(self, channel_name: str, video_id: str, format: str = "mp4") -> str:
339+
def _generate_videos_download_path(self, channel_name: str, video_id: str, format: str = "mp4") -> Path:
339340
video_file_name = f"{channel_name}_{video_id}.{format}"
340-
video_download_path = Path(settings.storage_path).expanduser().resolve() / settings.video_download_path
341-
return str(video_download_path / video_file_name)
341+
return (self._video_download_path / video_file_name)

migrations/merge.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import logging
2+
from sqlalchemy import create_engine, Table, MetaData
3+
from sqlalchemy.orm import sessionmaker
4+
from sqlalchemy.dialects.postgresql import insert
5+
from datetime import datetime
6+
from tqdm import tqdm
7+
8+
from app.config import settings
9+
10+
# Настройки логирования
11+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
12+
logger = logging.getLogger(__name__)
13+
14+
# Настройки соединения
15+
DB_CONFIGS = {
16+
"db1": {
17+
"host": settings.db_host,
18+
"port": settings.db_port,
19+
"database": settings.db_name,
20+
"user": settings.db_username,
21+
"password": settings.db_password,
22+
},
23+
"db2": {
24+
"host": "ip2",
25+
"port": "port2",
26+
"database": "db_name2",
27+
"user": "db_user2",
28+
"password": "db_pass2"
29+
}
30+
}
31+
32+
SCHEMA = settings.db_schema
33+
34+
# Функция создания движка БД
35+
def get_engine(config):
36+
"""Создает подключение к базе данных."""
37+
try:
38+
engine = create_engine(
39+
f"postgresql://{config['user']}:{config['password']}@{config['host']}:{config['port']}/{config['database']}"
40+
)
41+
logger.info(f"Успешное подключение к {config['database']} на {config['host']}")
42+
return engine
43+
except Exception as e:
44+
logger.error(f"Ошибка подключения к {config['database']} на {config['host']}: {e}")
45+
raise
46+
47+
# Подключение к БД
48+
engines = {db: get_engine(DB_CONFIGS[db]) for db in DB_CONFIGS}
49+
sessions = {db: sessionmaker(bind=engines[db])() for db in DB_CONFIGS}
50+
51+
# Метаданные и таблицы
52+
metadata = MetaData(schema=SCHEMA)
53+
try:
54+
metadata.reflect(bind=engines["db1"]) # Загружаем схему из первой БД
55+
logger.info("Метаданные успешно загружены.")
56+
except Exception as e:
57+
logger.error(f"Ошибка загрузки метаданных: {e}")
58+
raise
59+
60+
def merge_table(table_name, timestamp_column=None):
61+
"""Синхронизирует таблицы из второй базы в первую, обновляя по временным меткам."""
62+
try:
63+
logger.info(f"Объединение таблицы: {table_name}")
64+
table = Table(table_name, metadata, autoload_with=engines["db1"])
65+
66+
data_db1 = sessions["db1"].execute(table.select()).fetchall()
67+
data_db2 = sessions["db2"].execute(table.select()).fetchall()
68+
69+
merged_data = {}
70+
for row in data_db1 + data_db2:
71+
row_dict = dict(row)
72+
key = row_dict[table.primary_key.columns.keys()[0]]
73+
74+
if key in merged_data:
75+
if timestamp_column and row_dict[timestamp_column] > merged_data[key][timestamp_column]:
76+
merged_data[key] = row_dict
77+
else:
78+
merged_data[key] = row_dict
79+
80+
# Вставка данных с конфликтами
81+
with engines["db1"].begin() as conn:
82+
for row in tqdm(merged_data.values(), desc=f"Слияние {table_name}"):
83+
insert_stmt = insert(table).values(row)
84+
if timestamp_column:
85+
update_stmt = insert_stmt.on_conflict_do_update(
86+
index_elements=[table.primary_key.columns.keys()[0]],
87+
set_={timestamp_column: row[timestamp_column]}
88+
)
89+
else:
90+
update_stmt = insert_stmt.on_conflict_do_nothing()
91+
conn.execute(update_stmt)
92+
logger.info(f"Таблица {table_name} успешно объединена.")
93+
except Exception as e:
94+
logger.error(f"Ошибка при объединении таблицы {table_name}: {e}")
95+
raise
96+
97+
def merge_databases():
98+
"""Запускает процесс объединения всех таблиц баз данных."""
99+
try:
100+
logger.info("Начало объединения баз данных")
101+
tables_with_timestamps = [
102+
("channels", "last_update"),
103+
("channel_history", "recorded_at"),
104+
("videos", "last_update"),
105+
("video_history", "recorded_at"),
106+
]
107+
tables_without_timestamps = [
108+
"tags", "videotag", "thumbnails", "video_formats"
109+
]
110+
111+
for table, timestamp in tqdm(tables_with_timestamps, desc="Обновление с временными метками"):
112+
merge_table(table, timestamp)
113+
114+
for table in tqdm(tables_without_timestamps, desc="Обновление без временных меток"):
115+
merge_table(table)
116+
117+
logger.info("Объединение баз данных завершено.")
118+
except Exception as e:
119+
logger.error(f"Ошибка объединения баз данных: {e}")
120+
raise
121+
122+
if __name__ == "__main__":
123+
merge_databases()
124+

0 commit comments

Comments
 (0)