Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1f34d81
refactor: reconcile cache.py with file_manager.py
OverStyleFR Jun 19, 2026
21ad3dd
feat: enhance ProgressFile with optional progress callback
OverStyleFR Jun 19, 2026
0c25829
feat(upload): integrate progress messages with ProgressFile
OverStyleFR Jun 19, 2026
4f8a78e
feat(download): add progress messages and fix retention bugs
OverStyleFR Jun 19, 2026
213677f
feat(music): add progress messages and fix retention bugs
OverStyleFR Jun 19, 2026
e4c16c0
fix: curl_uploader finally block and Dockerfile setuptools
OverStyleFR Jun 19, 2026
ef26bed
chore: cleanup dead imports and standardize version
OverStyleFR Jun 19, 2026
da1895b
chore: add .env to gitignore
OverStyleFR Jun 19, 2026
0e1b66e
chore: add imghdr.py to gitignore for Python 3.13+ compatibility
OverStyleFR Jun 19, 2026
48fd842
fix: cache stats showing 0, progress message replies, remove message …
OverStyleFR Jun 19, 2026
26c03ac
feat: session-only cache with hit tracking, cache usage indicators
OverStyleFR Jun 19, 2026
e42f96b
chore(stats): display free space in GB, add French number formatting
OverStyleFR Jun 19, 2026
4328183
chore(stats): remove download_temp from stats display
OverStyleFR Jun 19, 2026
bf62535
fix: clear downloads folder at startup
OverStyleFR Jun 19, 2026
5b45c8d
deploy: optimisation Docker, CI/CD, standalone et retention
OverStyleFR Jun 19, 2026
4f31ca4
fix: commit imghdr.py shim for Python 3.13+ compatibility
OverStyleFR Jun 19, 2026
524ba56
fix: add urllib3 to requirements.txt for PTB v13.7 fallback
OverStyleFR Jun 19, 2026
410a373
fix: pin urllib3<2 and CI Python to 3.11 for PTB v13.7 compat
OverStyleFR Jun 19, 2026
48cd1fb
ci: bump setup-qemu and setup-buildx actions to v4
OverStyleFR Jun 19, 2026
5134809
docs: update AGENTS.md and .env.example after deploy optimisations
OverStyleFR Jun 19, 2026
11aa20c
fix: VERSION sourcee depuis .env/.env.example, pas config.py
OverStyleFR Jun 19, 2026
fb22a11
Merge branch 'main' into develop
OverStyleFR Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
.env
.env.*
!.env.example
.venv/
venv/
__pycache__/
*.py[cod]
*$py.class
.git/
.github/
.gitignore
.dockerignore
.env
.env.example
logs/
downloads/
download_temp/
__pycache__/
**/__pycache__/
*.pyc
imghdr.py
.kilo/
AGENTS.md
*.md
ffmpeg/
token.txt
egg-socialvideodownload.json
Dockerfile.pelican
entrypoint.sh
README.md
DOCS.md
38 changes: 20 additions & 18 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ on:
- develop

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Verify imports
run: python -c "import main"

build-and-push:
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -17,18 +30,11 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Set up QEMU
uses: docker/setup-qemu-action@v4

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run tests
run: |
# Add your test commands here
echo "No tests to run"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GitHub Packages
uses: docker/login-action@v4
Expand All @@ -45,14 +51,9 @@ jobs:
id: tags
run: |
REPO="ghcr.io/${{ env.repo_name }}"
if [ -f .env.example ] && grep -q '^VERSION=' .env.example; then
BOT_VERSION=$(grep '^VERSION=' .env.example | cut -d= -f2 | tr -d '[:space:]')
else
BOT_VERSION="unknown"
fi
VERSION_SHORT=$(echo "$BOT_VERSION" | sed -E 's/^((V[0-9]+)(\.[0-9]+)?).*/\1/')
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "tags=${REPO}:latest,${REPO}:${VERSION_SHORT},${REPO}:${BOT_VERSION}" >> $GITHUB_OUTPUT
VERSION=$(grep '^VERSION' .env.example | head -1 | sed 's/.*=\(.*\)/\1/')
echo "tags=${REPO}:latest,${REPO}:${VERSION}" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then
echo "tags=${REPO}:dev" >> $GITHUB_OUTPUT
else
Expand All @@ -66,6 +67,7 @@ jobs:
context: .
push: true
tags: ${{ steps.tags.outputs.tags }}
platforms: linux/amd64,linux/arm64

