Personal noctalia plugins collection
at main 133 lines 5.4 kB view raw
1#!/usr/bin/env python3 2"""Update or delete a VTODO item via Evolution Data Server.""" 3 4import argparse 5import json 6import os 7import sys 8from datetime import datetime, timezone 9 10import gi 11gi.require_version("ECal", "2.0") 12gi.require_version("EDataServer", "1.2") 13gi.require_version("ICalGLib", "3.0") 14from gi.repository import ECal, EDataServer, ICalGLib 15 16 17def find_task_source(registry, task_list_uid): 18 source = registry.ref_source(task_list_uid) 19 if source and source.has_extension(EDataServer.SOURCE_EXTENSION_TASK_LIST): 20 return source 21 for src in registry.list_sources(EDataServer.SOURCE_EXTENSION_TASK_LIST): 22 if src.get_display_name() == task_list_uid or src.get_uid() == task_list_uid: 23 return src 24 return None 25 26 27def make_ical_datetime(timestamp): 28 dt = datetime.fromtimestamp(timestamp, tz=timezone.utc).astimezone() 29 ical_time = ICalGLib.Time.new_null_time() 30 ical_time.set_date(dt.year, dt.month, dt.day) 31 ical_time.set_time(dt.hour, dt.minute, dt.second) 32 tz_name = os.path.realpath("/etc/localtime").split("/zoneinfo/")[1] 33 ical_time.set_timezone(ICalGLib.Timezone.get_builtin_timezone(tz_name)) 34 return ical_time 35 36 37def remove_property(comp, kind): 38 """Remove all properties of the given kind from a component.""" 39 prop = comp.get_first_property(kind) 40 while prop: 41 comp.remove_property(prop) 42 prop = comp.get_first_property(kind) 43 44 45def main(): 46 parser = argparse.ArgumentParser(description="Update/delete EDS VTODO item") 47 parser.add_argument("--task-list", required=True, help="Task list UID") 48 parser.add_argument("--uid", required=True, help="VTODO UID") 49 parser.add_argument("--action", required=True, choices=["complete", "uncomplete", "delete", "update"], 50 help="Action to perform") 51 parser.add_argument("--summary", help="New task summary (for update)") 52 parser.add_argument("--description", help="New task description (for update)") 53 parser.add_argument("--due", type=int, help="New due date as unix timestamp (for update)") 54 parser.add_argument("--priority", type=int, help="New priority 0-9 (for update)") 55 args = parser.parse_args() 56 57 try: 58 registry = EDataServer.SourceRegistry.new_sync(None) 59 source = find_task_source(registry, args.task_list) 60 if not source: 61 print(json.dumps({"success": False, "error": f"Task list not found: {args.task_list}"})) 62 sys.exit(1) 63 64 client = ECal.Client.connect_sync( 65 source, ECal.ClientSourceType.TASKS, 1, None 66 ) 67 68 if args.action == "delete": 69 client.remove_object_sync(args.uid, None, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None) 70 print(json.dumps({"success": True})) 71 return 72 73 # For complete/uncomplete, fetch the existing component first 74 success, comp = client.get_object_sync(args.uid, None, None) 75 if not success or not comp: 76 print(json.dumps({"success": False, "error": "VTODO not found"})) 77 sys.exit(1) 78 79 ical = comp.get_icalcomponent() 80 81 if args.action == "complete": 82 ical.set_status(ICalGLib.PropertyStatus.COMPLETED) 83 84 # Set PERCENT-COMPLETE to 100 85 remove_property(ical, ICalGLib.PropertyKind.PERCENTCOMPLETE_PROPERTY) 86 prop = ICalGLib.Property.new_percentcomplete(100) 87 ical.add_property(prop) 88 89 # Set COMPLETED timestamp 90 remove_property(ical, ICalGLib.PropertyKind.COMPLETED_PROPERTY) 91 now = datetime.now(timezone.utc) 92 completed_time = ICalGLib.Time.new_null_time() 93 completed_time.set_date(now.year, now.month, now.day) 94 completed_time.set_time(now.hour, now.minute, now.second) 95 completed_time.set_timezone(ICalGLib.Timezone.get_utc_timezone()) 96 prop = ICalGLib.Property.new_completed(completed_time) 97 ical.add_property(prop) 98 99 elif args.action == "uncomplete": 100 ical.set_status(ICalGLib.PropertyStatus.NEEDSACTION) 101 102 # Set PERCENT-COMPLETE to 0 103 remove_property(ical, ICalGLib.PropertyKind.PERCENTCOMPLETE_PROPERTY) 104 prop = ICalGLib.Property.new_percentcomplete(0) 105 ical.add_property(prop) 106 107 # Remove COMPLETED timestamp 108 remove_property(ical, ICalGLib.PropertyKind.COMPLETED_PROPERTY) 109 110 elif args.action == "update": 111 if args.summary is not None: 112 ical.set_summary(args.summary) 113 if args.description is not None: 114 ical.set_description(args.description) 115 if args.due is not None: 116 remove_property(ical, ICalGLib.PropertyKind.DUE_PROPERTY) 117 prop = ICalGLib.Property.new_due(make_ical_datetime(args.due)) 118 ical.add_property(prop) 119 if args.priority is not None: 120 remove_property(ical, ICalGLib.PropertyKind.PRIORITY_PROPERTY) 121 prop = ICalGLib.Property.new_priority(args.priority) 122 ical.add_property(prop) 123 124 client.modify_object_sync(comp, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None) 125 print(json.dumps({"success": True})) 126 127 except Exception as e: 128 print(json.dumps({"success": False, "error": str(e)})) 129 sys.exit(1) 130 131 132if __name__ == "__main__": 133 main()