mirror of
https://github.com/hanzoskill/mogcli.git
synced 2026-10-30 21:03:34 +00:00
598 lines
14 KiB
Go
598 lines
14 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/visionik/mogcli/internal/graph"
|
|
)
|
|
|
|
// CalendarCmd handles calendar operations.
|
|
type CalendarCmd struct {
|
|
List CalendarListCmd `cmd:"" help:"List events"`
|
|
Get CalendarGetCmd `cmd:"" help:"Get an event"`
|
|
Create CalendarCreateCmd `cmd:"" help:"Create an event"`
|
|
Update CalendarUpdateCmd `cmd:"" help:"Update an event"`
|
|
Delete CalendarDeleteCmd `cmd:"" help:"Delete an event"`
|
|
Calendars CalendarCalendarsCmd `cmd:"" help:"List calendars"`
|
|
Respond CalendarRespondCmd `cmd:"" help:"Respond to an event invitation"`
|
|
FreeBusy CalendarFreeBusyCmd `cmd:"" help:"Get free/busy information"`
|
|
ACL CalendarACLCmd `cmd:"" help:"List calendar permissions"`
|
|
}
|
|
|
|
// CalendarListCmd lists events.
|
|
type CalendarListCmd struct {
|
|
Calendar string `help:"Calendar ID (default: primary)"`
|
|
From string `help:"Start date (ISO format)" default:""`
|
|
To string `help:"End date (ISO format)" default:""`
|
|
Max int `help:"Maximum events" default:"25"`
|
|
}
|
|
|
|
// Run executes calendar list.
|
|
func (c *CalendarListCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Default to today through +30 days
|
|
from := time.Now()
|
|
to := from.AddDate(0, 0, 30)
|
|
|
|
if c.From != "" {
|
|
from, err = time.Parse("2006-01-02", c.From)
|
|
if err != nil {
|
|
from, err = time.Parse(time.RFC3339, c.From)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid --from date: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.To != "" {
|
|
to, err = time.Parse("2006-01-02", c.To)
|
|
if err != nil {
|
|
to, err = time.Parse(time.RFC3339, c.To)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid --to date: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
query := url.Values{}
|
|
query.Set("$top", fmt.Sprintf("%d", c.Max))
|
|
query.Set("$orderby", "start/dateTime")
|
|
query.Set("startDateTime", from.Format(time.RFC3339))
|
|
query.Set("endDateTime", to.Format(time.RFC3339))
|
|
|
|
path := "/me/calendarView"
|
|
if c.Calendar != "" {
|
|
path = fmt.Sprintf("/me/calendars/%s/calendarView", graph.ResolveID(c.Calendar))
|
|
}
|
|
|
|
data, err := client.Get(ctx, path, query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var resp struct {
|
|
Value []Event `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(resp.Value)
|
|
}
|
|
|
|
if len(resp.Value) == 0 {
|
|
fmt.Println("No events found")
|
|
return nil
|
|
}
|
|
|
|
for _, event := range resp.Value {
|
|
printEvent(event, root.Verbose)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CalendarGetCmd gets an event.
|
|
type CalendarGetCmd struct {
|
|
ID string `arg:"" help:"Event ID"`
|
|
}
|
|
|
|
// Run executes calendar get.
|
|
func (c *CalendarGetCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/events/%s", graph.ResolveID(c.ID))
|
|
|
|
data, err := client.Get(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var event Event
|
|
if err := json.Unmarshal(data, &event); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(event)
|
|
}
|
|
|
|
printEventDetail(event, root.Verbose)
|
|
return nil
|
|
}
|
|
|
|
// CalendarCreateCmd creates an event.
|
|
type CalendarCreateCmd struct {
|
|
Summary string `help:"Event title/summary" required:""`
|
|
From string `help:"Start time (ISO format)" required:""`
|
|
To string `help:"End time (ISO format)" required:""`
|
|
Location string `help:"Location"`
|
|
Description string `help:"Event description" name:"description"`
|
|
Attendees []string `help:"Attendee email addresses"`
|
|
AllDay bool `help:"All-day event" name:"all-day"`
|
|
Calendar string `help:"Calendar ID"`
|
|
}
|
|
|
|
// Run executes calendar create.
|
|
func (c *CalendarCreateCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
event := map[string]interface{}{
|
|
"subject": c.Summary,
|
|
"start": map[string]string{
|
|
"dateTime": c.From,
|
|
"timeZone": "UTC",
|
|
},
|
|
"end": map[string]string{
|
|
"dateTime": c.To,
|
|
"timeZone": "UTC",
|
|
},
|
|
}
|
|
|
|
if c.Location != "" {
|
|
event["location"] = map[string]string{"displayName": c.Location}
|
|
}
|
|
|
|
if c.Description != "" {
|
|
event["body"] = map[string]string{
|
|
"contentType": "text",
|
|
"content": c.Description,
|
|
}
|
|
}
|
|
|
|
if len(c.Attendees) > 0 {
|
|
var attendees []map[string]interface{}
|
|
for _, email := range c.Attendees {
|
|
attendees = append(attendees, map[string]interface{}{
|
|
"emailAddress": map[string]string{"address": email},
|
|
"type": "required",
|
|
})
|
|
}
|
|
event["attendees"] = attendees
|
|
}
|
|
|
|
if c.AllDay {
|
|
event["isAllDay"] = true
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := "/me/events"
|
|
if c.Calendar != "" {
|
|
path = fmt.Sprintf("/me/calendars/%s/events", graph.ResolveID(c.Calendar))
|
|
}
|
|
|
|
data, err := client.Post(ctx, path, event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var created Event
|
|
if err := json.Unmarshal(data, &created); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(created)
|
|
}
|
|
|
|
fmt.Printf("✓ Event created: %s (%s)\n", created.Subject, graph.FormatID(created.ID))
|
|
return nil
|
|
}
|
|
|
|
// CalendarUpdateCmd updates an event.
|
|
type CalendarUpdateCmd struct {
|
|
ID string `arg:"" help:"Event ID"`
|
|
Summary string `help:"New title/summary"`
|
|
From string `help:"New start time"`
|
|
To string `help:"New end time"`
|
|
Location string `help:"New location"`
|
|
Description string `help:"New description" name:"description"`
|
|
}
|
|
|
|
// Run executes calendar update.
|
|
func (c *CalendarUpdateCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updates := make(map[string]interface{})
|
|
|
|
if c.Summary != "" {
|
|
updates["subject"] = c.Summary
|
|
}
|
|
if c.From != "" {
|
|
updates["start"] = map[string]string{"dateTime": c.From, "timeZone": "UTC"}
|
|
}
|
|
if c.To != "" {
|
|
updates["end"] = map[string]string{"dateTime": c.To, "timeZone": "UTC"}
|
|
}
|
|
if c.Location != "" {
|
|
updates["location"] = map[string]string{"displayName": c.Location}
|
|
}
|
|
if c.Description != "" {
|
|
updates["body"] = map[string]string{"contentType": "text", "content": c.Description}
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
return fmt.Errorf("no updates specified")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/events/%s", graph.ResolveID(c.ID))
|
|
|
|
_, err = client.Patch(ctx, path, updates)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("✓ Event updated")
|
|
return nil
|
|
}
|
|
|
|
// CalendarDeleteCmd deletes an event.
|
|
type CalendarDeleteCmd struct {
|
|
ID string `arg:"" help:"Event ID"`
|
|
}
|
|
|
|
// Run executes calendar delete.
|
|
func (c *CalendarDeleteCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/events/%s", graph.ResolveID(c.ID))
|
|
|
|
if err := client.Delete(ctx, path); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("✓ Event deleted")
|
|
return nil
|
|
}
|
|
|
|
// CalendarCalendarsCmd lists calendars.
|
|
type CalendarCalendarsCmd struct{}
|
|
|
|
// Run executes calendars list.
|
|
func (c *CalendarCalendarsCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
data, err := client.Get(ctx, "/me/calendars", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var resp struct {
|
|
Value []Calendar `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(resp.Value)
|
|
}
|
|
|
|
for _, cal := range resp.Value {
|
|
marker := " "
|
|
if cal.IsDefaultCalendar {
|
|
marker = "*"
|
|
}
|
|
fmt.Printf("%s %-30s %s\n", marker, cal.Name, graph.FormatID(cal.ID))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CalendarRespondCmd responds to an event invitation.
|
|
type CalendarRespondCmd struct {
|
|
ID string `arg:"" help:"Event ID"`
|
|
Response string `arg:"" help:"Response: accept, decline, tentative"`
|
|
Comment string `help:"Optional comment"`
|
|
}
|
|
|
|
// Run executes calendar respond.
|
|
func (c *CalendarRespondCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var action string
|
|
switch c.Response {
|
|
case "accept":
|
|
action = "accept"
|
|
case "decline":
|
|
action = "decline"
|
|
case "tentative":
|
|
action = "tentativelyAccept"
|
|
default:
|
|
return fmt.Errorf("invalid response: %s (use accept, decline, or tentative)", c.Response)
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"sendResponse": true,
|
|
}
|
|
if c.Comment != "" {
|
|
body["comment"] = c.Comment
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := fmt.Sprintf("/me/events/%s/%s", graph.ResolveID(c.ID), action)
|
|
|
|
_, err = client.Post(ctx, path, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("✓ Responded: %s\n", c.Response)
|
|
return nil
|
|
}
|
|
|
|
// CalendarFreeBusyCmd gets free/busy information.
|
|
type CalendarFreeBusyCmd struct {
|
|
Emails []string `arg:"" help:"Email addresses to check"`
|
|
Start string `help:"Start time (ISO format)" required:""`
|
|
End string `help:"End time (ISO format)" required:""`
|
|
}
|
|
|
|
// CalendarACLCmd lists calendar permissions.
|
|
type CalendarACLCmd struct {
|
|
Calendar string `arg:"" optional:"" help:"Calendar ID (default: primary)"`
|
|
}
|
|
|
|
// Run executes calendar freebusy.
|
|
func (c *CalendarFreeBusyCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var schedules []string
|
|
for _, email := range c.Emails {
|
|
schedules = append(schedules, email)
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"schedules": schedules,
|
|
"startTime": map[string]string{"dateTime": c.Start, "timeZone": "UTC"},
|
|
"endTime": map[string]string{"dateTime": c.End, "timeZone": "UTC"},
|
|
"availabilityViewInterval": 30,
|
|
}
|
|
|
|
ctx := context.Background()
|
|
data, err := client.Post(ctx, "/me/calendar/getSchedule", body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
var resp interface{}
|
|
json.Unmarshal(data, &resp)
|
|
return outputJSON(resp)
|
|
}
|
|
|
|
fmt.Println(string(data))
|
|
return nil
|
|
}
|
|
|
|
// Event represents a calendar event.
|
|
type Event struct {
|
|
ID string `json:"id"`
|
|
Subject string `json:"subject"`
|
|
Start *Time `json:"start"`
|
|
End *Time `json:"end"`
|
|
Location *Loc `json:"location"`
|
|
Body *Body `json:"body"`
|
|
IsAllDay bool `json:"isAllDay"`
|
|
Organizer *Org `json:"organizer"`
|
|
}
|
|
|
|
// Time represents a datetime with timezone.
|
|
type Time struct {
|
|
DateTime string `json:"dateTime"`
|
|
TimeZone string `json:"timeZone"`
|
|
}
|
|
|
|
// Loc represents a location.
|
|
type Loc struct {
|
|
DisplayName string `json:"displayName"`
|
|
}
|
|
|
|
// Body represents event body.
|
|
type Body struct {
|
|
ContentType string `json:"contentType"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
// Org represents an organizer.
|
|
type Org struct {
|
|
EmailAddress struct {
|
|
Name string `json:"name"`
|
|
Address string `json:"address"`
|
|
} `json:"emailAddress"`
|
|
}
|
|
|
|
// Calendar represents a calendar.
|
|
type Calendar struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
IsDefaultCalendar bool `json:"isDefaultCalendar"`
|
|
}
|
|
|
|
// CalendarPermission represents a calendar permission (ACL entry).
|
|
type CalendarPermission struct {
|
|
ID string `json:"id"`
|
|
Role string `json:"role"`
|
|
AllowedRoles []string `json:"allowedRoles"`
|
|
EmailAddress *EmailAddress `json:"emailAddress"`
|
|
IsRemovable bool `json:"isRemovable"`
|
|
IsInsideOrganization bool `json:"isInsideOrganization"`
|
|
}
|
|
|
|
// EmailAddress represents an email address in a permission.
|
|
type EmailAddress struct {
|
|
Name string `json:"name"`
|
|
Address string `json:"address"`
|
|
}
|
|
|
|
// Run executes calendar acl.
|
|
func (c *CalendarACLCmd) Run(root *Root) error {
|
|
client, err := root.GetClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx := context.Background()
|
|
path := "/me/calendar/calendarPermissions"
|
|
if c.Calendar != "" {
|
|
path = fmt.Sprintf("/me/calendars/%s/calendarPermissions", graph.ResolveID(c.Calendar))
|
|
}
|
|
|
|
data, err := client.Get(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var resp struct {
|
|
Value []CalendarPermission `json:"value"`
|
|
}
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
if root.JSON {
|
|
return outputJSON(resp.Value)
|
|
}
|
|
|
|
if len(resp.Value) == 0 {
|
|
fmt.Println("No permissions found")
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Calendar Permissions")
|
|
fmt.Println()
|
|
for _, perm := range resp.Value {
|
|
email := "(no email)"
|
|
name := ""
|
|
if perm.EmailAddress != nil {
|
|
if perm.EmailAddress.Address != "" {
|
|
email = perm.EmailAddress.Address
|
|
}
|
|
if perm.EmailAddress.Name != "" {
|
|
name = perm.EmailAddress.Name
|
|
}
|
|
}
|
|
removable := ""
|
|
if !perm.IsRemovable {
|
|
removable = " (locked)"
|
|
}
|
|
if name != "" {
|
|
fmt.Printf("%-12s %s <%s>%s\n", perm.Role, name, email, removable)
|
|
} else {
|
|
fmt.Printf("%-12s %s%s\n", perm.Role, email, removable)
|
|
}
|
|
if root.Verbose {
|
|
fmt.Printf(" ID: %s\n", perm.ID)
|
|
}
|
|
}
|
|
fmt.Printf("\n%d permission(s)\n", len(resp.Value))
|
|
return nil
|
|
}
|
|
|
|
func printEvent(event Event, verbose bool) {
|
|
start := ""
|
|
if event.Start != nil {
|
|
t, _ := time.Parse("2006-01-02T15:04:05.0000000", event.Start.DateTime)
|
|
if event.IsAllDay {
|
|
start = t.Format("Jan 2")
|
|
} else {
|
|
start = t.Format("Jan 2 15:04")
|
|
}
|
|
}
|
|
|
|
location := ""
|
|
if event.Location != nil && event.Location.DisplayName != "" {
|
|
location = fmt.Sprintf(" @ %s", event.Location.DisplayName)
|
|
}
|
|
|
|
fmt.Printf("%-16s %s%s\n", start, event.Subject, location)
|
|
fmt.Printf(" ID: %s\n", graph.FormatID(event.ID))
|
|
if verbose {
|
|
fmt.Printf(" Full: %s\n", event.ID)
|
|
}
|
|
}
|
|
|
|
func printEventDetail(event Event, verbose bool) {
|
|
fmt.Printf("ID: %s\n", graph.FormatID(event.ID))
|
|
if verbose {
|
|
fmt.Printf("Full ID: %s\n", event.ID)
|
|
}
|
|
fmt.Printf("Subject: %s\n", event.Subject)
|
|
|
|
if event.Start != nil {
|
|
fmt.Printf("Start: %s\n", event.Start.DateTime)
|
|
}
|
|
if event.End != nil {
|
|
fmt.Printf("End: %s\n", event.End.DateTime)
|
|
}
|
|
if event.Location != nil && event.Location.DisplayName != "" {
|
|
fmt.Printf("Location: %s\n", event.Location.DisplayName)
|
|
}
|
|
if event.Organizer != nil {
|
|
fmt.Printf("Organizer: %s <%s>\n",
|
|
event.Organizer.EmailAddress.Name,
|
|
event.Organizer.EmailAddress.Address)
|
|
}
|
|
if event.Body != nil && event.Body.Content != "" {
|
|
content := event.Body.Content
|
|
if event.Body.ContentType == "html" {
|
|
content = stripHTML(content)
|
|
}
|
|
fmt.Printf("\n%s\n", content)
|
|
}
|
|
}
|