initial verstion
This commit is contained in:
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Use a specific version for stability
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Install system dependencies for image processing
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libheif-dev \
|
||||||
|
libde265-0 \
|
||||||
|
libjpeg62-turbo \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install python dependencies
|
||||||
|
# Using --no-cache-dir to keep the image small
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
aiogram \
|
||||||
|
pillow-heif \
|
||||||
|
Pillow \
|
||||||
|
prometheus_client \
|
||||||
|
aiohttp
|
||||||
|
|
||||||
|
# Copy your bot code
|
||||||
|
COPY bot.py .
|
||||||
|
|
||||||
|
# Standardize ports
|
||||||
|
EXPOSE 8000 8080
|
||||||
|
|
||||||
|
CMD ["python", "bot.py"]
|
||||||
115
bot.py
Normal file
115
bot.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import asyncio
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
|
import pillow_heif
|
||||||
|
from aiogram import BaseMiddleware, Bot, Dispatcher, F, types
|
||||||
|
from aiogram.types import BufferedInputFile, Message
|
||||||
|
from aiogram.utils.media_group import MediaGroupBuilder
|
||||||
|
from aiohttp import web
|
||||||
|
from PIL import Image
|
||||||
|
from prometheus_client import Counter, start_http_server
|
||||||
|
|
||||||
|
# --- CONFIG & METRICS ---
|
||||||
|
API_TOKEN = os.getenv("BOT_TOKEN")
|
||||||
|
METRICS_PORT = 8000
|
||||||
|
HEALTH_PORT = 8080
|
||||||
|
|
||||||
|
CONVERSION_COUNT = Counter("heic_conversions_total", "Total HEIC to JPEG conversions")
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
pillow_heif.register_heif_opener()
|
||||||
|
|
||||||
|
if not API_TOKEN:
|
||||||
|
raise ValueError("No BOT_TOKEN found in environment variables")
|
||||||
|
|
||||||
|
|
||||||
|
# --- MIDDLEWARE FOR ALBUMS ---
|
||||||
|
class AlbumMiddleware(BaseMiddleware):
|
||||||
|
"""Groups multiple messages in a media group into a single list."""
|
||||||
|
|
||||||
|
def __init__(self, latency: float = 0.6):
|
||||||
|
self.latency = latency
|
||||||
|
self.album_data: Dict[str, List[Message]] = {}
|
||||||
|
|
||||||
|
async def __call__(self, handler, event: Message, data: Dict[str, Any]):
|
||||||
|
if not event.media_group_id:
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
|
if event.media_group_id not in self.album_data:
|
||||||
|
self.album_data[event.media_group_id] = [event]
|
||||||
|
await asyncio.sleep(self.latency)
|
||||||
|
data["album"] = self.album_data.pop(event.media_group_id)
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
|
self.album_data[event.media_group_id].append(event)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
# --- HANDLERS ---
|
||||||
|
async def convert_image(bot: Bot, msg: Message) -> BufferedInputFile:
|
||||||
|
"""Helper to download and convert a single HEIC to JPEG."""
|
||||||
|
file_id = msg.document.file_id if msg.document else msg.photo[-1].file_id
|
||||||
|
file_name = msg.document.file_name if msg.document else f"{file_id}.heic"
|
||||||
|
|
||||||
|
file = await bot.get_file(file_id)
|
||||||
|
heic_buffer = await bot.download_file(file.file_path)
|
||||||
|
|
||||||
|
with Image.open(heic_buffer) as img:
|
||||||
|
jpeg_buffer = io.BytesIO()
|
||||||
|
img.convert("RGB").save(jpeg_buffer, format="JPEG", quality=95)
|
||||||
|
jpeg_buffer.seek(0)
|
||||||
|
|
||||||
|
new_name = file_name.rsplit(".", 1)[0] + ".jpg"
|
||||||
|
CONVERSION_COUNT.inc()
|
||||||
|
return BufferedInputFile(jpeg_buffer.read(), filename=new_name)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message(F.media_group_id)
|
||||||
|
async def handle_album(message: Message, album: List[Message], bot: Bot):
|
||||||
|
"""Handles multiple HEIC images sent as an album."""
|
||||||
|
media_group = MediaGroupBuilder(caption="Converted Images")
|
||||||
|
for msg in album:
|
||||||
|
if (
|
||||||
|
msg.document and msg.document.file_name.lower().endswith(".heic")
|
||||||
|
) or msg.photo:
|
||||||
|
conv_file = await convert_image(bot, msg)
|
||||||
|
media_group.add_document(media=conv_file)
|
||||||
|
|
||||||
|
await message.answer_media_group(media=media_group.build())
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message(F.document.file_name.lower().endswith(".heic"))
|
||||||
|
async def handle_single_doc(message: Message, bot: Bot):
|
||||||
|
"""Handles a single HEIC file."""
|
||||||
|
jpeg_file = await convert_image(bot, message)
|
||||||
|
await message.reply_document(document=jpeg_file)
|
||||||
|
|
||||||
|
|
||||||
|
# --- K8S HEALTH CHECK ---
|
||||||
|
async def health_check(request):
|
||||||
|
return web.Response(text="OK", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
# --- MAIN ---
|
||||||
|
async def main():
|
||||||
|
bot = Bot(token=API_TOKEN)
|
||||||
|
dp = Dispatcher()
|
||||||
|
dp.message.middleware(AlbumMiddleware())
|
||||||
|
|
||||||
|
# Start Prometheus
|
||||||
|
start_http_server(METRICS_PORT)
|
||||||
|
|
||||||
|
# Start Health Check Server
|
||||||
|
server = web.Application()
|
||||||
|
server.router.add_get("/healthz", health_check)
|
||||||
|
runner = web.AppRunner(server)
|
||||||
|
await runner.setup()
|
||||||
|
site = web.TCPSite(runner, "0.0.0.0", HEALTH_PORT)
|
||||||
|
|
||||||
|
await asyncio.gather(site.start(), dp.start_polling(bot))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
14
compose.yaml
Normal file
14
compose.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
services:
|
||||||
|
heic-bot:
|
||||||
|
image:
|
||||||
|
build: .
|
||||||
|
container_name: heic-converter
|
||||||
|
environment:
|
||||||
|
- BOT_TOKEN=${BOT_TOKEN}
|
||||||
|
ports:
|
||||||
|
- "8000:8000" # Prometheus Metrics
|
||||||
|
- "8080:8080" # Health Checks
|
||||||
|
volumes:
|
||||||
|
# Optional: mount a local folder if you want to log to file
|
||||||
|
- ./logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
51
k8s/base/bot.yaml
Normal file
51
k8s/base/bot.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: heic-converter-bot
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: heic-bot
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: heic-bot
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "8000"
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: bot
|
||||||
|
image: git.danilkolesnikov.ru/danilko09/heic-bot:latest
|
||||||
|
env:
|
||||||
|
- name: BOT_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: heic-bot-config
|
||||||
|
key: BOT_TOKEN
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
name: metrics
|
||||||
|
- containerPort: 8080
|
||||||
|
name: health
|
||||||
|
# Critical for image processing pods
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "200m"
|
||||||
|
limits:
|
||||||
|
memory: "1Gi"
|
||||||
|
cpu: "1000m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
2
k8s/base/kustomization.yaml
Normal file
2
k8s/base/kustomization.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
3
k8s/overlays/prod/kustomization.yaml
Normal file
3
k8s/overlays/prod/kustomization.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
namespace: iphone_to_jpeg_bot_prod
|
||||||
|
resources:
|
||||||
|
- ../../base
|
||||||
6
k8s/templates/bot_config.yaml
Normal file
6
k8s/templates/bot_config.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: heic-bot-config
|
||||||
|
data:
|
||||||
|
BOT_TOKEN: "YOUR_TELEGRAM_BOT_TOKEN_HERE"
|
||||||
19
skaffold.yaml
Normal file
19
skaffold.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: skaffold/v4beta13
|
||||||
|
kind: Config
|
||||||
|
metadata:
|
||||||
|
name: iphone-to-jpeg-bot
|
||||||
|
build:
|
||||||
|
artifacts:
|
||||||
|
- image: git.danilkolesnikov.ru/danilko09/heic-bot
|
||||||
|
docker:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
manifests:
|
||||||
|
kustomize:
|
||||||
|
paths:
|
||||||
|
- k8s/base
|
||||||
|
profiles:
|
||||||
|
- name: prod
|
||||||
|
manifests:
|
||||||
|
kustomize:
|
||||||
|
paths:
|
||||||
|
- k8s/overlays/prod
|
||||||
Reference in New Issue
Block a user