From d1916660e92bb3a1a763b886ee04d7a95a0a0b22 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Mon, 9 Sep 2024 18:17:13 +0200 Subject: [PATCH 01/19] Add exception class for Task Scheduler --- synology_api/auth.py | 5 ++++- synology_api/exceptions.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/synology_api/auth.py b/synology_api/auth.py index 23e6db1..b3b42d2 100644 --- a/synology_api/auth.py +++ b/synology_api/auth.py @@ -9,7 +9,7 @@ from .exceptions import SynoConnectionError, HTTPError, JSONDecodeError, LoginError, LogoutError, DownloadStationError from .exceptions import FileStationError, AudioStationError, ActiveBackupError, VirtualizationError, BackupError from .exceptions import CertificateError, CloudSyncError, DHCPServerError, DirectoryServerError, DockerError, DriveAdminError -from .exceptions import LogCenterError, NoteStationError, OAUTHError, PhotosError, SecurityAdvisorError +from .exceptions import LogCenterError, NoteStationError, OAUTHError, PhotosError, SecurityAdvisorError, TaskSchedulerError from .exceptions import UniversalSearchError, USBCopyError, VPNError, CoreSysInfoError, UndefinedError USE_EXCEPTIONS: bool = True @@ -350,6 +350,9 @@ def request_data(self, # Security advisor error: elif api_name.find('SecurityAdvisor') > -1: raise SecurityAdvisorError(error_code=error_code) + # Task Scheduler error: + elif api_name.find('SYNO.Core.TaskScheduler') or api_name.find('SYNO.Core.EventScheduler') > -1: + raise TaskSchedulerError(error_code=error_code) # Universal search error: elif api_name.find('SYNO.Finder') > -1: raise UniversalSearchError(error_code=error_code) diff --git a/synology_api/exceptions.py b/synology_api/exceptions.py index d52d226..cfdc61e 100644 --- a/synology_api/exceptions.py +++ b/synology_api/exceptions.py @@ -272,6 +272,18 @@ def __init__(self, error_code: int, *args: object) -> None: return +class TaskSchedulerError(SynoBaseException): + """Class for an error during TaskScheduler request. NOTE:... no docs on errors....""" + + def __init__(self, error_code: int, *args: object) -> None: + self.error_code = error_code + if error_code in error_codes.keys(): + super().__init__(error_message=error_codes[error_code], *args) + else: + super().__init__(error_message="TaskScheduler Error: %i" % error_code, *args) + return + + class UniversalSearchError(SynoBaseException): """Class for an error during UniversalSearch request. NOTE:... no docs on errors....""" From 6f3c6da5ab9a3a630a03d081849dedd312ab9a05 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Tue, 10 Sep 2024 17:30:52 +0200 Subject: [PATCH 02/19] Add getters --- synology_api/task_scheduler.py | 180 +++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 synology_api/task_scheduler.py diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py new file mode 100644 index 0000000..7c704bd --- /dev/null +++ b/synology_api/task_scheduler.py @@ -0,0 +1,180 @@ +from __future__ import annotations +from . import base_api +import json + + +class TaskScheduler(base_api.BaseApi): + """ + Task Scheduler API implementation. + + This API provides the functionality to get information related to the scheduler settings and current tasks. + + For the moment, the available actions with the API are: + - Get all tasks + - Get task information + - Get task results + - Get task result log + - Enable/Disable task + - Run task + - Delete task + - Get/Set output path for task results + - Create task + - Set task settings + + To implement in the future: + - Add retention settings for Recycle bin task set/create methods. + """ + + def get_task_list( + self, + sort_by: str = 'next_trigger_time', + sort_direction: str = 'ASC', + offset: int = 0, + limit: int = 50 + ) -> dict[str, object] | str: + """List all present tasks. + + Args: + sort_by (str, optional): + The field to sort tasks by. Defaults to `"next_trigger_time"`. + Possible values: + - "next_trigger_time" + - "name" + - "type" + - "action" + - "owner" + sort_direction (str, optional): + The sort direction. Defaults to `"ASC"`. + Possible values: + - "ASC" + - "DESC" + offset (int, optional): + Task offset for pagination. Defaults to `0`. + limit (int, optional): + Number of tasks to retrieve. Defaults to `50`. + + Returns: + dict|str: + A dictionary containing a list of the tasks and information related to them, or a string in case of an error. + + Example return: + { + "data": { + "tasks": [ + { + "action": "Run: rsync -aP --delete /volume1/test/ /volume1/test2/", + "can_delete": true, + "can_edit": true, + "can_run": true, + "enable": false, + "id": 13, + "name": "Sync folders", + "next_trigger_time": "2024-09-09 12:26", + "owner": "root", + "real_owner": "root", + "type": "script" + }, + { + "action": "Run: echo hello > /tmp/awacate.out", + "can_delete": true, + "can_edit": true, + "can_run": true, + "enable": true, + "id": 11, + "name": "TEST_CRONTAB", + "next_trigger_time": "2024-09-10 00:00", + "owner": "root", + "real_owner": "root", + "type": "script" + } + ] + "total": 2 + }, + "success": true + } + """ + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 3, + 'method': 'list', + 'sort_by': sort_by, + 'sort_direction': sort_direction, + 'offset': offset, + 'limit': limit + } + + return self.request_data(api_name, api_path, req_param) + + def get_output_config(self) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'config_get', + 'type': 'esynoscheduler' + } + + return self.request_data(api_name, api_path, req_param) + + def get_task_config( + self, + task_id: int, + real_owner: str + ) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'get', + 'id': task_id, + 'real_owner': real_owner + + } + + return self.request_data(api_name, api_path, req_param) + + def get_task_results( + self, + task_id: int + ) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'get_history_status_list', + 'id': task_id + + } + + return self.request_data(api_name, api_path, req_param) + + def get_task_result_logs( + self, + task_id: int, + timestamp: int + ) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'get_history_log', + 'timestamp': str(timestamp), + 'id': task_id + } + + return self.request_data(api_name, api_path, req_param) + From 9662e54d62e90b7546dfaaba5e606f06aee4422e Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Tue, 10 Sep 2024 17:52:22 +0200 Subject: [PATCH 03/19] Add setters and actions --- synology_api/task_scheduler.py | 146 +++++++++++++++++++++++++++++---- 1 file changed, 131 insertions(+), 15 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 7c704bd..628dcc5 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -25,6 +25,20 @@ class TaskScheduler(base_api.BaseApi): - Add retention settings for Recycle bin task set/create methods. """ + def get_output_config(self) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'config_get', + 'type': 'esynoscheduler' + } + + return self.request_data(api_name, api_path, req_param) + def get_task_list( self, sort_by: str = 'next_trigger_time', @@ -107,20 +121,6 @@ def get_task_list( return self.request_data(api_name, api_path, req_param) - def get_output_config(self) -> dict[str, object] | str: - """ - """ - api_name = 'SYNO.Core.EventScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': 'config_get', - 'type': 'esynoscheduler' - } - - return self.request_data(api_name, api_path, req_param) - def get_task_config( self, task_id: int, @@ -154,7 +154,6 @@ def get_task_results( 'version': 1, 'method': 'get_history_status_list', 'id': task_id - } return self.request_data(api_name, api_path, req_param) @@ -178,3 +177,120 @@ def get_task_result_logs( return self.request_data(api_name, api_path, req_param) + def set_output_config( + self, + enable_output: bool, + output_path: str + ) -> dict[str, object] | str: + """ + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'config_set', + 'type': 'esynoscheduler', + 'output_path': output_path, + 'enable_output': enable_output + } + + return self.request_data(api_name, api_path, req_param) + + def set_task_settings(self) -> dict[str, object] | str: + # TODO + print() + + def task_enable( + self, + task_id: int, + real_owner: str + ) -> dict[str, object] | str: + """ + """ + task_dict = { + 'id': task_id, + 'real_owner': real_owner, + 'enable': 'true' + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 2, + 'method': 'set_enable', + 'status': f'[{json.dumps(task_dict)}]', + } + + return self.request_data(api_name, api_path, req_param) + + def task_disable( + self, + task_id: int, + real_owner: str + ) -> dict[str, object] | str: + """ + """ + task_dict = { + 'id': task_id, + 'real_owner': real_owner, + 'enable': 'false' + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 2, + 'method': 'set_enable', + 'status': f'[{json.dumps(task_dict)}]', + } + + return self.request_data(api_name, api_path, req_param) + + def task_run( + self, + task_id: int, + real_owner: str + ) -> dict[str, object] | str: + """ + """ + task_dict = { + 'id': task_id, + 'real_owner': real_owner + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 2, + 'method': 'run', + 'tasks': f'[{json.dumps(task_dict)}]', + } + + return self.request_data(api_name, api_path, req_param) + + def task_delete( + self, + task_id: int, + real_owner: str + ) -> dict[str, object] | str: + """ + """ + task_dict = { + 'id': task_id, + 'real_owner': real_owner + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 2, + 'method': 'delete', + 'tasks': f'[{json.dumps(task_dict)}]', + } + + return self.request_data(api_name, api_path, req_param) From 6587ca78c1864e4d6b90aebd6fc18a0d62910d10 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 13:33:15 +0200 Subject: [PATCH 04/19] Add script task create method and Schedule interface --- synology_api/task_scheduler.py | 116 ++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 628dcc5..e191f3c 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -2,6 +2,70 @@ from . import base_api import json +class _Schedule(): + def __init__( + self, + run_frequently: bool = True, # date_type + run_days: str = '0,1,2,3,4,5,6', # week_days + run_date: str = '', # date + repeat: str = 'Daily', + monthly_week = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = 0, + ): + self.run_frequently = run_frequently + self.run_days = run_days + self.run_date = run_date + self.repeat = repeat + self.monthly_week = monthly_week + self.start_time_h = start_time_h + self.start_time_m = start_time_m + self.same_day_repeat_h = same_day_repeat_h + self.same_day_repeat_m = same_day_repeat_m + self.same_day_repeat_until = same_day_repeat_until + + def _generate_dict(self) -> dict: + schedule_dict = { + 'date_type': self.run_frequently, + 'monthly_week': json.dumps(self.monthly_week), + 'hour': self.start_time_h, # Start time - Hour for the schedule + 'minute': self.start_time_m, # Start time - Minute for the schedule + 'repeat_hour': self.same_day_repeat_h, # Continue running on the same day - Repeat each X hours 1..23 // 0 = disabled + 'repeat_min': self.same_day_repeat_m, # Continue running on the same day - Repeat every X minute [1, 5, 10, 15, 20, 30] // 0 = disabled + 'last_work_hour': self.same_day_repeat_until, # Last run time + } + repeat_modality = -1 + + if self.run_frequently: + if self.repeat == 'Daily': + repeat_modality = 1001 + if self.repeat == 'Weekly': + repeat_modality = 1002 + if self.repeat == 'Monthly': + repeat_modality = 1003 + + schedule_dict['week_day'] = self.run_days + + if self.run_frequently == 1: + if self.repeat == 'No repeat': + repeat_modality = 0 + if self.repeat == 'Monthly': + repeat_modality = 1 + if self.repeat == 'Every 3 months': + repeat_modality = 5 + if self.repeat == 'Every 6 months': + repeat_modality = 3 + if self.repeat == 'Yearly': + repeat_modality = 2 + + schedule_dict['date'] = self.run_date + + schedule_dict['repeat_date'] = repeat_modality + + return schedule_dict class TaskScheduler(base_api.BaseApi): """ @@ -23,7 +87,7 @@ class TaskScheduler(base_api.BaseApi): To implement in the future: - Add retention settings for Recycle bin task set/create methods. - """ + """ def get_output_config(self) -> dict[str, object] | str: """ @@ -294,3 +358,53 @@ def task_delete( } return self.request_data(api_name, api_path, req_param) + + def create_script_task( + self, + task_name: str, + owner: str, + script: str, + enable: bool = True, + run_frequently: bool = True, # date_type + run_days: str = '0,1,2,3,4,5,6', # week_days + run_date: str = '', # date + repeat: str = 'Daily', + monthly_week = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = 0, + notify_email: str = '', + notify_error: bool = False + ) -> dict[str, object] | str: + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + print(schedule_dict) + + extra = { + 'notify_enable': 'true' if notify_email is not '' else 'false', + 'notify_mail': notify_email, + 'notify_if_error': notify_error, + 'script': script + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'create', + 'name': task_name, + 'real_owner': owner, + 'owner': owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra) + } + + return self.request_data(api_name, api_path, req_param) + From 14d66b7edfc286e8ca5bec048593bf6d0f75f1b5 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 14:32:11 +0200 Subject: [PATCH 05/19] Improve create method --- synology_api/task_scheduler.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index e191f3c..0962fd4 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -9,7 +9,7 @@ def __init__( run_days: str = '0,1,2,3,4,5,6', # week_days run_date: str = '', # date repeat: str = 'Daily', - monthly_week = [], + monthly_week: list[str] = [], start_time_h: int = 0, start_time_m: int = 0, same_day_repeat_h: int = 0, @@ -29,7 +29,7 @@ def __init__( def _generate_dict(self) -> dict: schedule_dict = { - 'date_type': self.run_frequently, + 'date_type': 0 if self.run_frequently else 1, 'monthly_week': json.dumps(self.monthly_week), 'hour': self.start_time_h, # Start time - Hour for the schedule 'minute': self.start_time_m, # Start time - Minute for the schedule @@ -40,25 +40,24 @@ def _generate_dict(self) -> dict: repeat_modality = -1 if self.run_frequently: - if self.repeat == 'Daily': + if self.repeat == 'daily': repeat_modality = 1001 - if self.repeat == 'Weekly': + if self.repeat == 'weekly': repeat_modality = 1002 - if self.repeat == 'Monthly': + if self.repeat == 'monthly': repeat_modality = 1003 schedule_dict['week_day'] = self.run_days - - if self.run_frequently == 1: - if self.repeat == 'No repeat': + else: + if self.repeat == 'no_repeat': repeat_modality = 0 - if self.repeat == 'Monthly': + if self.repeat == 'monthly': repeat_modality = 1 - if self.repeat == 'Every 3 months': + if self.repeat == 'every_3_months': repeat_modality = 5 - if self.repeat == 'Every 6 months': + if self.repeat == 'every_6_months': repeat_modality = 3 - if self.repeat == 'Yearly': + if self.repeat == 'yearly': repeat_modality = 2 schedule_dict['date'] = self.run_date @@ -369,26 +368,25 @@ def create_script_task( run_days: str = '0,1,2,3,4,5,6', # week_days run_date: str = '', # date repeat: str = 'Daily', - monthly_week = [], + monthly_week: list[str] = [], start_time_h: int = 0, start_time_m: int = 0, same_day_repeat_h: int = 0, same_day_repeat_m: int = 0, same_day_repeat_until: int = 0, notify_email: str = '', - notify_error: bool = False + notify_only_on_error: bool = False ) -> dict[str, object] | str: schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) schedule_dict = schedule._generate_dict() - print(schedule_dict) extra = { 'notify_enable': 'true' if notify_email is not '' else 'false', 'notify_mail': notify_email, - 'notify_if_error': notify_error, + 'notify_if_error': 'true' if notify_only_on_error else 'false', 'script': script } @@ -403,7 +401,8 @@ def create_script_task( 'owner': owner, 'enable': enable, 'schedule': json.dumps(schedule_dict), - 'extra': json.dumps(extra) + 'extra': json.dumps(extra), + 'type': 'script' } return self.request_data(api_name, api_path, req_param) From a20bdb14e95490008097c612b8fc33f9628d7d31 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 17:20:53 +0200 Subject: [PATCH 06/19] Add docstring for create script and tweak behavior --- synology_api/task_scheduler.py | 82 +++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 0962fd4..938c765 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -33,9 +33,9 @@ def _generate_dict(self) -> dict: 'monthly_week': json.dumps(self.monthly_week), 'hour': self.start_time_h, # Start time - Hour for the schedule 'minute': self.start_time_m, # Start time - Minute for the schedule - 'repeat_hour': self.same_day_repeat_h, # Continue running on the same day - Repeat each X hours 1..23 // 0 = disabled + 'repeat_hour': self.same_day_repeat_h, # Continue running on the same day - Repeat each X hours 0..23 'repeat_min': self.same_day_repeat_m, # Continue running on the same day - Repeat every X minute [1, 5, 10, 15, 20, 30] // 0 = disabled - 'last_work_hour': self.same_day_repeat_until, # Last run time + 'last_work_hour': self.same_day_repeat_until if self.same_day_repeat_until > -1 else self.start_time_h, # Last run time, defaults to start time if not provided } repeat_modality = -1 @@ -65,6 +65,7 @@ def _generate_dict(self) -> dict: schedule_dict['repeat_date'] = repeat_modality return schedule_dict + class TaskScheduler(base_api.BaseApi): """ @@ -364,19 +365,88 @@ def create_script_task( owner: str, script: str, enable: bool = True, - run_frequently: bool = True, # date_type - run_days: str = '0,1,2,3,4,5,6', # week_days - run_date: str = '', # date + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', repeat: str = 'Daily', monthly_week: list[str] = [], start_time_h: int = 0, start_time_m: int = 0, same_day_repeat_h: int = 0, same_day_repeat_m: int = 0, - same_day_repeat_until: int = 0, + same_day_repeat_until: int = -1, notify_email: str = '', notify_only_on_error: bool = False ) -> dict[str, object] | str: + """Create a new script task with the provided schedule and notification settings. + + Args: + task_name (str): + The name of the task. + owner (str): + The task owner. + script (str): + The script to be executed. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + notify_email (str, optional): + Email address to send notifications to. Defaults to an empty string, thus disabling the notification feature. + notify_only_on_error (bool, optional): + If `True`, notifications are only sent when an error occurs. Defaults to `False`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) From 41a525c0f6d9c2dec4e30924abda987daffc66f9 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 20:06:29 +0200 Subject: [PATCH 07/19] Add beep and service control task create method --- synology_api/task_scheduler.py | 253 ++++++++++++++++++++++++++++++++- 1 file changed, 249 insertions(+), 4 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 938c765..b5cbbb5 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -1,6 +1,9 @@ from __future__ import annotations +import urllib.parse from . import base_api import json +import requests +import urllib class _Schedule(): def __init__( @@ -188,10 +191,12 @@ def get_task_list( def get_task_config( self, task_id: int, - real_owner: str + real_owner: str, + type: str = '' ) -> dict[str, object] | str: """ """ + # pass id=-1 and type=service to get a list of all available services with their corresponding IDs api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] api_path = info['path'] @@ -200,9 +205,11 @@ def get_task_config( 'method': 'get', 'id': task_id, 'real_owner': real_owner - } + if type != '': + req_param['type'] = type + return self.request_data(api_name, api_path, req_param) def get_task_results( @@ -368,7 +375,7 @@ def create_script_task( run_frequently: bool = True, run_days: str = '0,1,2,3,4,5,6', run_date: str = '', - repeat: str = 'Daily', + repeat: str = 'daily', monthly_week: list[str] = [], start_time_h: int = 0, start_time_m: int = 0, @@ -378,7 +385,7 @@ def create_script_task( notify_email: str = '', notify_only_on_error: bool = False ) -> dict[str, object] | str: - """Create a new script task with the provided schedule and notification settings. + """Create a new Script task with the provided schedule and notification settings. Args: task_name (str): @@ -476,4 +483,242 @@ def create_script_task( } return self.request_data(api_name, api_path, req_param) + + def create_beep_control_task( + self, + task_name: str, + owner: str, + enable: bool = True, + beep_duration: int = 60, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Create a new Beep Control task with the provided schedule and beep duration. + + Args: + task_name (str): + The name of the task. + owner (str): + The task owner. + beep_duration (int, optional): + The amount of seconds the beep will be triggered for, in seconds. Defaults to `60`. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'beep_duration': str(beep_duration) + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'create', + 'name': task_name, + 'real_owner': owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra), + 'type': 'beep' + } + + return self.request_data(api_name, api_path, req_param) + + def create_service_control_task( + self, + task_name: str, + owner: str, + services: list[dict], + action: str, + enable: bool = True, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Create a new Service Control task with the provided schedule and services to start/stop. + + Args: + task_name (str): + The name of the task. + owner (str): + The task owner. + services (list): + A list containing the services and their type to be influenced by the specified action (start / stop). + + To get a list of all the available services and their corresponding IDs, call `get_task_config(task_id=-1, real_owner=your_username, type='service')`. + + E.g.: + [ + {'id': 'AudioStation', 'type': 'package'}, + {'id': 'HyperBackup', 'type': 'package'}, + {'id': 'Samba', 'type': 'service'} + ] + + action (str): + The action to apply to the services. Either `'start'` or `'stop'`. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'services': [], + 'action': action + } + + for service in services: + service_dict = { + 'enable': True, + 'id': service['id'], + 'type': service['type'] + } + extra['services'].append(service_dict) + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'create', + 'name': task_name, + 'real_owner': owner, + 'owner': owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra), + 'type': 'service' + } + return self.request_data(api_name, api_path, req_param) \ No newline at end of file From cf0040313f249e92fea9bbf8b5b4716aeeae5c63 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 20:41:54 +0200 Subject: [PATCH 08/19] Add recycle bin create method --- synology_api/task_scheduler.py | 123 +++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index b5cbbb5..bb583d6 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -721,4 +721,127 @@ def create_service_control_task( 'type': 'service' } + return self.request_data(api_name, api_path, req_param) + + def create_recycle_bin_task( + self, + task_name: str, + owner: str, + clean_all_shares: bool, + policy: dict, + shares: list[str] = [], + enable: bool = True, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Create a new Service Control task with the provided schedule and services to start/stop. + + Args: + task_name (str): + The name of the task. + owner (str): + The task owner. + clean_all_shares (bool): + Whether the task should empty the recycle bins of all shares or not, if set to `False`, shares must be specified. + shares (list, optional): + List of shares of which to clean the recycle bins. Pass only the name of the shares without slashes, e.g. `shares=['photo', 'web']`. Defaults to an empty list. + policy (dict): + Determines what files will be deleted from the recycle bins. Possible values are: + - {"policy": "clean_all"} -> Clean all files + - {"policy": "time", "time": int} -> Clean all files older than X days, days being possed as value for "time" key. + - {"policy": "size", "size": int , "sort_type": int} -> Clean files until recycle bin size reaches given "size" in MB, delete files by "sort_type", "0 -> Delete bigger files first", "1 -> Delete older files first". + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'clean_share_policy': { + 'clean_all': clean_all_shares + }, + 'clean_file_policy': policy + } + + if clean_all_shares == False: + extra['clean_share_policy']['shares'] = shares + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'create', + 'name': task_name, + 'real_owner': owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra), + 'type': 'recycle' + } + return self.request_data(api_name, api_path, req_param) \ No newline at end of file From 4cb9f0cada6f807b66f45cdec0b1151bd3085563 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Wed, 11 Sep 2024 21:49:52 +0200 Subject: [PATCH 09/19] Add docstrings for rest of methods --- synology_api/task_scheduler.py | 253 +++++++++++++++++++++++++++++---- 1 file changed, 222 insertions(+), 31 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index bb583d6..7fcc72f 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -80,7 +80,6 @@ class TaskScheduler(base_api.BaseApi): - Get all tasks - Get task information - Get task results - - Get task result log - Enable/Disable task - Run task - Delete task @@ -93,7 +92,22 @@ class TaskScheduler(base_api.BaseApi): """ def get_output_config(self) -> dict[str, object] | str: - """ + """Retrieve tasks output configuration. + + Returns: + dict|str: + A dictionary containing a list of the tasks and information related to them, or a string in case of an error. + + Example return: + { + "data": { + "enable_output": true, + "output_path": "share/scripts_output", + "type": "esynoscheduler", + "__docstring": [] # Ignore this field, it is to avoid the docstring format from breaking. + }, + "success": true + } """ api_name = 'SYNO.Core.EventScheduler' info = self.gen_list[api_name] @@ -194,9 +208,62 @@ def get_task_config( real_owner: str, type: str = '' ) -> dict[str, object] | str: + """Retrieve the configuration for a specific task or list of all the available services and their corresponding IDs. + + Args: + task_id (int): + The ID of the task to retrieve the configuration for. Pass `-1` to get a list of all available services with their IDs. + real_owner (str): + The owner of the task. From `get_task_list()`. + type (str, optional): + The type of task (e.g., 'service'). Pass "service" to get a list of all available services with their IDs. Defaults to an empty string. + + Returns: + dict|str: + A dictionary containing the task configuration or a string in case of an error. + + Example return: + { + "data": { + "action": "Run: echo hello > /tmp/awacate.out", + "can_edit_name": true, + "can_edit_owner": true, + "enable": true, + "extra": { + "notify_enable": false, + "notify_if_error": false, + "notify_mail": "", + "script": "echo hello > /tmp/awacate.out" + }, + "id": 11, + "name": "TEST_CRONTAB", + "owner": "root", + "real_owner": "root", + "schedule": { + "date": "2024/9/11", + "date_type": 0, + "hour": 0, + "last_work_hour": 0, + "minute": 0, + "monthly_week": [], + "repeat_date": 1001, + "repeat_hour": 0, + "repeat_hour_store_config": [ + 1..23 + ], + "repeat_min": 0, + "repeat_min_store_config": [ + 1, + ... + ], + "version": 4, + "week_day": "0,1,2,3,4,5,6" + }, + "type": "script" + }, + "success": true + } """ - """ - # pass id=-1 and type=service to get a list of all available services with their corresponding IDs api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] api_path = info['path'] @@ -216,7 +283,36 @@ def get_task_results( self, task_id: int ) -> dict[str, object] | str: - """ + """Retrieve the results list for a specific task. + + Args: + task_id (int): + The ID of the task to retrieve the results for. + + Returns: + dict|str: + A dictionary containing the task results or a string in case of an error. + + Example return: + { + "data": [ + { + "exit_code": 127, + "exit_type": "by_signal", + "start_time": "2024-09-11 00:00:01", + "stop_time": "2024-09-11 00:00:06", + "timestamp": "1726005601" + }, + { + "exit_code": 0, + "exit_type": "normal", + "start_time": "2024-06-01 00:00:01", + "stop_time": "2024-06-01 00:00:02", + "timestamp": "1717192801" + } + ], + "success": true + } """ api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] @@ -229,31 +325,66 @@ def get_task_results( return self.request_data(api_name, api_path, req_param) - def get_task_result_logs( - self, - task_id: int, - timestamp: int - ) -> dict[str, object] | str: - """ - """ - api_name = 'SYNO.Core.TaskScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': 'get_history_log', - 'timestamp': str(timestamp), - 'id': task_id - } - - return self.request_data(api_name, api_path, req_param) + ###### + # For some reason it keeps returning error 4800, in /var/log/synoscgi.log it logs: + # 2024-09-11T21:27:56+02:00 xxx synowebapi_SYNO.Core.TaskScheduler_1_get_history_log[21830]: main.cpp:392 Invalid paramters. + # + # Tried with many combination of params but could not make it work so far. + ###### + + # def get_task_result_logs( + # self, + # task_id: int, + # timestamp: int + # ) -> dict[str, object] | str: + # """Retrieve the log information for a specific task result. + + # Args: + # task_id (int): + # The ID of the task to retrieve the log for. + # timestamp (int): + # The timestamp of the result for which to retrieve the logs. + + # Returns: + # dict|str: + # A dictionary containing the log of the result, or a string in case of an error. + + # Example return: + # {success: true} + # """ + # api_name = 'SYNO.Core.TaskScheduler' + # info = self.gen_list[api_name] + # api_path = info['path'] + # req_param = { + # 'version': 1, + # 'method': 'get_history_log', + # 'timestamp': str(timestamp) + # 'id': task_id + # } + + # return self.request_data(api_name, api_path, req_param) def set_output_config( self, enable_output: bool, - output_path: str + output_path: str = '' ) -> dict[str, object] | str: - """ + """Configure the output settings for tasks results. + + Args: + enable_output (bool): + Whether to enable result logging. + output_path (str, optional): + The path where the result logs will be stored, e.g. `'share/scripts_output'`. Defaults to empty string. + + Returns: + dict|str: + A dictionary containing the result of the output configuration or a string in case of an error. + + Example return: + { + "success": true + } """ api_name = 'SYNO.Core.EventScheduler' info = self.gen_list[api_name] @@ -277,12 +408,27 @@ def task_enable( task_id: int, real_owner: str ) -> dict[str, object] | str: - """ + """Enable a specific task. + + Args: + task_id (int): + The ID of the task to be enabled. + real_owner (str): + The owner of the task. From `get_task_list()`. + + Returns: + dict|str: + A dictionary containing the result of the task enabling or a string in case of an error. + + Example return: + { + "success": true + } """ task_dict = { 'id': task_id, 'real_owner': real_owner, - 'enable': 'true' + 'enable': True } api_name = 'SYNO.Core.TaskScheduler' @@ -301,12 +447,27 @@ def task_disable( task_id: int, real_owner: str ) -> dict[str, object] | str: - """ + """Disable a specific task. + + Args: + task_id (int): + The ID of the task to be disabled. + real_owner (str): + The owner of the task. From `get_task_list()`. + + Returns: + dict|str: + A dictionary containing the result of the task disabling or a string in case of an error. + + Example return: + { + "success": true + } """ task_dict = { 'id': task_id, 'real_owner': real_owner, - 'enable': 'false' + 'enable': False } api_name = 'SYNO.Core.TaskScheduler' @@ -325,7 +486,22 @@ def task_run( task_id: int, real_owner: str ) -> dict[str, object] | str: - """ + """Run a specific task. + + Args: + task_id (int): + The ID of the task to be run. + real_owner (str): + The owner of the task. From `get_task_list()`. + + Returns: + dict|str: + A dictionary containing the result of the task execution or a string in case of an error. + + Example return: + { + "success": true + } """ task_dict = { 'id': task_id, @@ -348,7 +524,22 @@ def task_delete( task_id: int, real_owner: str ) -> dict[str, object] | str: - """ + """Delete a specific task. + + Args: + task_id (int): + The ID of the task to be deleted. + real_owner (str): + The owner of the task. From `get_task_list()`. + + Returns: + dict|str: + A dictionary containing the result of the task deletion or a string in case of an error. + + Example return: + { + "success": true + } """ task_dict = { 'id': task_id, From 0a51285a08c7fa0d5fc074dbf1540470a76292fe Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Thu, 12 Sep 2024 00:23:57 +0200 Subject: [PATCH 10/19] Remove test imports --- synology_api/task_scheduler.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 7fcc72f..a6fa493 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -1,9 +1,6 @@ from __future__ import annotations -import urllib.parse from . import base_api import json -import requests -import urllib class _Schedule(): def __init__( From 67da9333b6ed71112fb87920e1bb5fcb87bade0e Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Thu, 12 Sep 2024 21:05:49 +0200 Subject: [PATCH 11/19] Add modify task methods and fix Schedule interface --- synology_api/task_scheduler.py | 512 ++++++++++++++++++++++++++++++++- 1 file changed, 505 insertions(+), 7 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index a6fa493..c48b312 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -39,7 +39,7 @@ def _generate_dict(self) -> dict: } repeat_modality = -1 - if self.run_frequently: + if self.run_frequently == 1: if self.repeat == 'daily': repeat_modality = 1001 if self.repeat == 'weekly': @@ -211,7 +211,7 @@ def get_task_config( task_id (int): The ID of the task to retrieve the configuration for. Pass `-1` to get a list of all available services with their IDs. real_owner (str): - The owner of the task. From `get_task_list()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. type (str, optional): The type of task (e.g., 'service'). Pass "service" to get a list of all available services with their IDs. Defaults to an empty string. @@ -411,7 +411,7 @@ def task_enable( task_id (int): The ID of the task to be enabled. real_owner (str): - The owner of the task. From `get_task_list()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. Returns: dict|str: @@ -450,7 +450,7 @@ def task_disable( task_id (int): The ID of the task to be disabled. real_owner (str): - The owner of the task. From `get_task_list()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. Returns: dict|str: @@ -489,7 +489,7 @@ def task_run( task_id (int): The ID of the task to be run. real_owner (str): - The owner of the task. From `get_task_list()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. Returns: dict|str: @@ -527,7 +527,7 @@ def task_delete( task_id (int): The ID of the task to be deleted. real_owner (str): - The owner of the task. From `get_task_list()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. Returns: dict|str: @@ -672,6 +672,133 @@ def create_script_task( return self.request_data(api_name, api_path, req_param) + def modify_script_task( + self, + task_id: int, + task_name: str, + owner: str, + real_owner: str, + script: str, + enable: bool = True, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1, + notify_email: str = '', + notify_only_on_error: bool = False + ) -> dict[str, object] | str: + """Modify settings of a Script task. This method overwrites all the settings of the task, so if you only want to change one setting, + you can fetch the current task configuration with `get_task_config()` and pass all the settings to this method. + + Args: + task_id (int): + The ID of the task. + task_name (str): + The name of the task. + owner (str): + The task owner. + real_owner (str): + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. + script (str): + The script to be executed. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + notify_email (str, optional): + Email address to send notifications to. Defaults to an empty string, thus disabling the notification feature. + notify_only_on_error (bool, optional): + If `True`, notifications are only sent when an error occurs. Defaults to `False`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'notify_enable': 'true' if notify_email is not '' else 'false', + 'notify_mail': notify_email, + 'notify_if_error': 'true' if notify_only_on_error else 'false', + 'script': script + } + + print(json.dumps(schedule_dict)) + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'set', + 'id': task_id, + 'name': task_name, + 'real_owner': real_owner, + 'owner': owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra) + } + + return self.request_data(api_name, api_path, req_param) + def create_beep_control_task( self, task_name: str, @@ -780,6 +907,116 @@ def create_beep_control_task( return self.request_data(api_name, api_path, req_param) + def modify_beep_control_task( + self, + task_id: int, + task_name: str, + real_owner: str, + enable: bool = True, + beep_duration: int = 60, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Modify settings of a Beep Control task. This method overwrites all the settings of the task, so if you only want to change one setting, + you can fetch the current task configuration with `get_task_config()` and pass all the settings to this method. + + Args: + task_name (str): + The name of the task. + real_owner (str): + The task owner. + beep_duration (int, optional): + The amount of seconds the beep will be triggered for, in seconds. Defaults to `60`. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'beep_duration': str(beep_duration) + } + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'set', + 'id': task_id, + 'name': task_name, + 'real_owner': real_owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra) + } + + return self.request_data(api_name, api_path, req_param) + def create_service_control_task( self, task_name: str, @@ -911,6 +1148,140 @@ def create_service_control_task( return self.request_data(api_name, api_path, req_param) + def modify_service_control_task( + self, + task_id: int, + task_name: str, + real_owner: str, + services: list[dict], + action: str, + enable: bool = True, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Modify settings of a Service Control task. This method overwrites all the settings of the task, so if you only want to change one setting, + you can fetch the current task configuration with `get_task_config()` and pass all the settings to this method. + + Args: + task_id (int): + The ID of the task. + task_name (str): + The name of the task. + real_owner (str): + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. + services (list): + A list containing the services and their type to be influenced by the specified action (start / stop). + + To get a list of all the available services and their corresponding IDs, call `get_task_config(task_id=-1, real_owner=your_username, type='service')`. + + E.g.: + [ + {'id': 'AudioStation', 'type': 'package'}, + {'id': 'HyperBackup', 'type': 'package'}, + {'id': 'Samba', 'type': 'service'} + ] + + action (str): + The action to apply to the services. Either `'start'` or `'stop'`. + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'services': [], + 'action': action + } + + for service in services: + service_dict = { + 'enable': True, + 'id': service['id'], + 'type': service['type'] + } + extra['services'].append(service_dict) + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'set', + 'id': task_id, + 'name': task_name, + 'real_owner': real_owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra) + } + + return self.request_data(api_name, api_path, req_param) + def create_recycle_bin_task( self, task_name: str, @@ -930,7 +1301,7 @@ def create_recycle_bin_task( same_day_repeat_m: int = 0, same_day_repeat_until: int = -1 ) -> dict[str, object] | str: - """Create a new Service Control task with the provided schedule and services to start/stop. + """Create a new Recycle Bin Control task with the provided schedule and services to start/stop. Args: task_name (str): @@ -1032,4 +1403,131 @@ def create_recycle_bin_task( 'type': 'recycle' } + return self.request_data(api_name, api_path, req_param) + + def modify_recycle_bin_task( + self, + task_id: int, + task_name: str, + real_owner: str, + clean_all_shares: bool, + policy: dict, + shares: list[str] = [], + enable: bool = True, + run_frequently: bool = True, + run_days: str = '0,1,2,3,4,5,6', + run_date: str = '', + repeat: str = 'daily', + monthly_week: list[str] = [], + start_time_h: int = 0, + start_time_m: int = 0, + same_day_repeat_h: int = 0, + same_day_repeat_m: int = 0, + same_day_repeat_until: int = -1 + ) -> dict[str, object] | str: + """Modify settings of a Recycle Bin Control task. This method overwrites all the settings of the task, so if you only want to change one setting, + you can fetch the current task configuration with `get_task_config()` and pass all the settings to this method. + + Args: + task_id (int): + The ID of the task. + task_name (str): + The name of the task. + real_owner (str): + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. + clean_all_shares (bool): + Whether the task should empty the recycle bins of all shares or not, if set to `False`, shares must be specified. + shares (list, optional): + List of shares of which to clean the recycle bins. Pass only the name of the shares without slashes, e.g. `shares=['photo', 'web']`. Defaults to an empty list. + policy (dict): + Determines what files will be deleted from the recycle bins. Possible values are: + - {"policy": "clean_all"} -> Clean all files + - {"policy": "time", "time": int} -> Clean all files older than X days, days being possed as value for "time" key. + - {"policy": "size", "size": int , "sort_type": int} -> Clean files until recycle bin size reaches given "size" in MB, delete files by "sort_type", "0 -> Delete bigger files first", "1 -> Delete older files first". + enable (bool, optional): + Whether the task should be enabled upon creation. Defaults to `True`. + run_frequently (bool, optional): + Determines whether the task runs on a recurring schedule (True) or only on a specific date (False). Defaults to `True`. + run_days (str, optional): + Days of the week when the task should run, used if `run_frequently` is set to `True`, specified as a comma-separated list + (e.g., '0,1,2' for Sunday, Monday, Tuesday). Defaults to `'0,1,2,3,4,5,6'` (Daily). + run_date (str, optional): + The specific date the task should run, used if `run_frequently` is set to `False`. Format: `yyyy/m/d` (no prefix zeros). + Defaults to an empty string. + repeat (str, optional): + How often the task should repeat. Possible values: + - "daily" -> Only when 'run_frequently=True' + - "weekly" -> Only when 'run_frequently=True' + - "monthly" -> Works for both 'run_frequently=True' and 'run_frequently=False' + - "no_repeat" -> Only when 'run_frequently=False' + - "every_3_months" -> Only when 'run_frequently=False' + - "every_6_months" -> Only when 'run_frequently=False' + - "yearly" -> Only when 'run_frequently=False' + Defaults to 'daily'. + monthly_week (list[str], optional): + If `run_frequently=True` and `repeat='monthly'`, specifies the weeks the task should run, e.g., `['first', 'third']`. + Defaults to an empty list. + start_time_h (int, optional): + Hour at which the task should start. Defaults to `0`. + start_time_m (int, optional): + Minute at which the task should start. Defaults to `0`. + same_day_repeat_h (int, optional): + Number of hours between repeated executions on the same day (run every x hours), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Possible values: `0..23` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_m (int, optional): + Number of minutes between repeated executions on the same day (run every x minutes), if "Continue running within the same day" is desired. + Set to `0` to disable same-day repeats. Defaults to `0` (disable same day repeat). + + Posible values: `1`, `5`, `10`, `15`, `20`, `30` + + The args `same_day_repeat_h` and `same_day_repeat_m` cannot be used at the same time, if both are passed, `same_day_repeat_h` will be prioritized. + same_day_repeat_until (int, optional): + Last hour of the day when the task can repeat. Defaults to `start_time_h`. + + Returns: + dict|str: + A dictionary with the id of the created task, or a string if there is an error. + + Example return: + { + "data": { + "id": 20 + }, + "success": true + } + """ + + schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, + same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) + + schedule_dict = schedule._generate_dict() + + extra = { + 'clean_share_policy': { + 'clean_all': clean_all_shares + }, + 'clean_file_policy': policy + } + + if clean_all_shares == False: + extra['clean_share_policy']['shares'] = shares + + api_name = 'SYNO.Core.TaskScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 4, + 'method': 'set', + 'id': task_id, + 'name': task_name, + 'real_owner': real_owner, + 'enable': enable, + 'schedule': json.dumps(schedule_dict), + 'extra': json.dumps(extra) + } + return self.request_data(api_name, api_path, req_param) \ No newline at end of file From de93fc5bcca7824d12a0f7ea48ea1439b493371b Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 00:43:00 +0200 Subject: [PATCH 12/19] Add password confirm method to core_sys_info --- synology_api/core_sys_info.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/synology_api/core_sys_info.py b/synology_api/core_sys_info.py index 5ddf8b1..c54c817 100644 --- a/synology_api/core_sys_info.py +++ b/synology_api/core_sys_info.py @@ -794,6 +794,38 @@ def password_expiry(self) -> dict[str, object] | str: req_param = {'version': info['maxVersion'], 'method': 'get'} return self.request_data(api_name, api_path, req_param) + + def password_confirm(self, password: str) -> dict[str, object] | str: + """Issues a passowrd/session comparison to ensure the given password matches the auth of the current session. + + This is needed by some APIs as a confirmation method, for example, when creating/modifying a scheduled task with root permissions. + Please note that the password will be sent in plain text, just like in the base auth method. + + Args: + password (str): + The password with which the session was initiated. + + Returns: + dict|str: + A dictionary containing a `SynoConfirmPWToken`, or an error message. + + Example return: + { + "data": { + "SynoConfirmPWToken": "xxxxx" + }, + "success": true + } + """ + # There is a way to send the password encrypted, but could not figure out how the NAS wants the data to be encrypted. + # It wants an AES and RSA key sent to it under the field "__cIpHeRtExT", tried some ways of implementing it, + # but kept getting decryption errors in /var/log/synoscgi.log, so just went through with the plain text password. + api_name = 'SYNO.Core.User.PasswordConfirm' + info = self.core_list[api_name] + api_path = info['path'] + req_param = {'version': 2, 'method': 'auth', 'password': password} + + return self.request_data(api_name, api_path, req_param) def personal_photo_enable(self) -> dict[str, object] | str: api_name = 'SYNO.Core.User.Home' From 775cf96bcdb2af828a39bf150e181708ab50f579 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 01:31:43 +0200 Subject: [PATCH 13/19] Modify create to work with root privileges, expose secure field in auth.py --- synology_api/auth.py | 1 + synology_api/task_scheduler.py | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/synology_api/auth.py b/synology_api/auth.py index b3b42d2..388dc21 100644 --- a/synology_api/auth.py +++ b/synology_api/auth.py @@ -32,6 +32,7 @@ def __init__(self, self._port: str = port self._username: str = username self._password: str = password + self._secure: bool = secure self._sid: Optional[str] = None self._syno_token: Optional[str] = None self._session_expire: bool = True diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index c48b312..140a4b3 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Optional from . import base_api +from .core_sys_info import SysInfo import json class _Schedule(): @@ -86,7 +88,7 @@ class TaskScheduler(base_api.BaseApi): To implement in the future: - Add retention settings for Recycle bin task set/create methods. - """ + """ def get_output_config(self) -> dict[str, object] | str: """Retrieve tasks output configuration. @@ -573,13 +575,15 @@ def create_script_task( notify_email: str = '', notify_only_on_error: bool = False ) -> dict[str, object] | str: - """Create a new Script task with the provided schedule and notification settings. + """Create a new Script task with the provided schedule and notification settings. + + If the task needs to run with root privileges, please specify the owner as "root". Args: task_name (str): The name of the task. owner (str): - The task owner. + The task owner. If the task needs to run with root privileges, please specify the owner as "root". script (str): The script to be executed. enable (bool, optional): @@ -642,7 +646,6 @@ def create_script_task( "success": true } """ - schedule = _Schedule(run_frequently, run_days, run_date, repeat, monthly_week, start_time_h, start_time_m, same_day_repeat_h, same_day_repeat_m, same_day_repeat_until) @@ -654,6 +657,14 @@ def create_script_task( 'notify_if_error': 'true' if notify_only_on_error else 'false', 'script': script } + root_token = '' + if owner == 'root': + sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, + secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, + otp_code=self.session._otp_code, application=self.application) + response = sys_info.password_confirm(password=self.session._password) + if response['success']: + root_token = response['data']['SynoConfirmPWToken'] api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] @@ -670,6 +681,10 @@ def create_script_task( 'type': 'script' } + if root_token != '': + api_name = 'SYNO.Core.TaskScheduler.Root' + req_param['SynoConfirmPWToken'] = root_token + return self.request_data(api_name, api_path, req_param) def modify_script_task( From 44cce64dffbd61ccec7d09a53d8c9a98d87320c1 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 01:43:01 +0200 Subject: [PATCH 14/19] Add feat for root owner to modify script task --- synology_api/task_scheduler.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 140a4b3..8f259ea 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -711,15 +711,17 @@ def modify_script_task( """Modify settings of a Script task. This method overwrites all the settings of the task, so if you only want to change one setting, you can fetch the current task configuration with `get_task_config()` and pass all the settings to this method. + If the task needs to run with root privileges, please specify the owner as "root". + Args: task_id (int): The ID of the task. task_name (str): The name of the task. owner (str): - The task owner. + The task owner. If the task needs to run with root privileges, please specify the owner as "root". real_owner (str): - The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. + The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. script (str): The script to be executed. enable (bool, optional): @@ -795,7 +797,14 @@ def modify_script_task( 'script': script } - print(json.dumps(schedule_dict)) + root_token = '' + if owner == 'root': + sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, + secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, + otp_code=self.session._otp_code, application=self.application) + response = sys_info.password_confirm(password=self.session._password) + if response['success']: + root_token = response['data']['SynoConfirmPWToken'] api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] @@ -812,6 +821,10 @@ def modify_script_task( 'extra': json.dumps(extra) } + if root_token != '': + api_name = 'SYNO.Core.TaskScheduler.Root' + req_param['SynoConfirmPWToken'] = root_token + return self.request_data(api_name, api_path, req_param) def create_beep_control_task( From 142009f4cc81b789e1b5483609da58f2a449ef3b Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 02:54:47 +0200 Subject: [PATCH 15/19] Add Event Scheduler methods --- synology_api/task_scheduler.py | 194 ++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 4 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 8f259ea..17193fb 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -90,6 +90,16 @@ class TaskScheduler(base_api.BaseApi): - Add retention settings for Recycle bin task set/create methods. """ + def __get_root_token(self) -> str: + sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, + secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, + otp_code=self.session._otp_code, application=self.application) + response = sys_info.password_confirm(password=self.session._password) + if response['success']: + return response['data']['SynoConfirmPWToken'] + else: + return '' + def get_output_config(self) -> dict[str, object] | str: """Retrieve tasks output configuration. @@ -397,10 +407,6 @@ def set_output_config( } return self.request_data(api_name, api_path, req_param) - - def set_task_settings(self) -> dict[str, object] | str: - # TODO - print() def task_enable( self, @@ -480,6 +486,41 @@ def task_disable( return self.request_data(api_name, api_path, req_param) + def event_task_set_enable( + self, + task_name: str, + enable: bool + ) -> dict[str, object] | str: + """Enable or disable Event task. + + Args + task_name (str): + Name of the Event task to enable/disable. + enable (bool): + Wheter to enable (`True`) or disable (`False`) the task. + + Returns + dict|str: + A dictionary containing the result of the action or a string in case of an error. + + Example return: + { + "success": true + } + + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'set_enable', + 'enable': enable, + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + def task_run( self, task_id: int, @@ -518,6 +559,37 @@ def task_run( return self.request_data(api_name, api_path, req_param) + def event_task_run( + self, + task_name: str + ) -> dict[str, object] | str: + """Run a specific Event task. + + Args: + task_name (str): + Name of the Event task to run. + + Returns: + dict|str: + A dictionary containing the result of the task execution or a string in case of an error. + + Example return: + { + "success": true + } + """ + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'run', + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + def task_delete( self, task_id: int, @@ -556,6 +628,120 @@ def task_delete( return self.request_data(api_name, api_path, req_param) + def event_task_delete( + self, + task_name: str + ) -> dict[str, object] | str: + """Delete a specific Event task. + + Args: + task_name (str): + Name of the Event task to run. + + Returns: + dict|str: + A dictionary containing the result of the task deletion or a string in case of an error. + + Example return: + { + "success": true + } + """ + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'delete', + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + + def event_task_do( + self, + action: str, # create || set + task_name: str, + owner: dict, # {"1026": "joel"} // {"UID":"USERNAME"} + trigger_event: str, # "shutdown" || "bootup" + script: str, + depend_on_task: list[str] = [], # list of task names that will be run before this task + enable: bool = True, + notify_email: str = '', + notify_only_on_error: bool = False + ) -> dict[str, object] | str: + """Create or modify an event-based task. + + Args: + action (str): + Action to perform on the task. Possible values: + - "create" -> Creates a new task. + - "set" -> Modify an existing task. + task_name (str): + The name of the task. + owner (dict): + Dictionary containing the owner's ID and name (e.g., `{"1026": "user1"}`). + You can get the user UID by running `synouser --get your_user` in your NAS CLI. + + For root privileges, pass `{"0":"root"}`. + trigger_event (str): + The event that triggers the task. Possible values: + - "shutdown" + - "bootup" + script (str): + The script to be executed when the task is triggered. + depend_on_task (list[str], optional): + A list of event triggered task names that this task depends on (i.e., tasks that will be run before this one). Defaults to an empty list. + enable (bool, optional): + Whether to enable the task. Defaults to `True`. + notify_email (str, optional): + Email address to send notifications to. Defaults to an empty string. Defaults to an empty string, thus disabling the notification feature. + notify_only_on_error (bool, optional): + If `True`, notifications are only sent when an error occurs. Defaults to `False`. + + Returns: + dict|str: + A dictionary containing the result of the task creation or modification, or a strnig in case of an error. + + Example return: + { + "success": true + } + """ + if action != 'create' and action != 'set': + return {'error': f'action <{action}> is not valid.'} + if trigger_event != 'shutdown' and trigger_event != 'bootup': + return {'error': f'trigger_event <{trigger_event}> is not valid.'} + + pre_tasks = '' + for task in depend_on_task: # NAS expects "[Task Name 1][Task Name 2]" + pre_tasks += f'[{task}]' + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': action, + 'task_name': task_name, + 'owner': json.dumps(owner), + 'event': trigger_event, + 'depend_on_task': pre_tasks, + 'enable': enable, + 'notify_enable': notify_email != '', + 'notify_mail': f'"{notify_email}"', # Fails if not formatted with double quotes. + 'notify_if_error': notify_only_on_error, + 'operation': script, + 'operation_type': 'script' + } + + if owner['0'] == 'root': + api_name = 'SYNO.Core.EventScheduler.Root' + req_param['SynoConfirmPWToken'] = self.__get_root_token() + + return self.request_data(api_name, api_path, req_param) + def create_script_task( self, task_name: str, From a4391c3bcc8499814e3e83ceceb9ee93d3f1231e Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 03:11:02 +0200 Subject: [PATCH 16/19] Separate Event Scheduler methods into its own Class --- synology_api/auth.py | 7 +- synology_api/exceptions.py | 12 ++ synology_api/task_scheduler.py | 236 ++------------------------------- 3 files changed, 25 insertions(+), 230 deletions(-) diff --git a/synology_api/auth.py b/synology_api/auth.py index 388dc21..0fa689e 100644 --- a/synology_api/auth.py +++ b/synology_api/auth.py @@ -9,7 +9,7 @@ from .exceptions import SynoConnectionError, HTTPError, JSONDecodeError, LoginError, LogoutError, DownloadStationError from .exceptions import FileStationError, AudioStationError, ActiveBackupError, VirtualizationError, BackupError from .exceptions import CertificateError, CloudSyncError, DHCPServerError, DirectoryServerError, DockerError, DriveAdminError -from .exceptions import LogCenterError, NoteStationError, OAUTHError, PhotosError, SecurityAdvisorError, TaskSchedulerError +from .exceptions import LogCenterError, NoteStationError, OAUTHError, PhotosError, SecurityAdvisorError, TaskSchedulerError, EventSchedulerError from .exceptions import UniversalSearchError, USBCopyError, VPNError, CoreSysInfoError, UndefinedError USE_EXCEPTIONS: bool = True @@ -352,8 +352,11 @@ def request_data(self, elif api_name.find('SecurityAdvisor') > -1: raise SecurityAdvisorError(error_code=error_code) # Task Scheduler error: - elif api_name.find('SYNO.Core.TaskScheduler') or api_name.find('SYNO.Core.EventScheduler') > -1: + elif api_name.find('SYNO.Core.TaskScheduler') > -1: raise TaskSchedulerError(error_code=error_code) + # Event Scheduler error: + elif api_name.find('SYNO.Core.EventScheduler') > -1: + raise EventSchedulerError(error_code=error_code) # Universal search error: elif api_name.find('SYNO.Finder') > -1: raise UniversalSearchError(error_code=error_code) diff --git a/synology_api/exceptions.py b/synology_api/exceptions.py index cfdc61e..ca9e6ad 100644 --- a/synology_api/exceptions.py +++ b/synology_api/exceptions.py @@ -284,6 +284,18 @@ def __init__(self, error_code: int, *args: object) -> None: return +class EventSchedulerError(SynoBaseException): + """Class for an error during EventScheduler request. NOTE:... no docs on errors....""" + + def __init__(self, error_code: int, *args: object) -> None: + self.error_code = error_code + if error_code in error_codes.keys(): + super().__init__(error_message=error_codes[error_code], *args) + else: + super().__init__(error_message="EventScheduler Error: %i" % error_code, *args) + return + + class UniversalSearchError(SynoBaseException): """Class for an error during UniversalSearch request. NOTE:... no docs on errors....""" diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 17193fb..8be9cef 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -1,5 +1,4 @@ from __future__ import annotations -from typing import Optional from . import base_api from .core_sys_info import SysInfo import json @@ -85,9 +84,6 @@ class TaskScheduler(base_api.BaseApi): - Get/Set output path for task results - Create task - Set task settings - - To implement in the future: - - Add retention settings for Recycle bin task set/create methods. """ def __get_root_token(self) -> str: @@ -136,7 +132,7 @@ def get_task_list( offset: int = 0, limit: int = 50 ) -> dict[str, object] | str: - """List all present tasks. + """List all present scheduled tasks and event triggered tasks. Args: sort_by (str, optional): @@ -408,18 +404,21 @@ def set_output_config( return self.request_data(api_name, api_path, req_param) - def task_enable( + def task_set_enable( self, task_id: int, - real_owner: str + real_owner: str, + enable: bool ) -> dict[str, object] | str: - """Enable a specific task. + """Enable or disable a task. Args: task_id (int): The ID of the task to be enabled. real_owner (str): The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. + enable (bool): + Wheter to enable (`True`) or disable (`False`) the task. Returns: dict|str: @@ -433,46 +432,7 @@ def task_enable( task_dict = { 'id': task_id, 'real_owner': real_owner, - 'enable': True - } - - api_name = 'SYNO.Core.TaskScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 2, - 'method': 'set_enable', - 'status': f'[{json.dumps(task_dict)}]', - } - - return self.request_data(api_name, api_path, req_param) - - def task_disable( - self, - task_id: int, - real_owner: str - ) -> dict[str, object] | str: - """Disable a specific task. - - Args: - task_id (int): - The ID of the task to be disabled. - real_owner (str): - The task real owner, usually it is `root`, you can double check from the result of `get_task_config()`. - - Returns: - dict|str: - A dictionary containing the result of the task disabling or a string in case of an error. - - Example return: - { - "success": true - } - """ - task_dict = { - 'id': task_id, - 'real_owner': real_owner, - 'enable': False + 'enable': enable } api_name = 'SYNO.Core.TaskScheduler' @@ -486,41 +446,6 @@ def task_disable( return self.request_data(api_name, api_path, req_param) - def event_task_set_enable( - self, - task_name: str, - enable: bool - ) -> dict[str, object] | str: - """Enable or disable Event task. - - Args - task_name (str): - Name of the Event task to enable/disable. - enable (bool): - Wheter to enable (`True`) or disable (`False`) the task. - - Returns - dict|str: - A dictionary containing the result of the action or a string in case of an error. - - Example return: - { - "success": true - } - - """ - api_name = 'SYNO.Core.EventScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': 'set_enable', - 'enable': enable, - 'task_name': task_name - } - - return self.request_data(api_name, api_path, req_param) - def task_run( self, task_id: int, @@ -559,37 +484,6 @@ def task_run( return self.request_data(api_name, api_path, req_param) - def event_task_run( - self, - task_name: str - ) -> dict[str, object] | str: - """Run a specific Event task. - - Args: - task_name (str): - Name of the Event task to run. - - Returns: - dict|str: - A dictionary containing the result of the task execution or a string in case of an error. - - Example return: - { - "success": true - } - """ - - api_name = 'SYNO.Core.EventScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': 'run', - 'task_name': task_name - } - - return self.request_data(api_name, api_path, req_param) - def task_delete( self, task_id: int, @@ -628,120 +522,6 @@ def task_delete( return self.request_data(api_name, api_path, req_param) - def event_task_delete( - self, - task_name: str - ) -> dict[str, object] | str: - """Delete a specific Event task. - - Args: - task_name (str): - Name of the Event task to run. - - Returns: - dict|str: - A dictionary containing the result of the task deletion or a string in case of an error. - - Example return: - { - "success": true - } - """ - - api_name = 'SYNO.Core.EventScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': 'delete', - 'task_name': task_name - } - - return self.request_data(api_name, api_path, req_param) - - def event_task_do( - self, - action: str, # create || set - task_name: str, - owner: dict, # {"1026": "joel"} // {"UID":"USERNAME"} - trigger_event: str, # "shutdown" || "bootup" - script: str, - depend_on_task: list[str] = [], # list of task names that will be run before this task - enable: bool = True, - notify_email: str = '', - notify_only_on_error: bool = False - ) -> dict[str, object] | str: - """Create or modify an event-based task. - - Args: - action (str): - Action to perform on the task. Possible values: - - "create" -> Creates a new task. - - "set" -> Modify an existing task. - task_name (str): - The name of the task. - owner (dict): - Dictionary containing the owner's ID and name (e.g., `{"1026": "user1"}`). - You can get the user UID by running `synouser --get your_user` in your NAS CLI. - - For root privileges, pass `{"0":"root"}`. - trigger_event (str): - The event that triggers the task. Possible values: - - "shutdown" - - "bootup" - script (str): - The script to be executed when the task is triggered. - depend_on_task (list[str], optional): - A list of event triggered task names that this task depends on (i.e., tasks that will be run before this one). Defaults to an empty list. - enable (bool, optional): - Whether to enable the task. Defaults to `True`. - notify_email (str, optional): - Email address to send notifications to. Defaults to an empty string. Defaults to an empty string, thus disabling the notification feature. - notify_only_on_error (bool, optional): - If `True`, notifications are only sent when an error occurs. Defaults to `False`. - - Returns: - dict|str: - A dictionary containing the result of the task creation or modification, or a strnig in case of an error. - - Example return: - { - "success": true - } - """ - if action != 'create' and action != 'set': - return {'error': f'action <{action}> is not valid.'} - if trigger_event != 'shutdown' and trigger_event != 'bootup': - return {'error': f'trigger_event <{trigger_event}> is not valid.'} - - pre_tasks = '' - for task in depend_on_task: # NAS expects "[Task Name 1][Task Name 2]" - pre_tasks += f'[{task}]' - - api_name = 'SYNO.Core.EventScheduler' - info = self.gen_list[api_name] - api_path = info['path'] - req_param = { - 'version': 1, - 'method': action, - 'task_name': task_name, - 'owner': json.dumps(owner), - 'event': trigger_event, - 'depend_on_task': pre_tasks, - 'enable': enable, - 'notify_enable': notify_email != '', - 'notify_mail': f'"{notify_email}"', # Fails if not formatted with double quotes. - 'notify_if_error': notify_only_on_error, - 'operation': script, - 'operation_type': 'script' - } - - if owner['0'] == 'root': - api_name = 'SYNO.Core.EventScheduler.Root' - req_param['SynoConfirmPWToken'] = self.__get_root_token() - - return self.request_data(api_name, api_path, req_param) - def create_script_task( self, task_name: str, From 7fbedfedf2afb4252c5b9e08e1885646d085fcf1 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 03:29:52 +0200 Subject: [PATCH 17/19] Add more Event methods and simplify create/modify scheduled script task --- synology_api/task_scheduler.py | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 8be9cef..464ebb9 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -623,14 +623,6 @@ def create_script_task( 'notify_if_error': 'true' if notify_only_on_error else 'false', 'script': script } - root_token = '' - if owner == 'root': - sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, - secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, - otp_code=self.session._otp_code, application=self.application) - response = sys_info.password_confirm(password=self.session._password) - if response['success']: - root_token = response['data']['SynoConfirmPWToken'] api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] @@ -647,9 +639,9 @@ def create_script_task( 'type': 'script' } - if root_token != '': + if owner == 'root': api_name = 'SYNO.Core.TaskScheduler.Root' - req_param['SynoConfirmPWToken'] = root_token + req_param['SynoConfirmPWToken'] = self.__get_root_token() return self.request_data(api_name, api_path, req_param) @@ -763,15 +755,6 @@ def modify_script_task( 'script': script } - root_token = '' - if owner == 'root': - sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, - secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, - otp_code=self.session._otp_code, application=self.application) - response = sys_info.password_confirm(password=self.session._password) - if response['success']: - root_token = response['data']['SynoConfirmPWToken'] - api_name = 'SYNO.Core.TaskScheduler' info = self.gen_list[api_name] api_path = info['path'] @@ -787,9 +770,9 @@ def modify_script_task( 'extra': json.dumps(extra) } - if root_token != '': + if owner == 'root': api_name = 'SYNO.Core.TaskScheduler.Root' - req_param['SynoConfirmPWToken'] = root_token + req_param['SynoConfirmPWToken'] = self.__get_root_token() return self.request_data(api_name, api_path, req_param) From f40050c267d98499646040a74f45544623da85da Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 03:51:12 +0200 Subject: [PATCH 18/19] Add Event Scheduler and add new methods to README --- README.md | 40 +++++ synology_api/event_scheduler.py | 295 ++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+) create mode 100644 synology_api/event_scheduler.py diff --git a/README.md b/README.md index a856877..4efcb7c 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,9 @@ This wrapper cover the following APIs for now: | SYNO.Core.SecurityScan.Status | | SYNO.Core.SecurityScan.Status | | SYNO.Core.User | +| SYNO.Core.User.PasswordExpiry | +| SYNO.Core.User.PasswordPolicy | +| SYNO.Core.User.PasswordConfirm | | SYNO.Core.QuickConnect | | SYNO.Core.QuickConnect.Permission | | SYNO.Core.Network.Router.Topology | @@ -268,6 +271,13 @@ This wrapper cover the following APIs for now: |----------------| | SYNO.CloudSync | +| Task Scheduler / Event Scheduler | +|----------------------------------| +| SYNO.Core.TaskScheduler | +| SYNO.Core.TaskScheduler.Root | +| SYNO.Core.EventScheduler | +| SYNO.Core.EventScheduler.Root | + # ### Not all Surveillance Station functions works. @@ -525,6 +535,7 @@ DS info with below functions: | `users_info()` | | `password_policy()` | | `password_expiry()` | +| `password_confirm()` | | `personal_photo_enable()` | | `ftp_chroot_user()` | | `server_pair()` | @@ -726,6 +737,35 @@ DS info with below functions: | `connection_remove()` | Remove a specific connection. | | `task_remove()` | Remove a specific task. | +### task_scheduler +| Functions | Description | +|---------------------------------|----------------------------------------------------------------| +| `get_output_config()` | Retrieve the task output configuration. | +| `get_task_list()` | Retrieve list of Scheduled and Event tasks. | +| `get_task_config()` | Get Scheduled task settings. | +| `get_task_results()` | Get results of a Scheduled task. | +| `set_output_config()` | Set the task output configuration. | +| `task_set_enable()` | Enable or disable a Scheduled task. | +| `task_run()` | Run a Scheduled task. | +| `task_delete()` | Delete a Scheduled task. | +| `create_script_task()` | Create a "User defined script scheduled task". | +| `modify_script_task()` | Modify a "User defined script scheduled task". | +| `create_beep_control_task()` | Create a "Beep control scheduled task". | +| `modify_beep_control_task()` | Modify a "Beep control scheduled task". | +| `create_service_control_task()` | Create a "Service scheduled task". | +| `modify_service_control_task()` | Modify a "Service scheduled task". | +| `create_recycle_bin_task()` | Create a "Recycle Bin scheduled task". | +| `modify_recycle_bin_task()` | Modify a "Recycle Bin scheduled task". | + +### event_scheduler +| Functions | Description | +|--------------------------------|----------------------------------------------------------------| +| `get_task_results()` | Get results of a Event Triggered task. | +| `get_result_output()` | Get the output for a given result. | +| `task_set_enable()` | Enable or disable a Event Triggered task. | +| `task_run()` | Run a Event Triggered task. | +| `task_delete()` | Delete a Event Triggered task. | +| `task_create_or_set()` | Create or modify a Event Triggered task. | #### What's still missing diff --git a/synology_api/event_scheduler.py b/synology_api/event_scheduler.py new file mode 100644 index 0000000..b8bc4e2 --- /dev/null +++ b/synology_api/event_scheduler.py @@ -0,0 +1,295 @@ +from __future__ import annotations +from . import base_api +from .core_sys_info import SysInfo +import json + +class EventScheduler(base_api.BaseApi): + """ + Event Scheduler API implementation. + + This API provides functionality solely related to Event Tasks. For scheduled tasks, check `TaskScheduler`. + + For the moment, the available actions with the API are: + - Get task results + - Get result output + - Enable/Disable task + - Run task + - Delete task + - Create task + - Set task settings + """ + + def __get_root_token(self) -> str: + sys_info = SysInfo(ip_address=self.session._ip_address, port=self.session._port, username=self.session._username, password=self.session._password, + secure=self.session._secure, cert_verify=self.session._verify, dsm_version=self.session._version, debug=self.session._debug, + otp_code=self.session._otp_code, application=self.application) + response = sys_info.password_confirm(password=self.session._password) + if response['success']: + return response['data']['SynoConfirmPWToken'] + else: + return '' + + def get_task_results( + self, + task_name: str + ) -> dict[str, object] | str: + """Retrieve the results list for a specific task. + + Args: + task_name (str): + Name of the Event task to enable/disable. + + Returns: + dict|str: + A dictionary containing the task results or a string in case of an error. + + Example return: + { + "data": [ + { + "event_fire_time": "2024-09-13 03:17:47", + "exit_info": { + "exit_code": 0, + "exit_type": "stop" + }, + "extra": {}, + "pid": 16058, + "result_id": 115, + "run_time_env": {}, + "start_time": "2024-09-13 03:17:47", + "stop_time": "2024-09-13 03:17:47", + "task_name": "asd", + "trigger_event": "on_demand" + } + ], + "success": true + } + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'result_list', + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + + def get_result_output( + self, + task_name: str, + result_id: int + ) -> dict[str, object] | str: + """Retrieve the output for a given result. + + Args: + task_name (str): + Name of the Event task to enable/disable. + result_id (int): + ID of the result to retrieve. From `get_task_results()`. + + Returns: + dict|str: + A dictionary containing the result output or a string in case of an error. + + Example return: + { + "data": { + "script_in": "hello", + "script_out": "/volume3/datastore/scripts_output/asd/1726190267/script.log: line 1: hello: command not found\n" + }, + "success": true + } + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'result_get_file', + 'task_name': task_name, + 'result_id': result_id + } + + return self.request_data(api_name, api_path, req_param) + + def task_set_enable( + self, + task_name: str, + enable: bool + ) -> dict[str, object] | str: + """Enable or disable Event task. + + Args: + task_name (str): + Name of the Event task to enable/disable. + enable (bool): + Wheter to enable (`True`) or disable (`False`) the task. + + Returns: + dict|str: + A dictionary containing the result of the action or a string in case of an error. + + Example return: + { + "success": true + } + + """ + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'set_enable', + 'enable': enable, + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + + def task_run( + self, + task_name: str + ) -> dict[str, object] | str: + """Run a specific Event task. + + Args: + task_name (str): + Name of the Event task to run. + + Returns: + dict|str: + A dictionary containing the result of the task execution or a string in case of an error. + + Example return: + { + "success": true + } + """ + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'run', + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + + def task_delete( + self, + task_name: str + ) -> dict[str, object] | str: + """Delete a specific Event task. + + Args: + task_name (str): + Name of the Event task to run. + + Returns: + dict|str: + A dictionary containing the result of the task deletion or a string in case of an error. + + Example return: + { + "success": true + } + """ + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': 'delete', + 'task_name': task_name + } + + return self.request_data(api_name, api_path, req_param) + + def task_create_or_set( + self, + action: str, + task_name: str, + owner: dict, + trigger_event: str, + script: str, + depend_on_task: list[str] = [], + enable: bool = True, + notify_email: str = '', + notify_only_on_error: bool = False + ) -> dict[str, object] | str: + """Create or modify an event-based task. + + Args: + action (str): + Action to perform on the task. Possible values: + - "create" -> Creates a new task. + - "set" -> Modify an existing task. + task_name (str): + The name of the task. + owner (dict): + Dictionary containing the owner's ID and name (e.g., `{"1026": "user1"}`). + You can get the user UID by running `synouser --get your_user` in your NAS CLI. + + For root privileges, pass `{"0":"root"}`. + trigger_event (str): + The event that triggers the task. Possible values: + - "shutdown" + - "bootup" + script (str): + The script to be executed when the task is triggered. + depend_on_task (list[str], optional): + A list of event triggered task names that this task depends on (i.e., tasks that will be run before this one). Defaults to an empty list. + enable (bool, optional): + Whether to enable the task. Defaults to `True`. + notify_email (str, optional): + Email address to send notifications to. Defaults to an empty string. Defaults to an empty string, thus disabling the notification feature. + notify_only_on_error (bool, optional): + If `True`, notifications are only sent when an error occurs. Defaults to `False`. + + Returns: + dict|str: + A dictionary containing the result of the task creation or modification, or a strnig in case of an error. + + Example return: + { + "success": true + } + """ + if action != 'create' and action != 'set': + return {'error': f'action <{action}> is not valid.'} + if trigger_event != 'shutdown' and trigger_event != 'bootup': + return {'error': f'trigger_event <{trigger_event}> is not valid.'} + + pre_tasks = '' + for task in depend_on_task: # NAS expects "[Task Name 1][Task Name 2]" + pre_tasks += f'[{task}]' + + api_name = 'SYNO.Core.EventScheduler' + info = self.gen_list[api_name] + api_path = info['path'] + req_param = { + 'version': 1, + 'method': action, + 'task_name': task_name, + 'owner': json.dumps(owner), + 'event': trigger_event, + 'depend_on_task': pre_tasks, + 'enable': enable, + 'notify_enable': notify_email != '', + 'notify_mail': f'"{notify_email}"', # Fails if not formatted with double quotes. + 'notify_if_error': notify_only_on_error, + 'operation': script, + 'operation_type': 'script' + } + + if owner['0'] == 'root': + api_name = 'SYNO.Core.EventScheduler.Root' + req_param['SynoConfirmPWToken'] = self.__get_root_token() + + return self.request_data(api_name, api_path, req_param) \ No newline at end of file From d1be2d6ac1234af9ab506a1d4cdb7a8455029d35 Mon Sep 17 00:00:00 2001 From: joeperpetua Date: Fri, 13 Sep 2024 03:56:05 +0200 Subject: [PATCH 19/19] Remove unnecesary ternary --- synology_api/task_scheduler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/synology_api/task_scheduler.py b/synology_api/task_scheduler.py index 464ebb9..2560fb5 100644 --- a/synology_api/task_scheduler.py +++ b/synology_api/task_scheduler.py @@ -618,9 +618,9 @@ def create_script_task( schedule_dict = schedule._generate_dict() extra = { - 'notify_enable': 'true' if notify_email is not '' else 'false', + 'notify_enable': notify_email != '', 'notify_mail': notify_email, - 'notify_if_error': 'true' if notify_only_on_error else 'false', + 'notify_if_error': notify_only_on_error, 'script': script } @@ -749,9 +749,9 @@ def modify_script_task( schedule_dict = schedule._generate_dict() extra = { - 'notify_enable': 'true' if notify_email is not '' else 'false', + 'notify_enable': notify_email != '', 'notify_mail': notify_email, - 'notify_if_error': 'true' if notify_only_on_error else 'false', + 'notify_if_error': notify_only_on_error, 'script': script }