mirror of
https://gitverse.ru/kpa39l/chronicle.nixg.ru.git
synced 2026-09-29 09:55:08 +00:00
Add telegram-archiver microservice (MVP)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user