Refactor session handling and configuration management
This commit is contained in:
@@ -10,28 +10,7 @@
|
||||
|
||||
## Get session
|
||||
|
||||
1)```python3 -m venv .venv && source .venv/bin/activate```
|
||||
2)```pip install pyrogram```
|
||||
3)Run ```TG_API_ID=290389758 TG_API_HASH=c22987sdfnkjjhd37efa5f0 python3 session_generator.py```
|
||||
4)Enter phone number, get code in telegram, enter code, and copy session string:
|
||||
|
||||
```text
|
||||
Enter phone number or bot token: +7 993 850 5104
|
||||
Is "+7 993 850 5104" correct? (y/N): y
|
||||
The confirmation code has been sent via Telegram app
|
||||
Enter confirmation code: 69267
|
||||
The two-step verification is enabled and a password is required
|
||||
Password hint: None
|
||||
Enter password (empty to recover): Passport-Vegan-Scale6
|
||||
|
||||
Your session string:
|
||||
========================================
|
||||
AgG7QBoANg0YVmwZTZmqadO4MJQdnaRRnXwSYpbbkGf49aATvTZj-yKcvdH8IsIDbwp00PbWFcbSjzoPsmiUK8BF80yVmML7iEQptBZrRTLsnoxmeglD-I1dqcAB2ufkxQDM_40y5KAHiFvzAzXhVngQo8W7u3ZPQXb_DTcfogXXePPBQAyV20cuwGrsArv-R39ssSWnFueGnB21Y_cTXTQAAAAFPEaL8AA
|
||||
========================================
|
||||
Use session on ENV variable TG_SESSION_STRING in docker-compose.yml
|
||||
```
|
||||
|
||||
6)Set session ENV variable in docker-compose.yml file:
|
||||
1) Сreate docker-compose.yml (or, recommended, create stack in portainer) with full configuration and mount the same data directory:
|
||||
|
||||
```docker-compose
|
||||
volumes:
|
||||
@@ -44,7 +23,6 @@ services:
|
||||
environment:
|
||||
TG_API_ID: 290389758
|
||||
TG_API_HASH: c22987sdfnkjjhd37efa5f0
|
||||
TG_SESSION_STRING: "AgG7QBoANg0YVmwZTZmqadO4MJQdn............FPEaL8AA"
|
||||
PYROGRAM_BRIDGE_URL: https://pgbridge.example.com
|
||||
API_PORT: 80
|
||||
TOKEN: "1234567890"
|
||||
@@ -59,6 +37,51 @@ services:
|
||||
traefik.http.routers.pgbridge.tls: true
|
||||
```
|
||||
|
||||
2) Run docker-compose up -d or start stack in portainer
|
||||
|
||||
3) Enter in container:
|
||||
|
||||
```bash
|
||||
docker exec -it pyrogram-bridge /bin/bash
|
||||
```
|
||||
|
||||
4) Run bridge in interactive mode:
|
||||
|
||||
```bash
|
||||
python3 api_server.py
|
||||
```
|
||||
|
||||
5) Enter phone number, get code in telegram, enter code, and copy session string. The session file will be saved in your local ./data directory.
|
||||
|
||||
```text
|
||||
Enter phone number or bot token: +7 993 850 5104
|
||||
Is "+7 993 850 5104" correct? (y/N): y
|
||||
The confirmation code has been sent via Telegram app
|
||||
Enter confirmation code: 69267
|
||||
The two-step verification is enabled and a password is required
|
||||
Password hint: None
|
||||
Enter password (empty to recover): Passport-Vegan-Scale6
|
||||
```
|
||||
|
||||
Session file will be saved in your data directory, in docker compose case — /var/lib/docker/volumes/pyrogram_bridge/_data/pyro_bridge.session.
|
||||
|
||||
6) Restart bridge container:
|
||||
|
||||
```bash
|
||||
docker restart pyrogram-bridge
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PYROGRAM_BRIDGE_URL - url to rss bridge, used for generate absolute url to media
|
||||
TOKEN - optional, if set, will be used to check if user has access to rss feed. If token is set, rss url will be https://pgbridge.example.com/rss/DragorWW_space/1234567890
|
||||
Use this if you rss bridge access all world, otherwise your bridge can be used by many people and telegram will inevitably be sanctioned for botting.
|
||||
@@ -91,6 +114,7 @@ This creates an unnecessary load, so we do something else: we request limit*2 an
|
||||
Exclusion flags are a way to filter channel content based on pre-defined (by me) criteria. It's not a universal regexp-based filtering engine, for example, but it does 99% of my tasks of filtering the content of some toxic tg channels (mostly with fresh memes).
|
||||
|
||||
There are several flags:
|
||||
|
||||
- video - presence of video and small text in the post
|
||||
- stream - words like "стрим", "livestream"
|
||||
- donat - word "донат" and its variations
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import os
|
||||
|
||||
def get_settings():
|
||||
tg_api_id = os.getenv("TG_API_ID")
|
||||
tg_api_hash = os.getenv("TG_API_HASH")
|
||||
if not tg_api_id or not tg_api_hash:
|
||||
print("TG_API_ID and TG_API_HASH must be set")
|
||||
os._exit(1)
|
||||
|
||||
return {
|
||||
"tg_api_id": int(os.getenv("TG_API_ID")),
|
||||
"tg_api_hash": os.getenv("TG_API_HASH"),
|
||||
"session_path": os.getenv("SESSION_PATH", "session.file") or "session.file",
|
||||
"tg_api_id": int(tg_api_id),
|
||||
"tg_api_hash": tg_api_hash,
|
||||
"session_path": os.getenv("SESSION_PATH", "data") or "data",
|
||||
"api_host": os.getenv("API_HOST", "0.0.0.0"),
|
||||
"api_port": int(os.getenv("API_PORT") or 8000),
|
||||
"session_string": os.getenv("TG_SESSION_STRING", ""),
|
||||
"pyrogram_bridge_url": os.getenv("PYROGRAM_BRIDGE_URL", ""),
|
||||
"log_level": os.getenv("LOG_LEVEL", "INFO"),
|
||||
"debug": os.getenv("DEBUG", "False") == "True",
|
||||
|
||||
+18
-25
@@ -16,7 +16,7 @@ if not logger.handlers:
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
async def create_time_based_media_groups(messages, merge_seconds: int = 5):
|
||||
async def _create_time_based_media_groups(messages, merge_seconds: int = 5):
|
||||
messages_sorted = sorted(messages, key=lambda x: x.date)
|
||||
cluster = []
|
||||
last_msg_date = None
|
||||
@@ -59,7 +59,7 @@ async def create_time_based_media_groups(messages, merge_seconds: int = 5):
|
||||
|
||||
return messages
|
||||
|
||||
async def create_messages_groups(messages):
|
||||
async def _create_messages_groups(messages):
|
||||
"""
|
||||
Process messages into formatted posts, handling media groups
|
||||
"""
|
||||
@@ -70,16 +70,14 @@ async def create_messages_groups(messages):
|
||||
for message in messages:
|
||||
try:
|
||||
# Skip service messages about pinned posts
|
||||
if message.service and 'PINNED_MESSAGE' in str(message.service):
|
||||
continue
|
||||
if message.service and 'PINNED_MESSAGE' in str(message.service): continue
|
||||
|
||||
if message.media_group_id:
|
||||
if message.media_group_id not in media_groups:
|
||||
media_groups[message.media_group_id] = []
|
||||
media_groups[message.media_group_id].append(message)
|
||||
else:
|
||||
# Single message becomes its own processing group
|
||||
processing_groups.append([message])
|
||||
processing_groups.append([message]) # Single message becomes its own processing group
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"message_processing_error: channel {message.chat.username}, message_id {message.id}, error {str(e)}")
|
||||
@@ -91,25 +89,20 @@ async def create_messages_groups(messages):
|
||||
processing_groups.append(media_group)
|
||||
|
||||
# Sort processing groups by date of first message in each group
|
||||
processing_groups.sort(
|
||||
key=lambda group: group[0].date,
|
||||
reverse=True
|
||||
)
|
||||
processing_groups.sort( key=lambda group: group[0].date, reverse=True )
|
||||
|
||||
return processing_groups
|
||||
|
||||
async def trim_messages_groups(messages_groups, limit):
|
||||
# Remove the oldest group (the one with the lowest message id based on the first message's date)
|
||||
if messages_groups:
|
||||
_removed_group = messages_groups.pop()
|
||||
async def _trim_messages_groups(messages_groups, limit):
|
||||
if messages_groups: # Remove the oldest group (the one with the lowest message id based on the first message's date)
|
||||
messages_groups.pop()
|
||||
|
||||
# Trim groups if they exceed the specified limit
|
||||
if len(messages_groups) > limit:
|
||||
if len(messages_groups) > limit: # Trim groups if they exceed the specified limit
|
||||
messages_groups = messages_groups[:limit]
|
||||
|
||||
return messages_groups
|
||||
|
||||
async def render_messages_groups(messages_groups, post_parser, exclude_flags: str | None = None):
|
||||
async def _render_messages_groups(messages_groups, post_parser, exclude_flags: str | None = None):
|
||||
"""
|
||||
Render message groups into HTML format
|
||||
Args:
|
||||
@@ -256,10 +249,10 @@ async def generate_channel_rss(channel: str,
|
||||
|
||||
# Process messages into groups and render them
|
||||
if Config['time_based_merge']:
|
||||
messages = await create_time_based_media_groups(messages, merge_seconds)
|
||||
message_groups = await create_messages_groups(messages)
|
||||
message_groups = await trim_messages_groups(message_groups, limit)
|
||||
final_posts = await render_messages_groups(message_groups, post_parser, exclude_flags)
|
||||
messages = await _create_time_based_media_groups(messages, merge_seconds)
|
||||
message_groups = await _create_messages_groups(messages)
|
||||
message_groups = await _trim_messages_groups(message_groups, limit)
|
||||
final_posts = await _render_messages_groups(message_groups, post_parser, exclude_flags)
|
||||
|
||||
# Generate feed entries
|
||||
for post in final_posts:
|
||||
@@ -337,10 +330,10 @@ async def generate_channel_html(channel: str,
|
||||
|
||||
# Process messages into groups and render them
|
||||
if Config['time_based_merge']:
|
||||
messages = await create_time_based_media_groups(messages, merge_seconds)
|
||||
message_groups = await create_messages_groups(messages)
|
||||
message_groups = await trim_messages_groups(message_groups, limit)
|
||||
final_posts = await render_messages_groups(message_groups, post_parser, exclude_flags)
|
||||
messages = await _create_time_based_media_groups(messages, merge_seconds)
|
||||
message_groups = await _create_messages_groups(messages)
|
||||
message_groups = await _trim_messages_groups(message_groups, limit)
|
||||
final_posts = await _render_messages_groups(message_groups, post_parser, exclude_flags)
|
||||
|
||||
# Generate HTML content
|
||||
html_posts = [post['html'] for post in final_posts]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import os
|
||||
import asyncio
|
||||
from pyrogram import Client
|
||||
|
||||
async def generate_session():
|
||||
api_id = os.getenv("TG_API_ID")
|
||||
api_hash = os.getenv("TG_API_HASH")
|
||||
|
||||
if not api_id or not api_hash:
|
||||
print("Error: Set TG_API_ID and TG_API_HASH environment variables")
|
||||
return
|
||||
|
||||
async with Client(
|
||||
name=":memory:",
|
||||
api_id=int(api_id),
|
||||
api_hash=api_hash,
|
||||
in_memory=True
|
||||
) as client:
|
||||
session = await client.export_session_string()
|
||||
print("\nYour session string:")
|
||||
print("=" * 40)
|
||||
print(session)
|
||||
print("=" * 40)
|
||||
print("\nUser session on ENV variable TG_SESSION_STRING in docker-compose.yml")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(generate_session())
|
||||
+23
-19
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import os
|
||||
from pyrogram import Client
|
||||
from config import get_settings
|
||||
|
||||
@@ -7,27 +8,30 @@ settings = get_settings()
|
||||
|
||||
class TelegramClient:
|
||||
def __init__(self):
|
||||
if settings["session_string"]:
|
||||
self.client = Client(
|
||||
name="pyro_bridge",
|
||||
session_string=settings["session_string"],
|
||||
api_id=settings["tg_api_id"],
|
||||
api_hash=settings["tg_api_hash"],
|
||||
in_memory=True
|
||||
)
|
||||
else:
|
||||
self.client = Client(
|
||||
name="pyro_bridge",
|
||||
api_id=settings["tg_api_id"],
|
||||
api_hash=settings["tg_api_hash"],
|
||||
workdir=settings["session_path"],
|
||||
in_memory=True
|
||||
)
|
||||
self._ensure_session_directory()
|
||||
self.client = Client(
|
||||
name="pyro_bridge",
|
||||
api_id=settings["tg_api_id"],
|
||||
api_hash=settings["tg_api_hash"],
|
||||
workdir=settings["session_path"],
|
||||
)
|
||||
|
||||
def _ensure_session_directory(self):
|
||||
try:
|
||||
os.makedirs(settings["session_path"], exist_ok=True)
|
||||
logger.debug(f'Session directory created/verified: {settings["session_path"]}')
|
||||
except Exception as e:
|
||||
logger.error(f'Failed to create session directory {settings["session_path"]}: {str(e)}')
|
||||
raise
|
||||
|
||||
async def start(self):
|
||||
if not self.client.is_connected:
|
||||
await self.client.start()
|
||||
logger.info("Telegram client connected")
|
||||
try:
|
||||
if not self.client.is_connected:
|
||||
await self.client.start()
|
||||
logger.info("Telegram client connected successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start Telegram client: {str(e)}")
|
||||
raise
|
||||
|
||||
async def stop(self):
|
||||
if self.client.is_connected:
|
||||
|
||||
Reference in New Issue
Block a user