- name: Image digest
run: echo ${{ steps.build-and-push.outputs.digest }}
245 changes: 126 additions & 119 deletions AGENTS.md

Large diffs are not rendered by default.

30 changes: 19 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
# --- FFmpeg Stage ---
FROM ghcr.io/linuxserver/ffmpeg:latest AS ffmpeg

# --- Build Stage ---
FROM python:3.11-slim-bookworm AS builder

WORKDIR /build
WORKDIR /app

RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential && \
rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt


# --- Final Stage ---
FROM python:3.11-slim-bookworm

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
PYTHONUNBUFFERED=1

WORKDIR /app

RUN apt-get update && \
apt-get install -y --no-install-recommends ffmpeg ca-certificates && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/wheels /wheels
COPY --from=ffmpeg /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=ffmpeg /usr/local/bin/ffprobe /usr/local/bin/ffprobe
RUN chmod +x /usr/local/bin/ffmpeg /usr/local/bin/ffprobe

COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && \
rm -rf /wheels
RUN pip install --no-cache $(ls /wheels/*.whl | grep -v setuptools) && rm -rf /wheels

COPY . .

RUN mkdir -p /app/logs /app/downloads /app/download_temp
RUN mkdir -p /app/logs /app/downloads

CMD ["python", "main.py"]
80 changes: 55 additions & 25 deletions commands/download.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# commands/download.py
import os
import yt_dlp
from utils.logger import console_logger
from utils.file_manager import is_already_downloaded, save_download
from utils.disk_manager import check_and_clean_if_needed
from utils.retention import set_retention
from utils.cache import add_to_cache, record_cache_hit
from utils.upload import upload_file


def _edit_progress(bot, chat_id, msg_id, text):
try:
bot.edit_message_text(chat_id=chat_id, message_id=msg_id, text=text)
except Exception:
pass


def download(update, context):
args = context.args
if not args:
Expand All @@ -14,41 +24,61 @@ def download(update, context):
return

url = args[0]
chat_id = update.message.chat_id
bot = context.bot

# Vérification de l'espace disque avant téléchargement
check_and_clean_if_needed()

console_logger.info(f"[DOWNLOAD] Traitement de l'URL: {url} par {update.message.from_user.username}")

progress_msg = update.message.reply_text(
"⏳ Téléchargement en cours...",
reply_to_message_id=update.message.message_id
)
progress_msg_id = progress_msg.message_id
ydl_opts = {'outtmpl': 'downloads/%(title)s.%(ext)s'}

should_download = True
from_cache = False
filename = None

if is_already_downloaded(url):
console_logger.info(f"[DOWNLOAD] Fichier déjà téléchargé pour l'URL: {url} par {update.message.from_user.username}. Récupération du fichier...")
console_logger.info(f"[DOWNLOAD] Fichier déjà téléchargé pour l'URL: {url} par {update.message.from_user.username}. Vérification du fichier...")
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False)
filename = ydl.prepare_filename(info)
upload_file(update, filename, context)
console_logger.info(f"[DOWNLOAD] Fichier envoyé pour l'URL: {url} par {update.message.from_user.username}")
if os.path.exists(filename):
should_download = False
from_cache = True
set_retention(filename)
add_to_cache(url, os.path.getsize(filename))
record_cache_hit(url)
_edit_progress(bot, chat_id, progress_msg_id, "📦 Utilisation du cache...")
else:
console_logger.warning(f"[DOWNLOAD] Fichier manquant malgré hash pour l'URL: {url}. Retéléchargement...")
except Exception as e:
update.message.reply_text("Erreur lors de la récupération du fichier.")
console_logger.error(f"[DOWNLOAD] Erreur récupération fichier pour l'URL: {url} par {update.message.from_user.username} - {str(e)}")
return
console_logger.error(f"[DOWNLOAD] Erreur récupération infos pour l'URL: {url} - {str(e)}")

max_attempts = 3
attempts = 0
while attempts < max_attempts:
try:
console_logger.info(f"[DOWNLOAD] Tentative {attempts + 1} de téléchargement pour l'URL: {url} par {update.message.from_user.username}")
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
filename = ydl.prepare_filename(info)
save_download(url)
set_retention(filename)
console_logger.info(f"[DOWNLOAD] Téléchargement terminé pour l'URL: {url} par {update.message.from_user.username}. Envoi du fichier...")
upload_file(update, filename, context)
break
except Exception as e:
attempts += 1
console_logger.error(f"[DOWNLOAD] Tentative {attempts} échouée pour l'URL: {url} par {update.message.from_user.username} - {str(e)}")
if attempts >= max_attempts:
update.message.reply_text("Erreur lors du téléchargement après plusieurs tentatives.")
if should_download:
max_attempts = 3
attempts = 0
while attempts < max_attempts:
try:
console_logger.info(f"[DOWNLOAD] Tentative {attempts + 1} de téléchargement pour l'URL: {url} par {update.message.from_user.username}")
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
filename = ydl.prepare_filename(info)
save_download(url)
set_retention(filename)
add_to_cache(url, os.path.getsize(filename))
break
except Exception as e:
attempts += 1
console_logger.error(f"[DOWNLOAD] Tentative {attempts} échouée pour l'URL: {url} par {update.message.from_user.username} - {str(e)}")
if attempts >= max_attempts:
_edit_progress(bot, chat_id, progress_msg_id, "❌ Échec du téléchargement après plusieurs tentatives.")
return

_edit_progress(bot, chat_id, progress_msg_id, "📤 Envoi en cours... 0%")
upload_file(update, filename, context, progress_msg_id=progress_msg_id, from_cache=from_cache)
64 changes: 47 additions & 17 deletions commands/music.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
from utils.logger import console_logger
from utils.file_manager import is_already_downloaded, save_download
from utils.retention import set_retention
from utils.cache import add_to_cache, record_cache_hit
from utils.upload import upload_file
from config import FFMPEG_PATH


def _edit_progress(bot, chat_id, msg_id, text):
try:
bot.edit_message_text(chat_id=chat_id, message_id=msg_id, text=text)
except Exception:
pass


def music(update, context):
args = context.args
if not args:
Expand All @@ -16,58 +25,79 @@ def music(update, context):
return

url = args[0]
chat_id = update.message.chat_id
bot = context.bot

console_logger.info(f"[MUSIC] Traitement de l'URL: {url} par {update.message.from_user.username}")

progress_msg = update.message.reply_text(
"⏳ Téléchargement vidéo en cours...",
reply_to_message_id=update.message.message_id
)
progress_msg_id = progress_msg.message_id
ydl_opts = {'outtmpl': 'downloads/%(title)s.%(ext)s'}

should_download = True
from_cache = False
video_file = None

if is_already_downloaded(url):
console_logger.info(f"[MUSIC] Vidéo déjà téléchargée pour l'URL: {url} par {update.message.from_user.username}. Récupération du fichier...")
console_logger.info(f"[MUSIC] Vidéo déjà téléchargée pour l'URL: {url} par {update.message.from_user.username}. Vérification du fichier...")
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False)
video_file = ydl.prepare_filename(info)
console_logger.info(f"[MUSIC] Vidéo trouvée: {video_file}")
if os.path.exists(video_file):
should_download = False
from_cache = True
set_retention(video_file)
add_to_cache(url, os.path.getsize(video_file))
record_cache_hit(url)
_edit_progress(bot, chat_id, progress_msg_id, "📦 Utilisation du cache...")
else:
console_logger.warning(f"[MUSIC] Vidéo manquante malgré hash pour l'URL: {url}. Retéléchargement...")
except Exception as e:
update.message.reply_text("Erreur lors de la récupération du fichier vidéo.")
console_logger.error(f"[MUSIC] Erreur récupération vidéo pour l'URL: {url} par {update.message.from_user.username} - {str(e)}")
console_logger.error(f"[MUSIC] Erreur récupération infos pour l'URL: {url} - {str(e)}")
_edit_progress(bot, chat_id, progress_msg_id, "❌ Erreur lors de la récupération de la vidéo.")
return
else:

if should_download:
max_attempts = 3
attempts = 0
while attempts < max_attempts:
try:
console_logger.info(f"[MUSIC] Tentative {attempts + 1} de téléchargement de la vidéo pour l'URL: {url} par {update.message.from_user.username}")
console_logger.info(f"[MUSIC] Tentative {attempts + 1} de téléchargement pour l'URL: {url} par {update.message.from_user.username}")
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=True)
video_file = ydl.prepare_filename(info)
save_download(url)
console_logger.info(f"[MUSIC] Téléchargement terminé: {video_file} par {update.message.from_user.username}")
set_retention(video_file)
add_to_cache(url, os.path.getsize(video_file))
break
except Exception as e:
attempts += 1
console_logger.error(f"[MUSIC] Tentative {attempts} échouée pour l'URL: {url} par {update.message.from_user.username} - {str(e)}")
if attempts >= max_attempts:
update.message.reply_text("Erreur lors du téléchargement de la vidéo après plusieurs tentatives.")
_edit_progress(bot, chat_id, progress_msg_id, "❌ Échec du téléchargement après plusieurs tentatives.")
return

# Conversion en audio MP3
_edit_progress(bot, chat_id, progress_msg_id, "🔄 Conversion audio...")

audio_file = os.path.splitext(video_file)[0] + ".mp3"
if os.path.exists(audio_file):
console_logger.info(f"[MUSIC] Fichier audio déjà converti: {audio_file}")
else:
try:
console_logger.info(f"[MUSIC] Conversion de {video_file} en audio {audio_file} via ffmpeg pour {update.message.from_user.username}...")
stream = ffmpeg.input(video_file)
stream = ffmpeg.output(stream, audio_file, format='mp3', acodec='libmp3lame', audio_bitrate='192k')
ffmpeg.run(stream, cmd=FFMPEG_PATH, quiet=True)
set_retention(audio_file)
add_to_cache(url + "#audio", os.path.getsize(audio_file))
console_logger.info(f"[MUSIC] Conversion terminée: {audio_file} pour {update.message.from_user.username}")
except Exception as e:
update.message.reply_text("Erreur lors de la conversion en audio.")
_edit_progress(bot, chat_id, progress_msg_id, "❌ Erreur lors de la conversion en audio.")
console_logger.error(f"[MUSIC] Erreur conversion en audio pour {video_file} par {update.message.from_user.username} - {str(e)}")
return

try:
console_logger.info(f"[MUSIC] Envoi du fichier audio: {audio_file} pour {update.message.from_user.username}")
upload_file(update, audio_file, context)
except Exception as e:
update.message.reply_text("Erreur lors de l'envoi du fichier audio.")
console_logger.error(f"[MUSIC] Erreur envoi audio pour {audio_file} par {update.message.from_user.username} - {str(e)}")
_edit_progress(bot, chat_id, progress_msg_id, "📤 Envoi en cours... 0%")
upload_file(update, audio_file, context, progress_msg_id=progress_msg_id, from_cache=from_cache)
3 changes: 1 addition & 2 deletions commands/start.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from telegram import ParseMode
from utils.logger import console_logger

def start(update, context):
Expand All @@ -8,5 +7,5 @@ def start(update, context):
"Je suis un bot qui permet de télécharger des vidéos/musiques via des liens de réseaux sociaux (principalement YouTube & TikTok)."
)

update.message.reply_text(welcome_message, parse_mode=ParseMode.HTML)
update.message.reply_text(welcome_message)
console_logger.info(f"[START] Commande /start exécutée par {update.message.from_user.username}")
Loading
Loading