88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Delete Google Calendar events matching a regex pattern in summary or description.
|
|
|
|
Usage:
|
|
python gcal_delete_by_pattern.py --pattern "foo-bar.*" --credentials /path/to/sa.json [--year 2026] [--dry-run]
|
|
python gcal_delete_by_pattern.py --pattern "\\(custody-gen\\)" --credentials sa.json --field description
|
|
"""
|
|
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from datetime import date
|
|
|
|
from google.oauth2.service_account import Credentials
|
|
from googleapiclient.discovery import build
|
|
|
|
CALENDAR_ID = "ked0ao233h92ogj8l80b7e9a4g@group.calendar.google.com"
|
|
SCOPES = ["https://www.googleapis.com/auth/calendar"]
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Delete Google Calendar events matching a regex pattern"
|
|
)
|
|
parser.add_argument("--pattern", required=True, help="Regex pattern to match")
|
|
parser.add_argument("--credentials", required=True, help="Path to GCP service account JSON")
|
|
parser.add_argument("--year", type=int, default=date.today().year, help="Year to scan (default: current)")
|
|
parser.add_argument("--field", choices=["summary", "description", "both"], default="both",
|
|
help="Which field to match against (default: both)")
|
|
parser.add_argument("--dry-run", action="store_true", help="List matches without deleting")
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
regex = re.compile(args.pattern)
|
|
except re.error as e:
|
|
print(f"Invalid regex: {e}")
|
|
sys.exit(1)
|
|
|
|
creds = Credentials.from_service_account_file(args.credentials, scopes=SCOPES)
|
|
service = build("calendar", "v3", credentials=creds)
|
|
|
|
time_min = f"{args.year}-01-01T00:00:00Z"
|
|
time_max = f"{args.year + 1}-01-31T23:59:59Z"
|
|
|
|
matched = 0
|
|
page_token = None
|
|
|
|
while True:
|
|
result = service.events().list(
|
|
calendarId=CALENDAR_ID,
|
|
timeMin=time_min,
|
|
timeMax=time_max,
|
|
maxResults=250,
|
|
pageToken=page_token,
|
|
).execute()
|
|
|
|
for event in result.get("items", []):
|
|
summary = event.get("summary", "") or ""
|
|
description = event.get("description", "") or ""
|
|
|
|
hit = False
|
|
if args.field in ("summary", "both") and regex.search(summary):
|
|
hit = True
|
|
if args.field in ("description", "both") and regex.search(description):
|
|
hit = True
|
|
|
|
if hit:
|
|
matched += 1
|
|
start = event.get("start", {}).get("dateTime", event.get("start", {}).get("date", "?"))
|
|
label = f"{start} | {summary[:50]} | {description[:50]}"
|
|
if args.dry_run:
|
|
print(f" [MATCH] {label}")
|
|
else:
|
|
service.events().delete(calendarId=CALENDAR_ID, eventId=event["id"]).execute()
|
|
print(f" [DELETED] {label}")
|
|
|
|
page_token = result.get("nextPageToken")
|
|
if not page_token:
|
|
break
|
|
|
|
action = "matched" if args.dry_run else "deleted"
|
|
print(f"\nDone. {matched} events {action} for pattern '{args.pattern}' in {args.year}.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|