Personal noctalia plugins collection
1#!/usr/bin/env python3
2"""Update or delete a VEVENT 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_calendar_source(registry, calendar_uid):
18 source = registry.ref_source(calendar_uid)
19 if source and source.has_extension(EDataServer.SOURCE_EXTENSION_CALENDAR):
20 return source
21 for src in registry.list_sources(EDataServer.SOURCE_EXTENSION_CALENDAR):
22 if src.get_display_name() == calendar_uid or src.get_uid() == calendar_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 VEVENT item")
47 parser.add_argument("--calendar", required=True, help="Calendar source UID")
48 parser.add_argument("--uid", required=True, help="VEVENT UID")
49 parser.add_argument("--action", required=True, choices=["delete", "update"],
50 help="Action to perform")
51 parser.add_argument("--summary", help="New event summary")
52 parser.add_argument("--location", help="New event location")
53 parser.add_argument("--description", help="New event description")
54 parser.add_argument("--start", type=int, help="New start time (unix timestamp)")
55 parser.add_argument("--end", type=int, help="New end time (unix timestamp)")
56 args = parser.parse_args()
57
58 try:
59 registry = EDataServer.SourceRegistry.new_sync(None)
60 source = find_calendar_source(registry, args.calendar)
61 if not source:
62 print(json.dumps({"success": False, "error": f"Calendar not found: {args.calendar}"}))
63 sys.exit(1)
64
65 client = ECal.Client.connect_sync(
66 source, ECal.ClientSourceType.EVENTS, 1, None
67 )
68
69 if args.action == "delete":
70 client.remove_object_sync(args.uid, None, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None)
71 print(json.dumps({"success": True}))
72 return
73
74 # For update, fetch the existing component first
75 success, comp = client.get_object_sync(args.uid, None, None)
76 if not success or not comp:
77 print(json.dumps({"success": False, "error": "VEVENT not found"}))
78 sys.exit(1)
79
80 ical = comp.get_icalcomponent()
81
82 if args.summary is not None:
83 ical.set_summary(args.summary)
84
85 if args.location is not None:
86 ical.set_location(args.location)
87
88 if args.description is not None:
89 ical.set_description(args.description)
90
91 if args.start is not None:
92 ical.set_dtstart(make_ical_datetime(args.start))
93
94 if args.end is not None:
95 ical.set_dtend(make_ical_datetime(args.end))
96
97 client.modify_object_sync(comp, ECal.ObjModType.ALL, ECal.OperationFlags.NONE, None)
98 print(json.dumps({"success": True}))
99
100 except Exception as e:
101 print(json.dumps({"success": False, "error": str(e)}))
102 sys.exit(1)
103
104
105if __name__ == "__main__":
106 main()