| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | #!python3 | 
					
						
							| 
									
										
										
										
											2020-06-12 18:13:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  | Inkycal Todoist Module | 
					
						
							| 
									
										
										
										
											2020-06-12 18:13:14 +02:00
										 |  |  | Copyright by aceisace | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  | import arrow | 
					
						
							| 
									
										
										
										
											2020-06-12 18:13:14 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | from inkycal.modules.template import inkycal_module | 
					
						
							|  |  |  | from inkycal.custom import * | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  | from todoist_api_python.api import TodoistAPI | 
					
						
							| 
									
										
										
										
											2020-06-12 18:13:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 02:58:27 +02:00
										 |  |  | logger = logging.getLogger(__name__) | 
					
						
							| 
									
										
										
										
											2020-06-12 18:13:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | class Todoist(inkycal_module): | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     """Todoist api class
 | 
					
						
							| 
									
										
										
										
											2023-01-10 22:58:01 +01:00
										 |  |  |     parses todos from the todoist api. | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     name = "Todoist API - show your todos from todoist" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     requires = { | 
					
						
							|  |  |  |         'api_key': { | 
					
						
							|  |  |  |             "label": "Please enter your Todoist API-key", | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     optional = { | 
					
						
							|  |  |  |         'project_filter': { | 
					
						
							|  |  |  |             "label": "Show Todos only from following project (separated by a comma). Leave empty to show " + | 
					
						
							|  |  |  |                      "todos from all projects", | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     def __init__(self, config): | 
					
						
							|  |  |  |         """Initialize inkycal_rss module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         super().__init__(config) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config = config['config'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check if all required parameters are present | 
					
						
							|  |  |  |         for param in self.requires: | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |             if param not in config: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                 raise Exception(f'config is missing {param}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # module specific parameters | 
					
						
							|  |  |  |         self.api_key = config['api_key'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # if project filter is set, initialize it | 
					
						
							|  |  |  |         if config['project_filter'] and isinstance(config['project_filter'], str): | 
					
						
							|  |  |  |             self.project_filter = config['project_filter'].split(',') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.project_filter = config['project_filter'] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |         self._api = TodoistAPI(config['api_key']) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # give an OK message | 
					
						
							| 
									
										
										
										
											2022-10-03 02:58:27 +02:00
										 |  |  |         print(f'{__name__} loaded') | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def _validate(self): | 
					
						
							|  |  |  |         """Validate module-specific parameters""" | 
					
						
							|  |  |  |         if not isinstance(self.api_key, str): | 
					
						
							|  |  |  |             print('api_key has to be a string: "Yourtopsecretkey123" ') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def generate_image(self): | 
					
						
							|  |  |  |         """Generate image for this module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Define new image size with respect to padding | 
					
						
							|  |  |  |         im_width = int(self.width - (2 * self.padding_left)) | 
					
						
							|  |  |  |         im_height = int(self.height - (2 * self.padding_top)) | 
					
						
							|  |  |  |         im_size = im_width, im_height | 
					
						
							|  |  |  |         logger.info(f'Image size: {im_size}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create an image for black pixels and one for coloured pixels | 
					
						
							|  |  |  |         im_black = Image.new('RGB', size=im_size, color='white') | 
					
						
							|  |  |  |         im_colour = Image.new('RGB', size=im_size, color='white') | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # Check if internet is available | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |         if internet_available(): | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             logger.info('Connection test passed') | 
					
						
							| 
									
										
										
										
											2022-02-08 23:39:29 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |             raise NetworkNotReachableError | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Set some parameters for formatting todos | 
					
						
							|  |  |  |         line_spacing = 1 | 
					
						
							|  |  |  |         line_height = self.font.getsize('hg')[1] + line_spacing | 
					
						
							|  |  |  |         line_width = im_width | 
					
						
							|  |  |  |         max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate padding from top so the lines look centralised | 
					
						
							|  |  |  |         spacing_top = int(im_height % line_height / 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate line_positions | 
					
						
							|  |  |  |         line_positions = [ | 
					
						
							|  |  |  |             (0, spacing_top + _ * line_height) for _ in range(max_lines)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get all projects by name and id | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |         all_projects = self._api.get_projects() | 
					
						
							|  |  |  |         filtered_project_ids_and_names = {project.id: project.name for project in all_projects} | 
					
						
							|  |  |  |         all_active_tasks = self._api.get_tasks() | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         logger.debug(f"all_projects: {all_projects}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Filter entries in all_projects if filter was given | 
					
						
							|  |  |  |         if self.project_filter: | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |             filtered_projects = [project for project in all_projects if project.name in self.project_filter] | 
					
						
							|  |  |  |             filtered_project_ids_and_names = {project.id: project.name for project in filtered_projects} | 
					
						
							|  |  |  |             filtered_project_ids = [project for project in filtered_project_ids_and_names] | 
					
						
							|  |  |  |             logger.debug(f"filtered projects: {filtered_projects}") | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |             # If filter was activated and no project was found with that name, | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             # raise an exception to avoid showing a blank image | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |             if not filtered_projects: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                 logger.error('No project found from project filter!') | 
					
						
							|  |  |  |                 logger.error('Please double check spellings in project_filter') | 
					
						
							|  |  |  |                 raise Exception('No matching project found in filter. Please ' | 
					
						
							|  |  |  |                                 'double check spellings in project_filter or leave' | 
					
						
							|  |  |  |                                 'empty') | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |             # filtered version of all active tasks | 
					
						
							|  |  |  |             all_active_tasks = [task for task in all_active_tasks if task.project_id in filtered_project_ids] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Simplify the tasks for faster processing | 
					
						
							|  |  |  |         simplified = [ | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |                 'name': task.content, | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |                 'due': arrow.get(task.due.date, "YYYY-MM-DD").format("D-MMM-YY") if task.due else "", | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |                 'priority': task.priority, | 
					
						
							|  |  |  |                 'project': filtered_project_ids_and_names[task.project_id] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-10-03 01:03:24 +02:00
										 |  |  |             for task in all_active_tasks | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         logger.debug(f'simplified: {simplified}') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         project_lengths = [] | 
					
						
							|  |  |  |         due_lengths = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for task in simplified: | 
					
						
							|  |  |  |             if task["project"]: | 
					
						
							|  |  |  |                 project_lengths.append(int(self.font.getlength(task['project']) * 1.1)) | 
					
						
							|  |  |  |             if task["due"]: | 
					
						
							|  |  |  |                 due_lengths.append(int(self.font.getlength(task['due']) * 1.1)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # Get maximum width of project names for selected font | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         project_offset = int(max(project_lengths)) if project_lengths else 0 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Get maximum width of project dues for selected font | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         due_offset = int(max(due_lengths)) if due_lengths else 0 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         # create a dict with names of filtered groups | 
					
						
							|  |  |  |         groups = {group_name:[] for group_name in filtered_project_ids_and_names.values()} | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         for task in simplified: | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |             group_of_current_task = task["project"] | 
					
						
							|  |  |  |             if group_of_current_task in groups: | 
					
						
							|  |  |  |                 groups[group_of_current_task].append(task) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         logger.debug(f"grouped: {groups}") | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Add the parsed todos on the image | 
					
						
							|  |  |  |         cursor = 0 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |         for name, todos in groups.items(): | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             if todos: | 
					
						
							|  |  |  |                 for todo in todos: | 
					
						
							| 
									
										
										
										
											2022-10-04 23:56:43 +02:00
										 |  |  |                     if cursor < max_lines: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                         line_x, line_y = line_positions[cursor] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 22:06:28 +02:00
										 |  |  |                         if todo['project']: | 
					
						
							|  |  |  |                             # Add todos project name | 
					
						
							|  |  |  |                             write( | 
					
						
							|  |  |  |                                 im_colour, line_positions[cursor], | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |                                 (project_offset, line_height), | 
					
						
							| 
									
										
										
										
											2022-10-04 22:06:28 +02:00
										 |  |  |                                 todo['project'], font=self.font, alignment='left') | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |                         # Add todos due if not empty | 
					
						
							| 
									
										
										
										
											2022-10-04 22:06:28 +02:00
										 |  |  |                         if todo['due']: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                             write( | 
					
						
							|  |  |  |                                 im_black, | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |                                 (line_x + project_offset, line_y), | 
					
						
							|  |  |  |                                 (due_offset, line_height), | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                                 todo['due'], font=self.font, alignment='left') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 22:06:28 +02:00
										 |  |  |                         if todo['name']: | 
					
						
							|  |  |  |                             # Add todos name | 
					
						
							|  |  |  |                             write( | 
					
						
							|  |  |  |                                 im_black, | 
					
						
							| 
									
										
										
										
											2022-10-04 23:31:33 +02:00
										 |  |  |                                 (line_x + project_offset + due_offset, line_y), | 
					
						
							|  |  |  |                                 (im_width - project_offset - due_offset, line_height), | 
					
						
							| 
									
										
										
										
											2022-10-04 22:06:28 +02:00
										 |  |  |                                 todo['name'], font=self.font, alignment='left') | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                         cursor += 1 | 
					
						
							| 
									
										
										
										
											2022-10-04 23:56:43 +02:00
										 |  |  |                     else: | 
					
						
							|  |  |  |                         logger.error('More todos than available lines') | 
					
						
							|  |  |  |                         break | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # return the images ready for the display | 
					
						
							|  |  |  |         return im_black, im_colour | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2022-10-03 02:58:27 +02:00
										 |  |  |     print(f'running {__name__} in standalone/debug mode') |