Skip to content

feat(hooks): run configurable command after each successful download - #55

Open
rfsbraz wants to merge 1 commit into
mainfrom
feat/post-download-hooks
Open

feat(hooks): run configurable command after each successful download#55
rfsbraz wants to merge 1 commit into
mainfrom
feat/post-download-hooks

Conversation

@rfsbraz

@rfsbraz rfsbraz commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Fixes #35

  • New TDL_POST_DOWNLOAD_HOOK setting runs a command after each successful download, with {file}, {dir}, {filename}, {extension}, {source} and {size} placeholders
  • TDL_POST_DOWNLOAD_HOOK_TIMEOUT (default 300s) kills hung hooks
  • Injection-safe by construction: the template is tokenized first, placeholders are substituted into tokens afterwards, and the process runs without a shell - filenames can never add arguments or shell syntax
  • Hook stdout/stderr logged at DEBUG, non-zero exits and timeouts at WARNING; a hook failure never marks the download as failed
  • Unit tests cover substitution, injection safety, execution, timeout and the never-fail contract; README documents the feature

Copilot AI review requested due to automatic review settings July 5, 2026 08:44
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Testing this PR

Option 1 — Docker Compose override:

Create a docker-compose.override.yml:

services:
  telegram-downloader:
    build: https://github.com/rfsbraz/telegram-downloader.git#refs/pull/55/head

Then run:

docker compose up --build

Option 2 — Direct build and run:

docker build https://github.com/rfsbraz/telegram-downloader.git#refs/pull/55/head -t telegram-downloader:pr-55
docker run --rm -it telegram-downloader:pr-55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a configurable post-download hook that runs an external command after each successful download, enabling integrations (extract/convert/scan/move) without modifying the downloader core (Fixes #35).

Changes:

  • Introduces post_download_hook and post_download_hook_timeout configuration, loaded from env/YAML and documented in README.
  • Executes the hook after successful downloads with placeholder substitution and timeout handling.
  • Adds unit tests for command building/substitution and hook execution semantics (success, non-zero exit, missing command, timeout, invalid syntax).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/client/test_hooks.py Adds unit tests for placeholder substitution, injection safety, execution, and timeout behavior.
src/main.py Invokes the post-download hook after each successful download.
src/config/schema.py Adds config schema fields for the hook template and timeout.
src/config/loader.py Ensures env var parsing supports the new hook fields and timeout integer coercion.
src/client/hooks.py Implements hook command building and async subprocess execution with logging + timeout.
README.md Documents the new hook settings and available placeholders.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/client/hooks.py
Comment on lines +52 to +56
argv = []
for token in shlex.split(template):
for name in PLACEHOLDERS:
token = token.replace("{" + name + "}", values[name])
argv.append(token)
Comment thread src/client/hooks.py
Comment on lines +93 to +101
try:
process = await asyncio.create_subprocess_exec(
*argv,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
except Exception as e:
log.warning(f"Post-download hook failed to start for {file_path.name}: {e}")
return
Comment thread src/client/hooks.py
Comment on lines +103 to +116
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(), timeout=timeout
)
except asyncio.TimeoutError:
process.kill()
await process.wait()
log.warning(
f"Post-download hook timed out after {timeout}s for {file_path.name}"
)
return
except Exception as e:
log.warning(f"Post-download hook failed for {file_path.name}: {e}")
return
Comment thread src/config/schema.py
Comment on lines +116 to +118
# Post-download hook: shell command run after each successful download.
# Supports placeholders: {file}, {dir}, {filename}, {extension}, {source}, {size}
post_download_hook: Optional[str] = None
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: post-download hooks

2 participants