Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions App/Extensions/EventKit+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,30 @@ extension EKEventStatus {
}
}

extension EKEvent {
/// Whether the current user organizes/owns this event, as opposed to having
/// been invited to it by someone else.
///
/// An event with no organizer (e.g. a plain event you created, a birthday, or
/// a subscribed holiday) is treated as your own. An event that has an organizer
/// is yours only if that organizer is the current user; otherwise it was created
/// by someone else and shared/invited to you (e.g. a colleague's leave).
var isOrganizedByCurrentUser: Bool {
guard let organizer = organizer else { return true }
return organizer.isCurrentUser
}

/// Display name of the organizer, if the event was organized by someone else.
/// Returns `nil` for events you own or when no organizer information is available.
var organizerDisplayName: String? {
guard let organizer = organizer, !organizer.isCurrentUser else { return nil }
return organizer.name ?? organizer.url.absoluteString.replacingOccurrences(
of: "mailto:",
with: ""
)
}
}

extension EKRecurrenceFrequency {
init(_ string: String) {
switch string.lowercased() {
Expand Down
36 changes: 35 additions & 1 deletion App/Services/Calendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ final class CalendarService: Service {
),
"hasAlarms": .boolean(),
"isRecurring": .boolean(),
"organizer": .string(
description:
"Filter by who organized the event: 'me' for events you created or organize (including personal events with no other participants, birthdays, and subscribed holidays), 'others' for events someone else invited you to (e.g. a colleague's shared vacation/leave). Omit to include both.",
enum: ["me", "others"]
),
],
additionalProperties: false
),
Expand Down Expand Up @@ -216,7 +221,36 @@ final class CalendarService: Service {
events = events.filter { ($0.hasRecurrenceRules) == isRecurring }
}

return events.map { Event($0) }
if case .string(let organizer) = arguments["organizer"] {
switch organizer {
case "me":
events = events.filter { $0.isOrganizedByCurrentUser }
case "others":
events = events.filter { !$0.isOrganizedByCurrentUser }
default:
break
}
}

// Encode each event and annotate it with organizer information so callers
// can distinguish events the user owns from ones they were invited to.
let encoder = JSONEncoder()
encoder.userInfo[Ontology.DateTime.timeZoneOverrideKey] = TimeZone.current
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
let decoder = JSONDecoder()

return try events.map { ekEvent -> Value in
let data = try encoder.encode(Event(ekEvent))
var value = try decoder.decode(Value.self, from: data)
if case .object(var object) = value {
object["organizedByMe"] = .bool(ekEvent.isOrganizedByCurrentUser)
if let organizerName = ekEvent.organizerDisplayName {
object["organizer"] = .string(organizerName)
}
value = .object(object)
}
return value
}
}
Tool(
name: "events_create",
Expand Down