snapshot current state before gitea sync

This commit is contained in:
2026-02-18 10:50:25 +01:00
commit e367a38065
13 changed files with 243 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(/Users/felixfoertsch/Developer/TelegramStickersDownloaderHTTP/.venv/bin/pip list:*)",
"Bash(.venv/bin/pip install:*)",
"Bash(python3:*)",
"Bash(.venv/bin/python:*)"
]
}
}

10
StiggerDLBot/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.env
.venv/
cache/
downloads/
__pycache__/
*.pyc
.DS_Store
.idea/
.cache/
.vscode/

View File

View File

@@ -0,0 +1,14 @@
from pathlib import Path
from dotenv import load_dotenv
from pydantic_settings import BaseSettings
load_dotenv()
class Settings(BaseSettings):
bot_token: str
downloads_dir: Path = Path("downloads")
settings = Settings()

20
StiggerDLBot/app/main.py Normal file
View File

@@ -0,0 +1,20 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import stickersets
app = FastAPI(title="Telegram Stickers API", root_path="/StiggerDLBot")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET"],
allow_headers=["*"],
)
app.include_router(stickersets.router)
@app.get("/health")
def health():
return {"status": "ok"}

View File

@@ -0,0 +1,17 @@
from pydantic import BaseModel
class StickerResponse(BaseModel):
id: str
emoji: str
emoji_name: str
is_animated: bool
png_url: str
gif_url: str | None = None
class StickerSetResponse(BaseModel):
name: str
title: str
sticker_count: int
stickers: list[StickerResponse]

View File

View File

@@ -0,0 +1,31 @@
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from app.models import StickerSetResponse
from app.services import sticker_service
router = APIRouter(prefix="/api/stickersets")
@router.get("/{pack_name}", response_model=StickerSetResponse)
def get_sticker_set(pack_name: str):
result = sticker_service.get_sticker_set(pack_name)
if result is None:
raise HTTPException(status_code=404, detail="Sticker pack not found")
return result
@router.get("/{pack_name}/stickers/{sticker_id}.png")
def get_sticker_png(pack_name: str, sticker_id: str):
path = sticker_service.get_sticker_file(pack_name, sticker_id, "png")
if path is None:
raise HTTPException(status_code=404, detail="Sticker not found")
return FileResponse(path, media_type="image/png")
@router.get("/{pack_name}/stickers/{sticker_id}.gif")
def get_sticker_gif(pack_name: str, sticker_id: str):
path = sticker_service.get_sticker_file(pack_name, sticker_id, "gif")
if path is None:
raise HTTPException(status_code=404, detail="Sticker not found")
return FileResponse(path, media_type="image/gif")

View File

View File

@@ -0,0 +1,125 @@
from pathlib import Path
from tstickers.convert import Backend
from tstickers.manager import StickerManager
from app.config import settings
from app.models import StickerResponse, StickerSetResponse
_manager: StickerManager | None = None
def _get_manager() -> StickerManager:
global _manager
if _manager is None:
_manager = StickerManager(settings.bot_token)
return _manager
def _pack_is_cached(pack_name: str) -> bool:
png_dir = settings.downloads_dir / pack_name / "png"
gif_dir = settings.downloads_dir / pack_name / "gif"
has_png = png_dir.exists() and any(png_dir.iterdir())
has_gif = gif_dir.exists() and any(gif_dir.iterdir())
return has_png or has_gif
def get_sticker_set(pack_name: str) -> StickerSetResponse | None:
"""Fetch a sticker pack, download and convert if not cached."""
manager = _get_manager()
# Get pack metadata (uses requests-cache internally)
pack = manager.getPack(pack_name)
if pack is None:
return None
actual_name = pack["name"]
if not _pack_is_cached(actual_name):
# downloadPack calls getPack internally again, but it's cached
manager.downloadPack(actual_name)
manager.convertPack(actual_name, formats={"gif", "png"}, backend=Backend.RLOTTIE_PYTHON)
# Build response from files on disk
png_dir = settings.downloads_dir / actual_name / "png"
gif_dir = settings.downloads_dir / actual_name / "gif"
# Build a lookup of sticker metadata from the pack files
sticker_lookup: dict[str, dict] = {}
for sticker in pack["files"]:
sticker_id = sticker.name.split("_")[-1].split(".")[0]
sticker_lookup[sticker_id] = {
"emoji": sticker.emoji,
"emoji_name": sticker.emojiName(),
"is_animated": sticker.fileType == "tgs",
}
stickers: list[StickerResponse] = []
if png_dir.exists():
for png_file in sorted(png_dir.iterdir()):
sticker_id = png_file.stem.split("+")[0]
emoji_name = png_file.stem.split("+")[1] if "+" in png_file.stem else ""
meta = sticker_lookup.get(sticker_id, {})
emoji = meta.get("emoji", "")
is_animated = meta.get("is_animated", False)
if not emoji_name:
emoji_name = meta.get("emoji_name", "")
gif_url = None
gif_file = gif_dir / f"{png_file.stem}.gif"
if gif_file.exists():
gif_url = f"/api/stickersets/{actual_name}/stickers/{sticker_id}.gif"
stickers.append(
StickerResponse(
id=sticker_id,
emoji=emoji,
emoji_name=emoji_name,
is_animated=is_animated,
png_url=f"/api/stickersets/{actual_name}/stickers/{sticker_id}.png",
gif_url=gif_url,
)
)
# Also check for GIF-only stickers (animated TGS that don't produce PNG)
if gif_dir.exists():
existing_ids = {s.id for s in stickers}
for gif_file in sorted(gif_dir.iterdir()):
sticker_id = gif_file.stem.split("+")[0]
if sticker_id in existing_ids:
continue
emoji_name = gif_file.stem.split("+")[1] if "+" in gif_file.stem else ""
meta = sticker_lookup.get(sticker_id, {})
stickers.append(
StickerResponse(
id=sticker_id,
emoji=meta.get("emoji", ""),
emoji_name=emoji_name or meta.get("emoji_name", ""),
is_animated=True,
png_url=f"/api/stickersets/{actual_name}/stickers/{sticker_id}.png",
gif_url=f"/api/stickersets/{actual_name}/stickers/{sticker_id}.gif",
)
)
return StickerSetResponse(
name=actual_name,
title=pack["title"],
sticker_count=len(stickers),
stickers=stickers,
)
def get_sticker_file(pack_name: str, sticker_id: str, fmt: str) -> Path | None:
"""Return the path to a converted sticker file, or None if not found."""
fmt_dir = settings.downloads_dir / pack_name / fmt
if not fmt_dir.exists():
return None
# Files are named like: {id}+{emoji_name}.{ext}
for f in fmt_dir.iterdir():
if f.stem.split("+")[0] == sticker_id:
return f
return None

10
StiggerDLBot/botfather.md Normal file
View File

@@ -0,0 +1,10 @@
Name: StiggerDLBot
Username: StiggerDLBot
Done! Congratulations on your new bot. You will find it at t.me/StiggerDLBot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
8247171504:AAE5o32Zv1B6N5VXh5S9m6H97LTB6c7Vu1s
Keep your token secure and store it safely, it can be used by anyone to control your bot.
For a description of the Bot API, see this page: https://core.telegram.org/bots/api

View File

@@ -0,0 +1,6 @@
fastapi
uvicorn[standard]
pydantic-settings
python-dotenv
tstickers
Pillow