Skip to content

Commit b486ae4

Browse files
sigitoshsigitoshDirtyRacer1337
authored
Add webhook feature (ThePornDatabase#272)
* add webhook feature * refactor --------- Co-authored-by: sigitosh <¨sigitosh@proton.me¨> Co-authored-by: DirtyRacer <DirtyRacer@ya.ru>
1 parent 14dbc64 commit b486ae4

File tree

5 files changed

+143
-1
lines changed

5 files changed

+143
-1
lines changed

namer/configuration.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,16 @@ class NamerConfig:
460460
Add creation date from failed log to table in web interface
461461
"""
462462

463+
webhook_enabled: bool = False
464+
"""
465+
Enable webhook notification when a file is successfully renamed
466+
"""
467+
468+
webhook_url: str = ''
469+
"""
470+
URL to send HTTP POST notification to when a file is successfully renamed
471+
"""
472+
463473
cache_session: Optional[CachedSession] = None
464474
"""
465475
If use_requests_cache is true this http.session will be constructed and used for requests to TPDB.

namer/configuration_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ def set_boolean(updater: ConfigUpdater, section: str, key: str, value: bool) ->
308308
'allow_delete_files': ('watchdog', to_bool, from_bool),
309309
'add_columns_from_log': ('watchdog', to_bool, from_bool),
310310
'add_complete_column': ('watchdog', to_bool, from_bool),
311+
'webhook_enabled': ('webhook', to_bool, from_bool),
312+
'webhook_url': ('webhook', None, None),
311313
'debug': ('watchdog', to_bool, from_bool),
312314
'console_format': ('watchdog', None, None),
313315
'manual_mode': ('watchdog', to_bool, from_bool),

namer/namer.cfg.default

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,10 @@ manual_mode = False
309309
# values in the stack trace, potentially including the porndb token, this setting should only be turned on
310310
# if you are going to check an logs you share for your token.
311311
diagnose_errors = False
312+
313+
[webhook]
314+
# Enable webhook notification when a file is successfully renamed
315+
webhook_enabled = False
316+
317+
# URL to send HTTP POST notification to when a file is successfully renamed
318+
webhook_url =

namer/namer.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from namer.database import search_file_in_database, write_file_to_database
2424
from namer.ffmpeg import FFProbeResults, FFMpeg
2525
from namer.fileinfo import FileInfo
26+
from namer.http import Http
2627
from namer.metadataapi import get_complete_metadataapi_net_fileinfo, get_image, get_trailer, match, share_hash
2728
from namer.moviexml import parse_movie_xml_file, write_nfo
2829
from namer.name_formatter import PartialFormatter
@@ -239,7 +240,7 @@ def process_file(command: Command) -> Optional[Command]:
239240
target = move_to_final_location(command, new_metadata)
240241
tag_in_place(target.target_movie_file, command.config, new_metadata, ffprobe_results)
241242
add_extra_artifacts(target.target_movie_file, new_metadata, search_results, phash, command.config)
242-
243+
send_webhook_notification(target.target_movie_file, command.config)
243244
logger.success('Done processing file: {}, moved to {}', command.target_movie_file, target.target_movie_file)
244245
return target
245246
elif command.inplace is False:
@@ -272,6 +273,27 @@ def add_extra_artifacts(video_file: Path, new_metadata: LookedUpFileInfo, search
272273
write_nfo(video_file, new_metadata, config, trailer, poster, background, phash)
273274

274275

276+
def send_webhook_notification(video_file: Path, config: NamerConfig):
277+
"""
278+
Send a webhook notification to the configured URL when a file is successfully renamed.
279+
"""
280+
if not config.webhook_enabled or not config.webhook_url:
281+
return
282+
283+
payload = {
284+
'target_movie_file': str(video_file)
285+
}
286+
287+
response = None
288+
try:
289+
response = Http.post(config.webhook_url, json=payload)
290+
except Exception as e:
291+
logger.error(f'Failed to send webhook notification: {str(e)}')
292+
293+
if response and response.ok:
294+
logger.info(f'Webhook notification sent successfully to {config.webhook_url}')
295+
296+
275297
def check_arguments(file_to_process: Path, dir_to_process: Path, config_override: Path):
276298
"""
277299
check arguments.

test/namer_webhook_test.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
Test webhook notification functionality in namer.py
3+
"""
4+
import logging
5+
import unittest
6+
from pathlib import Path
7+
from unittest.mock import patch, MagicMock
8+
9+
from namer.namer import send_webhook_notification
10+
from test.utils import sample_config, environment, new_ea
11+
12+
13+
class WebhookTest(unittest.TestCase):
14+
"""
15+
Test the webhook notification functionality.
16+
"""
17+
18+
def test_webhook_disabled(self):
19+
"""
20+
Test that no webhook is sent when the feature is disabled.
21+
"""
22+
config = sample_config()
23+
config.webhook_enabled = False
24+
config.webhook_url = 'http://example.com/webhook'
25+
26+
with patch('requests.request') as mock_post:
27+
send_webhook_notification(Path('/some/path/movie.mp4'), config)
28+
mock_post.assert_not_called()
29+
30+
def test_webhook_no_url(self):
31+
"""
32+
Test that no webhook is sent when the URL is not configured.
33+
"""
34+
config = sample_config()
35+
config.webhook_enabled = True
36+
config.webhook_url = ''
37+
38+
with patch('requests.request') as mock_post:
39+
send_webhook_notification(Path('/some/path/movie.mp4'), config)
40+
mock_post.assert_not_called()
41+
42+
def test_webhook_success(self):
43+
"""
44+
Test that a webhook is successfully sent when enabled and URL is configured.
45+
"""
46+
config = sample_config()
47+
config.webhook_enabled = True
48+
config.webhook_url = 'http://example.com/webhook'
49+
50+
with patch('requests.request') as mock_post:
51+
mock_response = MagicMock()
52+
mock_response.raise_for_status.return_value = None
53+
mock_post.return_value = mock_response
54+
55+
send_webhook_notification(Path('/some/path/movie.mp4'), config)
56+
57+
mock_post.assert_called_once()
58+
# Verify payload structure
59+
args, kwargs = mock_post.call_args
60+
self.assertEqual(args[0], 'POST')
61+
self.assertEqual(args[1], 'http://example.com/webhook')
62+
self.assertEqual(kwargs['json'], {'target_movie_file': str(Path('/some/path/movie.mp4'))})
63+
64+
def test_webhook_failure(self):
65+
"""
66+
Test that webhook errors are properly handled.
67+
"""
68+
config = sample_config()
69+
config.webhook_enabled = True
70+
config.webhook_url = 'http://example.com/webhook'
71+
72+
with patch('requests.request') as mock_post:
73+
mock_post.side_effect = Exception('Connection error')
74+
75+
# Should not raise an exception
76+
send_webhook_notification(Path('/some/path/movie.mp4'), config)
77+
78+
mock_post.assert_called_once()
79+
80+
def test_integration_with_file_processing(self):
81+
"""
82+
Test that webhook is triggered when a file is successfully processed.
83+
"""
84+
with environment() as (tempdir, _fakeTPDB, config):
85+
config.webhook_enabled = True
86+
config.webhook_url = 'http://example.com/webhook'
87+
88+
with patch('namer.namer.send_webhook_notification') as mock_webhook:
89+
targets = [new_ea(tempdir, use_dir=False)]
90+
91+
# Process the file
92+
from namer.namer import main
93+
main(['-f', str(targets[0].file), '-c', str(config.config_file)])
94+
95+
# Verify webhook was called
96+
mock_webhook.assert_called()
97+
98+
99+
if __name__ == '__main__':
100+
logging.basicConfig(level=logging.INFO)
101+
unittest.main()

0 commit comments

Comments
 (0)