Add telegram-archiver microservice (MVP)

This commit is contained in:
2026-02-19 21:41:52 +03:00
parent 7166f535d6
commit 6332b91ba4
13 changed files with 1635 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
"""
Pydantic models for Telegram Archiver.
Used for data validation and serialization.
"""
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional, Literal
from enum import Enum
class MediaType(str, Enum):
"""Types of media files supported by the archiver."""
PHOTO = "photo"
VIDEO = "video"
DOCUMENT = "document"
AUDIO = "audio"
VOICE = "voice"
VIDEO_NOTE = "video_note"
STICKER = "sticker"
ANIMATION = "animation" # GIF
class MediaFile(BaseModel):
"""Represents a media file attached to a Telegram message."""
filename: str = Field(..., description="Original filename")
type: MediaType = Field(..., description="Type of media")
caption: Optional[str] = Field(None, description="Caption text for the media")
size: Optional[int] = Field(None, description="File size in bytes")
is_too_large: bool = Field(
default=False,
description="True if file exceeds MAX_FILE_SIZE limit"
)
download_url: Optional[str] = Field(
None,
description="Telegram URL for files that weren't downloaded"
)
model_config = {"use_enum_values": True}
class PostData(BaseModel):
"""
Represents a single Telegram post with all metadata.
This is the main data structure passed to the markdown generator.
"""
message_id: int = Field(..., description="Telegram message ID")
date: datetime = Field(..., description="Message timestamp")
text: str = Field(default="", description="Message text in Markdown format")
author: str = Field(..., description="Channel name or author")
channel_username: str = Field(..., description="Channel username (for output path)")
# Relationships
reply_to: Optional[int] = Field(
None,
description="Message ID this post is replying to"
)
repost_from: Optional[int] = Field(
None,
description="Original message ID if this is a forwarded post"
)
repost_channel: Optional[str] = Field(
None,
description="Original channel name if this is a forwarded post"
)
# Media attachments
media_files: list[MediaFile] = Field(
default_factory=list,
description="List of media files attached to the post"
)
# Metadata
views: Optional[int] = Field(None, description="View count (if available)")
has_large_files: bool = Field(
default=False,
description="True if some files were not downloaded due to size limit"
)
@property
def bundle_dir(self) -> str:
"""Return the directory name for this post's bundle."""
return str(self.message_id)
@property
def index_path(self) -> str:
"""Return relative path to index.md within bundle."""
return f"{self.message_id}/index.md"
def has_media(self) -> bool:
"""Check if post has any media files."""
return len(self.media_files) > 0
def has_downloaded_media(self) -> bool:
"""Check if post has any downloaded (not too large) media files."""
return any(not f.is_too_large for f in self.media_files)
class ArchiveRequest(BaseModel):
"""Request model for FastAPI endpoint."""
channel: str = Field(..., description="Telegram channel username or ID")
output_dir: Optional[str] = Field(
None,
description="Override default output directory"
)
limit: Optional[int] = Field(
None,
description="Limit number of posts to archive (for testing)"
)
from_message_id: Optional[int] = Field(
None,
description="Start archiving from this message ID"
)
force: bool = Field(
default=False,
description="Force re-download of already archived posts"
)
class ArchiveResponse(BaseModel):
"""Response model for FastAPI endpoint."""
channel: str
channel_title: str
posts_archived: int
posts_skipped: int
media_downloaded: int
media_skipped_large: int
output_path: str
log_file: str
large_files_report: Optional[str] = None
duration_seconds: float
class HealthCheck(BaseModel):
"""Health check response."""
status: str
version: str
session_active: bool