Codebase list gcalcli / d6aa68fb-77c3-4d1e-8934-fbb6ae9827c8/main
New upstream snapshot. Debian Janitor 2 years ago
36 changed file(s) with 927 addition(s) and 4182 deletion(s). Raw diff Collapse all Expand all
+0
-11
.coveragerc less more
0 [report]
1 show_missing = True
2 exclude_lines=
3 pragma: no cover
4 pragma: no py${PYTEST_PYMAJVER} cover
5 omit =
6 *__init__.py
7 [paths]
8 source =
9 gcalcli/
10 .tox/py*/lib/python*/site-packages/gcalcli/
+0
-14
.gitignore less more
0 __pycache__/
1 .pytest_cache/
2 build/
3 dist/
4 venv/
5 htmlcov/
6
7 gcalcli.egg-info
8
9 .coverage
10 .pytest_cache?
11 .tox
12
13 *.pyc
+0
-8
.travis.yml less more
0 sudo: false
1 language: python
2 python:
3 - "3.5"
4 - "3.6"
5 - "3.7"
6 install: pip install tox-travis
7 script: tox
+0
-21
LICENSE less more
0 MIT License
1
2 Copyright (c) 2019 Eric Davis, Brian Hartvigsen, Joshua Crowgey
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
0 Metadata-Version: 2.1
1 Name: gcalcli
2 Version: 4.3.0
3 Summary: Google Calendar Command Line Interface
4 Home-page: https://github.com/insanum/gcalcli
5 Author: Eric Davis, Brian Hartvigsen, Joshua Crowgey
6 Author-email: edavis@insanum.com, brian.andrew@brianandjenny.com, jcrowgey@uw.edu
7 Maintainer: Joshua Crowgey
8 Maintainer-email: jcrowgey@uw.edu
9 License: MIT
10 Description: UNKNOWN
11 Platform: UNKNOWN
12 Classifier: Development Status :: 5 - Production/Stable
13 Classifier: Environment :: Console
14 Classifier: Intended Audience :: End Users/Desktop
15 Classifier: License :: OSI Approved :: MIT License
16 Classifier: Programming Language :: Python :: 3
17 Requires-Python: >=3
18 Provides-Extra: vobject
+0
-1
README.md less more
0 docs/README.md
0 gcalcli
1 =======
2 [![Build Status](https://travis-ci.org/insanum/gcalcli.svg?branch=master)](https://travis-ci.org/insanum/gcalcli)
3
4 #### Google Calendar Command Line Interface
5
6 gcalcli is a Python application that allows you to access your Google
7 Calendar(s) from a command line. It's easy to get your agenda, search for
8 events, add new events, delete events, edit events, see recently updated
9 events, and even import those annoying ICS/vCal invites from Microsoft
10 Exchange and/or other sources. Additionally, gcalcli can be used as a reminder
11 service and execute any application you want when an event is coming up.
12
13 gcalcli uses the [Google Calendar API version 3](https://developers.google.com/google-apps/calendar/).
14
15 Requirements
16 ------------
17
18 * [Python3](http://www.python.org)
19 * [dateutil](http://www.labix.org/python-dateutil)
20 * [Google API Client](https://developers.google.com/api-client-library/python)
21 * [httplib2](https://github.com/httplib2/httplib2)
22 * [oauth2client](https://github.com/google/oauth2client)
23 * [parsedatetime](https://github.com/bear/parsedatetime)
24 * A love for the command line!
25
26 ### Optional packages
27
28 * [vobject](http://vobject.skyhouseconsulting.com) Python module
29 Used for ics/vcal importing.
30
31 Installation
32 ------------
33
34 Check your OS distribution for packages.
35
36 ### Debian/Ubuntu
37
38 ```sh
39 apt-get install gcalcli
40 ```
41
42 ### Install using [Nix](https://nixos.org/nix/)
43
44 ```sh
45 nix-env -i gcalcli
46 ```
47
48 ### Install using [Homebrew](https://brew.sh/) (MacOS)
49
50 ```sh
51 brew install gcalcli
52 ```
53
54
55 ### Install from PyPI
56
57 ```sh
58 pip install gcalcli
59 ```
60
61 ### Install from source
62
63 ```sh
64 git clone https://github.com/insanum/gcalcli.git
65 cd gcalcli
66 python setup.py install
67 ```
68
69 ### Install optional package
70
71 ```sh
72 pip install vobject
73 ```
74
75 Features
76 --------
77
78 * OAuth2 authention with your Google account
79 * list your calendars
80 * show an agenda using a specified start/end date and time
81 * show updates since a specified datetime for events between a start/end date and time
82 * find conflicts between events matching search term
83 * ascii text graphical calendar display with variable width
84 * search for past and/or future events
85 * "quick add" new events to a specified calendar
86 * "add" a new event to a specified calendar (interactively or automatically)
87 * "delete" event(s) from a calendar(s) (interactively or automatically)
88 * "edit" event(s) interactively
89 * import events from ICS/vCal files to a specified calendar
90 * easy integration with your favorite mail client (attachment handler)
91 * run as a cron job and execute a command for reminders
92 * work against specific calendars (by calendar name w/ regex)
93 * flag file support for specifying option defaults
94 * colored output and unicode character support
95 * super fun hacking with shell scripts, cron, screen, tmux, conky, etc
96
97 Screenshots
98 -----------
99
100 ![gcalcli 5](https://github.com/insanum/gcalcli/raw/master/docs/gcalcli_5.png)
101
102 ![gcalcli 1](https://github.com/insanum/gcalcli/raw/master/docs/gcalcli_1.png)
103
104 ![gcalcli 2](https://github.com/insanum/gcalcli/raw/master/docs/gcalcli_2.png)
105
106 ![gcalcli 3](https://github.com/insanum/gcalcli/raw/master/docs/gcalcli_3.png)
107
108 ![gcalcli 4](https://github.com/insanum/gcalcli/raw/master/docs/gcalcli_4.png)
109
110 HowTo
111 -----
112
113 #### Usage
114
115 `gcalcli` provides a series of subcommands with the following functionality:
116
117 list list available calendars
118 edit edit calendar events
119 agenda get an agenda for a time period
120 updates get updates since a datetime for a time period
121 calw get a week-based agenda in calendar format
122 calm get a month agenda in calendar format
123 quick quick-add an event to a calendar
124 add add a detailed event to the calendar
125 import import an ics/vcal file to a calendar
126 remind execute command if event occurs within <mins> time
127
128 See the manual (`man (1) gcalcli`), or run with `--help`/`-h` for detailed usage.
129
130 #### Login Information
131
132 OAuth2 is used for authenticating with your Google account. The resulting token
133 is placed in the ~/.gcalcli_oauth file. When you first start gcalcli the
134 authentication process will proceed. Simply follow the instructions.
135
136 If desired, you can use your own Calendar API instead of the default API values.
137 *NOTE*: these steps are optional!
138
139 * Go to the [Google developer console](https://console.developers.google.com/)
140 * Make a new project for gcalcli
141 * On the sidebar under APIs & Auth, click APIs
142 * Enable the Calendar API
143 * On the sidebar click Credentials
144 * Create a new Client ID. Set the type to Installed Application and the subtype
145 to Other. You will be asked to fill in some consent form information, but what
146 you put here isn't important. It's just what will show up when gcalcli opens
147 up the OAuth website. Anything optional can safely be left blank.
148 * Go back to the credentials page and grab your ID and Secret.
149 * If desired, add the client-id and client-secret to your .gcalclirc:
150
151 --client-id=xxxxxxxxxxxxxxx.apps.googleusercontent.com
152 --client-secret=xxxxxxxxxxxxxxxxx
153
154 * Remove your existing OAuth information (typically ~/.gcalcli_oauth).
155 * Run gcalcli with any desired argument, making sure the new client-id and
156 client-secret are passed on the command line or placed in your .gcalclirc. The
157 OAuth authorization page should be opened automatically in your default
158 browser.
159
160 #### HTTP Proxy Support
161
162 gcalcli will automatically work with an HTTP Proxy simply by setting up some
163 environment variables used by the gdata Python module:
164
165 ```
166 http_proxy
167 https_proxy
168 proxy-username or proxy_username
169 proxy-password or proxy_password
170 ```
171
172 Note that these environment variables must be lowercase.
173
174 #### Flag File
175
176 `gcalcli` is able to read default configuration information from a flag file.
177 This file is located, by default, at '~/.gcalclirc'. The flag file takes one
178 command line parameter per line.
179
180 In the current version, the flag file only supports the global options (options
181 against the `gcalcli` program itself). The plan, longer term, is to support a
182 a configuration formation (probably toml or ini), which will allow for
183 configuration of subcommands (such as `add`, `agenda`, `calw`, etc.)
184
185 Example:
186
187 ```
188 --nocache
189 --nocolor
190 --default-calendar=CALENDAR_NAME
191 --client-secret=API_KEY
192 ```
193
194 Note that long options require an equal sign if specifying a parameter. With
195 short options the equal sign is optional.
196
197 #### Configuration Folders
198
199 gcalcli is able to store all its necessary information in a specific folder (use
200 the --configFolder option.) Each folder will contain 2 files: oauth and cache.
201 An optional 3rd file, gcalclirc, can be present for specific flags that you only
202 want to apply when using this configuration folder.
203
204 #### Importing VCS/VCAL/ICS Files from Exchange (or other)
205
206 Importing events from files is easy with gcalcli. The 'import' command accepts
207 a filename on the command line or can read from standard input. Here is a script
208 that can be used as an attachment handler for Thunderbird or in a mailcap entry
209 with Mutt (or in Mutt you could just use the attachment viewer and pipe command):
210
211 ```sh
212 #!/bin/bash
213
214 TERMINAL=evilvte
215 CONFIG=~/.gcalclirc
216
217 $TERMINAL -e bash -c "echo 'Importing invite...' ; \
218 gcalcli --detail-url=short \
219 --calendar='Eric Davis' \
220 import -v \"$1\" ; \
221 read -p 'press enter to exit: '"
222 ```
223
224 Note that with Thunderbird you'll have to have the 'Show All Body Parts'
225 extension installed for seeing the calendar attachments when not using
226 'Lightning'. See this
227 [bug report](https://bugzilla.mozilla.org/show_bug.cgi?id=505024)
228 for more details.
229
230 #### Event Popup Reminders
231
232 The 'remind' command for gcalcli is used to execute any command as an event
233 notification. This can be a notify-send or an xmessage-like popup or whatever
234 else you can think of. gcalcli does not contain a daemon so you'll have to use
235 some other tool to ensure gcalcli is run in a timely manner for notifications.
236 Two options are using cron or a loop inside a shell script.
237
238 Cron:
239 ```sh
240 % crontab -l
241 */10 * * * * /usr/bin/gcalcli remind
242 ```
243
244 Shell script like your .xinitrc so notifications only occur when you're logged
245 in via X:
246 ```sh
247 #!/bin/bash
248
249 [[ -x /usr/bin/dunst ]] && /usr/bin/dunst -config ~/.dunstrc &
250
251 if [ -x /usr/bin/gcalcli ]; then
252 while true; do
253 /usr/bin/gcalcli --calendar="davis" remind
254 sleep 300
255 done &
256 fi
257
258 exec herbstluftwm # :-)
259 ```
260
261 By default gcalcli executes the notify-send command for notifications. Most
262 common Linux desktop enviroments already contain a DBUS notification daemon
263 that supports libnotify so it should automagically just work. If you're like
264 me and use nothing that is common I highly recommend the
265 [dunst](https://github.com/knopwob/dunst) dmenu'ish notification daemon.
266
267 Note that each time you run this you will get a reminder if you're still inside
268 the event duration. Also note that due to time slip between machines, gcalcli
269 will give you a ~5 minute margin of error. Plan your cron jobs accordingly.
270
271 #### Agenda On Your Root Desktop
272
273 Put your agenda on your desktop using
274 [Conky](https://github.com/brndnmtthws/conky). The '--conky' option causes
275 gcalcli to output Conky color sequences. Note that you need to use the Conky
276 'execpi' command for the gcalcli output to be parsed for color sequences. Add
277 the following to your .conkyrc:
278
279 ```
280 ${execpi 300 gcalcli --conky agenda}
281 ```
282
283 To also get a graphical calendar that shows the next three weeks add:
284
285 ```
286 ${execpi 300 gcalcli --conky calw 3}
287 ```
288
289 You may need to increase the `text_buffer_size` in your conkyrc file. Users
290 have reported that the default of 256 bytes is too small for busy calendars.
291
292 Additionaly you need to set `--lineart=unicode` to output unicode-characters
293 for box drawing. To avoid misaligned borders use a monospace font like 'DejaVu
294 Sans Mono'. On Python2 it might be necessary to set the environment variable
295 `PYTHONIOENCODING=utf8` if you are using characters beyond ascii. For
296 example:
297 ```
298 ${font DejaVu Sans Mono:size=9}${execpi 300 export PYTHONIOENCODING=utf8 && gcalcli --conky --lineart=unicode calw 3}
299 ```
300
301 #### Agenda Integration With tmux
302
303 Put your next event in the left of your 'tmux' status line. Add the following
304 to your tmux.conf file:
305
306 ```
307 set-option -g status-interval 60
308 set-option -g status-left "#[fg=blue,bright]#(gcalcli agenda | head -2 | tail -1)#[default]"
309 ```
310
311 #### Agenda Integration With screen
312
313 Put your next event in your 'screen' hardstatus line. First add a cron job
314 that will dump you agenda to a text file:
315
316 ```
317 % crontab -e
318 ```
319
320 Then add the following line:
321
322 ```
323 */5 * * * * gcalcli --nocolor --nostarted agenda "`date`" > /tmp/gcalcli_agenda.txt
324 ```
325
326 Next create a simple shell script that will extract the first agenda line.
327 Let's call this script 'screen_agenda':
328
329 ```
330 #!/bin/bash
331 head -2 /tmp/gcalcli_agenda.txt | tail -1
332 ```
333
334 Next configure screen's hardstatus line to gather data from a backtick command.
335 Of course your hardstatus line is most likely very different than this (Mine
336 is!):
337
338 ```
339 backtick 1 60 60 screen_agenda
340 hardstatus "[ %1` ]"
341 ```
0 gcalcli (4.3.0+git20210724.1.34c355c-1) UNRELEASED; urgency=low
1
2 * New upstream snapshot.
3
4 -- Debian Janitor <janitor@jelmer.uk> Thu, 05 Aug 2021 16:42:18 -0000
5
06 gcalcli (4.3.0-1) unstable; urgency=medium
17
28 * New upstream version 4.3.0.
4444 ```sh
4545 nix-env -i gcalcli
4646 ```
47
48 ### Install using [Homebrew](https://brew.sh/) (MacOS)
49
50 ```sh
51 brew install gcalcli
52 ```
53
4754
4855 ### Install from PyPI
4956
0 """Handlers for specific agendaupdate actions."""
1
2 from gcalcli.details import FIELD_HANDLERS, FIELDNAMES_READONLY
3 from gcalcli.exceptions import ReadonlyError
4
5
6 CONFERENCE_DATA_VERSION = 1
7
8
9 def _iter_field_handlers(row):
10 for fieldname, value in row.items():
11 handler = FIELD_HANDLERS[fieldname]
12 yield fieldname, handler, value
13
14
15 def patch(row, cal, interface):
16 """Patch event with new data."""
17 event_id = row['id']
18 if not event_id:
19 return insert(row, cal, interface)
20
21 curr_event = None
22 mod_event = {}
23 cal_id = cal['id']
24
25 for fieldname, handler, value in _iter_field_handlers(row):
26 if fieldname in FIELDNAMES_READONLY:
27 # Instead of changing mod_event, the Handler.patch() for
28 # a readonly field checks against the current values.
29
30 if curr_event is None:
31 curr_event = interface._retry_with_backoff(
32 interface.get_events()
33 .get(
34 calendarId=cal_id,
35 eventId=event_id
36 )
37 )
38
39 handler.patch(cal, curr_event, fieldname, value)
40 else:
41 handler.patch(cal, mod_event, fieldname, value)
42
43 interface._retry_with_backoff(
44 interface.get_events()
45 .patch(
46 calendarId=cal_id,
47 eventId=event_id,
48 conferenceDataVersion=CONFERENCE_DATA_VERSION,
49 body=mod_event
50 )
51 )
52
53
54 def insert(row, cal, interface):
55 """Insert new event."""
56 event = {}
57 cal_id = cal['id']
58
59 for fieldname, handler, value in _iter_field_handlers(row):
60 if fieldname in FIELDNAMES_READONLY:
61 raise ReadonlyError("Cannot specify value on insert.")
62
63 handler.patch(cal, event, fieldname, value)
64
65 interface._retry_with_backoff(
66 interface.get_events()
67 .insert(
68 calendarId=cal_id,
69 conferenceDataVersion=CONFERENCE_DATA_VERSION,
70 body=event
71 )
72 )
73
74
75 def delete(row, cal, interface):
76 """Delete event."""
77 cal_id = cal['id']
78 event_id = row['id']
79
80 interface.delete(cal_id, event_id)
81
82
83 def ignore(*args, **kwargs):
84 """Do nothing."""
85
86
87 ACTIONS = {"patch", "insert", "delete", "ignore"}
11 import argparse
22 import gcalcli
33 from gcalcli import utils
4 from gcalcli.details import DETAILS
45 from gcalcli.deprecations import parser_allow_deprecated, DeprecatedStoreTrue
56 from gcalcli.printer import valid_color_name
67 from oauth2client import tools
89 import copy as _copy
910 import datetime
1011 import locale
11
12 DETAILS = ['calendar', 'location', 'length', 'reminders', 'description',
13 'url', 'conference', 'attendees', 'email', 'attachments', 'end']
14
12 import sys
1513
1614 PROGRAM_OPTIONS = {
1715 '--client-id': {'default': gcalcli.__API_CLIENT_ID__,
307305 help='get an agenda for a time period',
308306 description='Get an agenda for a time period.')
309307
308 agendaupdate = sub.add_parser(
309 'agendaupdate',
310 help='update calendar from agenda TSV file',
311 description='Update calendar from agenda TSV file.')
312 agendaupdate.add_argument(
313 'file', type=argparse.FileType('r'), nargs='?', default=sys.stdin)
314
310315 sub.add_parser(
311316 'updates',
312317 parents=[details_parser, output_parser, updates_parser],
150150 elif parsed_args.command == 'agenda':
151151 gcal.AgendaQuery(start=parsed_args.start, end=parsed_args.end)
152152
153 elif parsed_args.command == 'agendaupdate':
154 gcal.AgendaUpdate(parsed_args.file)
155
153156 elif parsed_args.command == 'updates':
154157 gcal.UpdatesQuery(
155158 last_updated_datetime=parsed_args.since,
0 """Handlers for specific details of events."""
1
2 from collections import OrderedDict
3 from datetime import datetime
4 from itertools import chain
5 from typing import List
6
7 from dateutil.parser import isoparse, parse
8
9 from gcalcli.exceptions import ReadonlyError, ReadonlyCheckError
10 from gcalcli.utils import is_all_day
11
12 FMT_DATE = '%Y-%m-%d'
13 FMT_TIME = '%H:%M'
14 TODAY = datetime.now().date()
15 ACTION_DEFAULT = "patch"
16
17 URL_PROPS = OrderedDict([('html_link', 'htmlLink'),
18 ('hangout_link', 'hangoutLink')])
19 ENTRY_POINT_PROPS = OrderedDict([('conference_entry_point_type',
20 'entryPointType'),
21 ('conference_uri', 'uri')])
22
23
24 def _valid_title(event):
25 if 'summary' in event and event['summary'].strip():
26 return event['summary']
27 else:
28 return '(No title)'
29
30
31 class Handler:
32 """Handler for a specific detail of an event."""
33
34 # list of strings for fieldnames provided by this object
35 # XXX: py36+: change to `fieldnames: List[str]`
36 fieldnames = [] # type: List[str]
37
38 @classmethod
39 def get(cls, event):
40 """Return simple string representation for columnar output."""
41 raise NotImplementedError
42
43 @classmethod
44 def patch(cls, cal, event, fieldname, value):
45 """Patch event from value."""
46 raise NotImplementedError
47
48
49 class SingleFieldHandler(Handler):
50 """Handler for a detail that only produces one column."""
51
52 @classmethod
53 def get(cls, event):
54 return [cls._get(event).strip()]
55
56 @classmethod
57 def patch(cls, cal, event, fieldname, value):
58 return cls._patch(event, value)
59
60
61 class SimpleSingleFieldHandler(SingleFieldHandler):
62 """Handler for single-string details that require no special processing."""
63
64 @classmethod
65 def _get(cls, event):
66 return event.get(cls.fieldnames[0], '')
67
68 @classmethod
69 def _patch(cls, event, value):
70 event[cls.fieldnames[0]] = value
71
72
73 class Time(Handler):
74 """Handler for dates and times."""
75
76 fieldnames = ['start_date', 'start_time', 'end_date', 'end_time']
77
78 @classmethod
79 def _datetime_to_fields(cls, instant, all_day):
80 instant_date = instant.strftime(FMT_DATE)
81
82 if all_day:
83 instant_time = ''
84 else:
85 instant_time = instant.strftime(FMT_TIME)
86
87 return [instant_date, instant_time]
88
89 @classmethod
90 def get(cls, event):
91 all_day = is_all_day(event)
92
93 start_fields = cls._datetime_to_fields(event['s'], all_day)
94 end_fields = cls._datetime_to_fields(event['e'], all_day)
95
96 return start_fields + end_fields
97
98 @classmethod
99 def patch(cls, cal, event, fieldname, value):
100 instant_name, _, unit = fieldname.partition('_')
101
102 assert instant_name in {'start', 'end'}
103
104 if unit == 'date':
105 instant = event[instant_name] = {}
106 instant_date = parse(value, default=TODAY)
107
108 instant['date'] = instant_date.isoformat()
109 instant['dateTime'] = None # clear any previous non-all-day time
110 else:
111 assert unit == 'time'
112
113 # If the time field is empty, do nothing.
114 # This enables all day events.
115 if not value.strip():
116 return
117
118 # date must be an earlier TSV field than time
119 instant = event[instant_name]
120 instant_date = isoparse(instant['date'])
121 instant_datetime = parse(value, default=instant_date)
122
123 instant['date'] = None # clear all-day date
124 instant['dateTime'] = instant_datetime.isoformat()
125 instant['timeZone'] = cal['timeZone']
126
127
128 class Url(Handler):
129 """Handler for HTML and legacy Hangout links."""
130
131 fieldnames = list(URL_PROPS.keys())
132
133 @classmethod
134 def get(cls, event):
135 return [event.get(prop, '') for prop in URL_PROPS.values()]
136
137 @classmethod
138 def patch(cls, cal, event, fieldname, value):
139 if fieldname == 'html_link':
140 raise ReadonlyError(fieldname,
141 'It is not possible to verify that the value '
142 'has not changed. '
143 'Remove it from the input.')
144
145 prop = URL_PROPS[fieldname]
146
147 # Fail if the current value doesn't
148 # match the desired patch. This requires an additional API query for
149 # each row, so best to avoid attempting to update these fields.
150
151 curr_value = event.get(prop, '')
152
153 if curr_value != value:
154 raise ReadonlyCheckError(fieldname, curr_value, value)
155
156
157 class Conference(Handler):
158 """Handler for videoconference and teleconference details."""
159
160 fieldnames = list(ENTRY_POINT_PROPS.keys())
161
162 @classmethod
163 def get(cls, event):
164 if 'conferenceData' not in event:
165 return ['', '']
166
167 data = event['conferenceData']
168
169 # only display first entry point for TSV
170 # https://github.com/insanum/gcalcli/issues/533
171 entry_point = data['entryPoints'][0]
172
173 return [entry_point.get(prop, '')
174 for prop in ENTRY_POINT_PROPS.values()]
175
176 @classmethod
177 def patch(cls, cal, event, fieldname, value):
178 if not value:
179 return
180
181 prop = ENTRY_POINT_PROPS[fieldname]
182
183 data = event.setdefault('conferenceData', {})
184 entry_points = data.setdefault('entryPoints', [])
185 if not entry_points:
186 entry_points.append({})
187
188 entry_point = entry_points[0]
189 entry_point[prop] = value
190
191
192 class Title(SingleFieldHandler):
193 """Handler for title."""
194
195 fieldnames = ['title']
196
197 @classmethod
198 def _get(cls, event):
199 return _valid_title(event)
200
201 @classmethod
202 def _patch(cls, event, value):
203 event['summary'] = value
204
205
206 class Location(SimpleSingleFieldHandler):
207 """Handler for location."""
208
209 fieldnames = ['location']
210
211
212 class Description(SimpleSingleFieldHandler):
213 """Handler for description."""
214
215 fieldnames = ['description']
216
217
218 class Calendar(SingleFieldHandler):
219 """Handler for calendar."""
220
221 fieldnames = ['calendar']
222
223 @classmethod
224 def _get(cls, event):
225 return event['gcalcli_cal']['summary']
226
227 @classmethod
228 def patch(cls, cal, event, fieldname, value):
229 curr_value = cal['summary']
230
231 if curr_value != value:
232 raise ReadonlyCheckError(fieldname, curr_value, value)
233
234
235 class Email(SingleFieldHandler):
236 """Handler for emails."""
237
238 fieldnames = ['email']
239
240 @classmethod
241 def _get(cls, event):
242 return event['creator'].get('email', '')
243
244
245 class ID(SimpleSingleFieldHandler):
246 """Handler for event ID."""
247
248 fieldnames = ['id']
249
250
251 class Action(SimpleSingleFieldHandler):
252 """Handler specifying event processing during an update."""
253
254 fieldnames = ['action']
255
256 @classmethod
257 def _get(cls, event):
258 return ACTION_DEFAULT
259
260
261 HANDLERS = OrderedDict([('id', ID),
262 ('time', Time),
263 ('url', Url),
264 ('conference', Conference),
265 ('title', Title),
266 ('location', Location),
267 ('description', Description),
268 ('calendar', Calendar),
269 ('email', Email),
270 ('action', Action)])
271 HANDLERS_READONLY = {Url, Calendar}
272
273 FIELD_HANDLERS = dict(chain.from_iterable(
274 (((fieldname, handler)
275 for fieldname in handler.fieldnames)
276 for handler in HANDLERS.values())))
277
278 FIELDNAMES_READONLY = frozenset(fieldname
279 for fieldname, handler
280 in FIELD_HANDLERS.items()
281 if handler in HANDLERS_READONLY)
282
283 _DETAILS_WITHOUT_HANDLERS = ['length', 'reminders', 'attendees',
284 'attachments', 'end']
285
286 DETAILS = list(HANDLERS.keys()) + _DETAILS_WITHOUT_HANDLERS
287 DETAILS_DEFAULT = {'time', 'title'}
77 self.message = message
88
99
10 class ReadonlyError(Exception):
11 def __init__(self, fieldname, message):
12 message = 'Field {} is read-only. {}'.format(fieldname, message)
13 super(ReadonlyError, self).__init__(message)
14
15
16 class ReadonlyCheckError(ReadonlyError):
17 _fmt = 'Current value "{}" does not match update value "{}"'
18
19 def __init__(self, fieldname, curr_value, mod_value):
20 message = self._fmt.format(curr_value, mod_value)
21 super(ReadonlyCheckError, self).__init__(fieldname, message)
22
23
1024 def raise_one_cal_error(cals):
1125 raise GcalcliError(
1226 'You must only specify a single calendar\n'
0 from csv import DictReader, excel_tab
01 import os
12 import re
23 import shlex
34 import httplib2
5 from itertools import chain
46 import time
57 import textwrap
68 import json
1315 import pickle
1416
1517 from gcalcli import __program__, __version__
16 from gcalcli import utils
17 from gcalcli.utils import days_since_epoch
18 from gcalcli import actions, utils
19 from gcalcli.actions import ACTIONS
20 from gcalcli.details import (
21 _valid_title, ACTION_DEFAULT, DETAILS_DEFAULT, HANDLERS)
22 from gcalcli.utils import days_since_epoch, is_all_day
1823 from gcalcli.validators import (
1924 get_input, get_override_color_id, STR_NOT_EMPTY, PARSABLE_DATE, STR_TO_INT,
2025 VALID_COLORS, STR_ALLOW_EMPTY, REMINDER, PARSABLE_DURATION
3641
3742 EventTitle = namedtuple('EventTitle', ['title', 'color'])
3843
44 CONFERENCE_DATA_VERSION = 1
3945
4046 class GoogleCalendarInterface:
4147
157163 http=self._google_auth())
158164
159165 return self.cal_service
166
167 def get_events(self):
168 return self.get_cal_service().events()
160169
161170 def _get_cached(self):
162171 if self.options['config_folder']:
246255 else:
247256 return 'default'
248257
249 def _valid_title(self, event):
250 if 'summary' in event and event['summary'].strip():
251 return event['summary']
252 else:
253 return '(No title)'
254
255 def _isallday(self, event):
256 return event['s'].hour == 0 and event['s'].minute == 0 and \
257 event['e'].hour == 0 and event['e'].minute == 0
258
259258 def _cal_monday(self, day_num):
260259 """Shift the day number if we're doing cal monday, or cal_weekend is
261260 false, since that also means we're starting on day 1
273272 return e_start < time_point and e_end >= time_point
274273
275274 def _format_title(self, event, allday=False):
276 titlestr = self._valid_title(event)
275 titlestr = _valid_title(event)
277276 if allday:
278277 return titlestr
279278 elif self.options['military']:
302301
303302 for event in event_list:
304303 event_daynum = self._cal_monday(int(event['s'].strftime('%w')))
305 event_allday = self._isallday(event)
304 event_allday = is_all_day(event)
306305
307306 event_end_date = event['e']
308307 if event_allday:
586585 self.printer.msg(week_bottom + '\n', color_border)
587586
588587 def _tsv(self, start_datetime, event_list):
588 keys = set(self.details.keys())
589 keys.update(DETAILS_DEFAULT)
590
591 handlers = [handler
592 for key, handler in HANDLERS.items()
593 if key in keys]
594
595 header_row = chain.from_iterable(handler.fieldnames
596 for handler in handlers)
597 print(*header_row, sep='\t')
598
589599 for event in event_list:
590600 if self.options['ignore_started'] and (event['s'] < self.now):
591601 continue
592602 if self.options['ignore_declined'] and self._DeclinedEvent(event):
593603 continue
594 output = '%s\t%s\t%s\t%s' % (event['s'].strftime('%Y-%m-%d'),
595 event['s'].strftime('%H:%M'),
596 event['e'].strftime('%Y-%m-%d'),
597 event['e'].strftime('%H:%M'))
598
599 if self.details.get('url'):
600 output += '\t%s' % (event['htmlLink']
601 if 'htmlLink' in event else '')
602 output += '\t%s' % (event['hangoutLink']
603 if 'hangoutLink' in event else '')
604
605 if self.details.get('conference'):
606 conference_data = (event['conferenceData']
607 if 'conferenceData' in event else None)
608
609 # only display first entry point for TSV
610 # https://github.com/insanum/gcalcli/issues/533
611 entry_point = (conference_data['entryPoints'][0]
612 if conference_data is not None else None)
613
614 output += '\t%s' % (entry_point['entryPointType']
615 if conference_data is not None else '')
616
617 output += '\t%s' % (entry_point['uri']
618 if conference_data is not None else '')
619
620 output += '\t%s' % self._valid_title(event).strip()
621
622 if self.details.get('location'):
623 output += '\t%s' % (event['location'].strip()
624 if 'location' in event else '')
625
626 if self.details.get('description'):
627 output += '\t%s' % (event['description'].strip()
628 if 'description' in event else '')
629
630 if self.details.get('calendar'):
631 output += '\t%s' % event['gcalcli_cal']['summary'].strip()
632
633 if self.details.get('email'):
634 output += '\t%s' % (event['creator']['email'].strip()
635 if 'email' in event['creator'] else '')
636
637 output = '%s\n' % output.replace('\n', '''\\n''')
638 sys.stdout.write(output)
604
605 row = []
606 for handler in handlers:
607 row.extend(handler.get(event))
608
609 output = ('\t'.join(row)).replace('\n', '''\\n''')
610 print(output)
639611
640612 def _PrintEvent(self, event, prefix):
641613
674646 self.printer.msg(prefix, self.options['color_date'])
675647
676648 happening_now = event['s'] <= self.now <= event['e']
677 all_day = self._isallday(event)
649 all_day = is_all_day(event)
678650 if self.options['override_color'] and event.get('colorId'):
679651 if happening_now and not all_day:
680652 event_color = self.options['color_now_marker']
689661 if all_day:
690662 fmt = ' ' + time_width + ' %s\n'
691663 self.printer.msg(
692 fmt % ('', self._valid_title(event).strip()),
664 fmt % ('', _valid_title(event).strip()),
693665 event_color
694666 )
695667 else:
705677
706678 self.printer.msg(
707679 fmt % (tmp_start_time_str, tmp_end_time_str,
708 self._valid_title(event).strip()),
680 _valid_title(event).strip()),
709681 event_color
710682 )
711683
847819 )
848820 self.printer.msg(xstr, 'default')
849821
822 def delete(self, cal_id, event_id):
823 self._retry_with_backoff(
824 self.get_events()
825 .delete(calendarId=cal_id,
826 eventId=event_id)
827 )
828
850829 def _delete_event(self, event):
830 cal_id = event['gcalcli_cal']['id']
831 event_id = event['id']
851832
852833 if self.expert:
853 self._retry_with_backoff(
854 self.get_cal_service()
855 .events()
856 .delete(
857 calendarId=event['gcalcli_cal']['id'],
858 eventId=event['id']
859 )
860 )
834 self.delete(cal_id, event_id)
861835 self.printer.msg('Deleted!\n', 'red')
862836 return
863837
868842 return
869843
870844 elif val.lower() == 'y':
871 self._retry_with_backoff(
872 self.get_cal_service()
873 .events()
874 .delete(
875 calendarId=event['gcalcli_cal']['id'],
876 eventId=event['id']
877 )
878 )
845 self.delete(cal_id, event_id)
879846 self.printer.msg('Deleted!\n', 'red')
880847
881848 elif val.lower() == 'q':
935902 mod_event[k] = event[k]
936903
937904 self._retry_with_backoff(
938 self.get_cal_service()
939 .events()
905 self.get_events()
940906 .patch(
941907 calendarId=event['gcalcli_cal']['id'],
942908 eventId=event['id'],
11091075 pageToken = events.get('nextPageToken')
11101076 if pageToken:
11111077 events = self._retry_with_backoff(
1112 self.get_cal_service()
1113 .events()
1078 self.get_events()
11141079 .list(
11151080 calendarId=cal['id'],
11161081 pageToken=pageToken
11261091 event_list = []
11271092 for cal in self.cals:
11281093 events = self._retry_with_backoff(
1129 self.get_cal_service()
1130 .events()
1094 self.get_events()
11311095 .list(
11321096 calendarId=cal['id'],
11331097 timeMin=start.isoformat() if start else None,
12351199 end = (start + timedelta(days=self.agenda_length))
12361200
12371201 return self._display_queried_events(start, end)
1202
1203 def AgendaUpdate(self, file=sys.stdin):
1204 reader = DictReader(file, dialect=excel_tab)
1205
1206 if len(self.cals) != 1:
1207 raise GcalcliError('Must specify a single calendar.')
1208
1209 cal = self.cals[0]
1210
1211 for row in reader:
1212 action = row.get("action", ACTION_DEFAULT)
1213 if action not in ACTIONS:
1214 raise GcalcliError('Action "{}" not supported.'.format(action))
1215
1216 getattr(actions, action)(row, cal, self)
12381217
12391218 def CalQuery(self, cmd, start_text='', count=1):
12401219 if not start_text:
12911270 raise GcalcliError('You must only specify a single calendar\n')
12921271
12931272 new_event = self._retry_with_backoff(
1294 self.get_cal_service()
1295 .events()
1273 self.get_events()
12961274 .quickAdd(
12971275 calendarId=self.cals[0]['id'],
12981276 text=event_text
13091287 'method': m})
13101288
13111289 new_event = self._retry_with_backoff(
1312 self.get_cal_service()
1313 .events()
1290 self.get_events()
13141291 .patch(
13151292 calendarId=self.cals[0]['id'],
13161293 eventId=new_event['id'],
13691346 event['attendees'] = list(map(lambda w: {'email': w}, who))
13701347
13711348 event = self._add_reminders(event, reminders)
1372 events = self.get_cal_service().events()
1349 events = self.get_events()
13731350 request = events.insert(calendarId=self.cals[0]['id'], body=event)
13741351 new_event = self._retry_with_backoff(request)
13751352
14271404 event['s'].strftime('%p').lower()
14281405
14291406 message += '%s %s\n' % \
1430 (tmp_time_str, self._valid_title(event).strip())
1407 (tmp_time_str, _valid_title(event).strip())
14311408
14321409 if not message:
14331410 return
16011578
16021579 if not verbose:
16031580 new_event = self._retry_with_backoff(
1604 self.get_cal_service()
1605 .events()
1581 self.get_events()
16061582 .insert(
16071583 calendarId=self.cals[0]['id'],
16081584 body=event
16201596 continue
16211597 if val.lower() == 'i':
16221598 new_event = self._retry_with_backoff(
1623 self.get_cal_service()
1624 .events()
1599 self.get_events()
16251600 .insert(
16261601 calendarId=self.cals[0]['id'],
16271602 body=event
142142 hour_min_fmt = '%H:%M' if military else '%I:%M'
143143 ampm = '' if military else dt.strftime('%p').lower()
144144 return dt.strftime(hour_min_fmt).lstrip('0') + ampm
145
146
147 def is_all_day(event):
148 # XXX: currently gcalcli represents all-day events as those that both begin
149 # and end at midnight. This is ambiguous with Google Calendar events that
150 # are not all-day but happen to begin and end at midnight.
151
152 return (event['s'].hour == 0 and event['s'].minute == 0
153 and event['e'].hour == 0 and event['e'].minute == 0)
0 Metadata-Version: 2.1
1 Name: gcalcli
2 Version: 4.3.0
3 Summary: Google Calendar Command Line Interface
4 Home-page: https://github.com/insanum/gcalcli
5 Author: Eric Davis, Brian Hartvigsen, Joshua Crowgey
6 Author-email: edavis@insanum.com, brian.andrew@brianandjenny.com, jcrowgey@uw.edu
7 Maintainer: Joshua Crowgey
8 Maintainer-email: jcrowgey@uw.edu
9 License: MIT
10 Description: UNKNOWN
11 Platform: UNKNOWN
12 Classifier: Development Status :: 5 - Production/Stable
13 Classifier: Environment :: Console
14 Classifier: Intended Audience :: End Users/Desktop
15 Classifier: License :: OSI Approved :: MIT License
16 Classifier: Programming Language :: Python :: 3
17 Requires-Python: >=3
18 Provides-Extra: vobject
0 ChangeLog
1 MANIFEST.in
2 README.md
3 setup.cfg
4 setup.py
5 docs/README.md
6 docs/gcalcli_1.png
7 docs/gcalcli_2.png
8 docs/gcalcli_3.png
9 docs/gcalcli_4.png
10 docs/gcalcli_5.png
11 docs/man1/gcalcli.1
12 gcalcli/__init__.py
13 gcalcli/actions.py
14 gcalcli/argparsers.py
15 gcalcli/cli.py
16 gcalcli/conflicts.py
17 gcalcli/deprecations.py
18 gcalcli/details.py
19 gcalcli/exceptions.py
20 gcalcli/gcal.py
21 gcalcli/printer.py
22 gcalcli/utils.py
23 gcalcli/validators.py
24 gcalcli.egg-info/PKG-INFO
25 gcalcli.egg-info/SOURCES.txt
26 gcalcli.egg-info/dependency_links.txt
27 gcalcli.egg-info/entry_points.txt
28 gcalcli.egg-info/requires.txt
29 gcalcli.egg-info/top_level.txt
0 [console_scripts]
1 gcalcli = gcalcli.cli:main
2
0 google-api-python-client>=1.4
1 httplib2
2 oauth2client
3 parsedatetime
4 python-dateutil
5
6 [vobject]
7 vobject
00 [bdist_wheel]
11 universal = 1
2
3 [egg_info]
4 tag_build =
5 tag_date = 0
6
33
44 try:
55 import pypandoc
6 long_description = pypandoc.convert('README.md', 'rst',
7 format='markdown_github',
8 extra_args=("--wrap=none",))
6 long_description = pypandoc.convert_file(
7 'README.md',
8 'rst',
9 format='markdown_github',
10 extra_args=("--wrap=none",)
11 )
912 except ImportError:
1013 import sys
1114 print('Warning: No long description generated.', file=sys.stderr)
+0
-153
tests/conftest.py less more
0 import os
1 import sys
2
3 import pytest
4
5 from datetime import datetime
6 from dateutil.tz import tzlocal
7
8 from apiclient.discovery import HttpMock, build
9
10 from gcalcli.argparsers import (get_color_parser,
11 get_cal_query_parser,
12 get_output_parser)
13 from gcalcli.gcal import GoogleCalendarInterface
14 from gcalcli.printer import Printer
15
16 TEST_DATA_DIR = os.path.dirname(os.path.abspath(__file__)) + '/data'
17
18 mock_event = [{'colorId': "10",
19 'created': '2018-12-31T09:20:32.000Z',
20 'creator': {'email': 'matthew.lemon@gmail.com'},
21 'e': datetime(2019, 1, 8, 15, 15, tzinfo=tzlocal()),
22 'end': {'dateTime': '2019-01-08T15:15:00Z'},
23 'etag': '"3092496064420000"',
24 'gcalcli_cal': {'accessRole': 'owner',
25 'backgroundColor': '#4986e7',
26 'colorId': '16',
27 'conferenceProperties': {
28 'allowedConferenceSolutionTypes':
29 ['eventHangout']
30 },
31 'defaultReminders': [],
32 'etag': '"153176133553000"',
33 'foregroundColor': '#000000',
34 'id': '12pp3nqo@group.calendar.google.com',
35 'kind': 'calendar#calendarListEntry',
36 'selected': True,
37 'summary': 'Test Calendar',
38 'timeZone': 'Europe/London'},
39 'htmlLink': '',
40 'iCalUID': '31376E6-8B63-416C-B73A-74D10F51F',
41 'id': '_6coj0c9o88r3b9a26spk2b9n6sojed2464o4cd9h8o',
42 'kind': 'calendar#event',
43 'organizer': {
44 'displayName': 'Test Calendar',
45 'email': 'tst@group.google.com',
46 'self': True},
47 'reminders': {'useDefault': True},
48 's': datetime(2019, 1, 8, 14, 15, tzinfo=tzlocal()),
49 'sequence': 0,
50 'start': {'dateTime': '2019-01-08T14:15:00Z'},
51 'status': 'confirmed',
52 'summary': 'Test Event',
53 'updated': '2018-12-31T09:20:32.210Z'}]
54
55
56 @pytest.fixture
57 def default_options():
58 opts = vars(get_color_parser().parse_args([]))
59 opts.update(vars(get_cal_query_parser().parse_args([])))
60 opts.update(vars(get_output_parser().parse_args([])))
61 return opts
62
63
64 @pytest.fixture
65 def PatchedGCalIForEvents(monkeypatch):
66 def mocked_search_for_events(self, start, end, search_text):
67 return mock_event
68
69 def mocked_calendar_service(self):
70 http = HttpMock(
71 TEST_DATA_DIR + '/cal_service_discovery.json',
72 {'status': '200'})
73 if not self.cal_service:
74 self.cal_service = build(
75 serviceName='calendar', version='v3', http=http)
76 return self.cal_service
77
78 def mocked_calendar_list(self):
79 http = HttpMock(
80 TEST_DATA_DIR + '/cal_list.json', {'status': '200'})
81 request = self.get_cal_service().calendarList().list()
82 cal_list = request.execute(http=http)
83 self.all_cals = [cal for cal in cal_list['items']]
84 if not self.cal_service:
85 self.cal_service = build(
86 serviceName='calendar', version='v3', http=http)
87 return self.cal_service
88
89 def mocked_msg(self, msg, colorname='default', file=sys.stdout):
90 # ignores file and always writes to stdout
91 if self.use_color:
92 msg = self.colors[colorname] + msg + self.colors['default']
93 sys.stdout.write(msg)
94
95 monkeypatch.setattr(
96 GoogleCalendarInterface, '_search_for_events',
97 mocked_search_for_events
98 )
99 monkeypatch.setattr(
100 GoogleCalendarInterface, 'get_cal_service', mocked_calendar_service
101 )
102 monkeypatch.setattr(
103 GoogleCalendarInterface, '_get_cached', mocked_calendar_list
104 )
105 monkeypatch.setattr(Printer, 'msg', mocked_msg)
106
107 def _init(**opts):
108 return GoogleCalendarInterface(use_cache=False, **opts)
109
110 return _init
111
112
113 @pytest.fixture
114 def PatchedGCalI(monkeypatch):
115 def mocked_calendar_service(self):
116 http = HttpMock(
117 TEST_DATA_DIR + '/cal_service_discovery.json',
118 {'status': '200'})
119 if not self.cal_service:
120 self.cal_service = build(
121 serviceName='calendar', version='v3', http=http)
122 return self.cal_service
123
124 def mocked_calendar_list(self):
125 http = HttpMock(
126 TEST_DATA_DIR + '/cal_list.json', {'status': '200'})
127 request = self.get_cal_service().calendarList().list()
128 cal_list = request.execute(http=http)
129 self.all_cals = [cal for cal in cal_list['items']]
130 if not self.cal_service:
131 self.cal_service = build(
132 serviceName='calendar', version='v3', http=http)
133 return self.cal_service
134
135 def mocked_msg(self, msg, colorname='default', file=sys.stdout):
136 # ignores file and always writes to stdout
137 if self.use_color:
138 msg = self.colors[colorname] + msg + self.colors['default']
139 sys.stdout.write(msg)
140
141 monkeypatch.setattr(
142 GoogleCalendarInterface, 'get_cal_service', mocked_calendar_service
143 )
144 monkeypatch.setattr(
145 GoogleCalendarInterface, '_get_cached', mocked_calendar_list
146 )
147 monkeypatch.setattr(Printer, 'msg', mocked_msg)
148
149 def _init(**opts):
150 return GoogleCalendarInterface(use_cache=False, **opts)
151
152 return _init
+0
-147
tests/data/cal_list.json less more
0 {
1 "nextSyncToken": "somebase64text==",
2 "items": [
3 {
4 "summary": "jcrowgey@uw.edu",
5 "accessRole": "owner",
6 "etag": "\"etag0\"",
7 "location": "Seattle",
8 "defaultReminders": [],
9 "colorId": "15",
10 "timeZone": "America/Los_Angeles",
11 "foregroundColor": "#000000",
12 "id": "jcrowgey@uw.edu",
13 "selected": true,
14 "backgroundColor": "#9fc6e7",
15 "conferenceProperties": {
16 "allowedConferenceSolutionTypes": [
17 "eventNamedHangout"
18 ]
19 },
20 "kind": "calendar#calendarListEntry"
21 },
22 {
23 "summary": "Google Code Jam",
24 "accessRole": "reader",
25 "etag": "\"1522646459661000\"",
26 "description": "Public calendar of important dates and events for Google Code Jam competitions.",
27 "colorId": "18",
28 "timeZone": "UTC",
29 "foregroundColor": "#000000",
30 "defaultReminders": [],
31 "id": "google.com_jqv7qt9iifsaj94cuknckrabd8@group.calendar.google.com",
32 "selected": true,
33 "backgroundColor": "#b99aff",
34 "conferenceProperties": {
35 "allowedConferenceSolutionTypes": [
36 "eventNamedHangout"
37 ]
38 },
39 "kind": "calendar#calendarListEntry"
40 },
41 {
42 "notificationSettings": {
43 "notifications": [
44 {
45 "method": "email",
46 "type": "eventCreation"
47 },
48 {
49 "method": "email",
50 "type": "eventChange"
51 },
52 {
53 "method": "email",
54 "type": "eventCancellation"
55 },
56 {
57 "method": "email",
58 "type": "eventResponse"
59 }
60 ]
61 },
62 "summary": "joshuacrowgey@gmail.com",
63 "accessRole": "owner",
64 "etag": "\"etag1\"",
65 "defaultReminders": [
66 {
67 "method": "email",
68 "minutes": 10
69 },
70 {
71 "method": "popup",
72 "minutes": 30
73 }
74 ],
75 "colorId": "2",
76 "timeZone": "America/Los_Angeles",
77 "foregroundColor": "#000000",
78 "id": "joshuacrowgey@gmail.com",
79 "primary": true,
80 "selected": true,
81 "backgroundColor": "#d06b64",
82 "conferenceProperties": {
83 "allowedConferenceSolutionTypes": [
84 "eventHangout"
85 ]
86 },
87 "kind": "calendar#calendarListEntry"
88 },
89 {
90 "summary": "Contacts",
91 "accessRole": "reader",
92 "etag": "\"etag2\"",
93 "defaultReminders": [],
94 "colorId": "13",
95 "timeZone": "America/Los_Angeles",
96 "foregroundColor": "#000000",
97 "id": "#contacts@group.v.calendar.google.com",
98 "selected": true,
99 "backgroundColor": "#92e1c0",
100 "conferenceProperties": {
101 "allowedConferenceSolutionTypes": [
102 "eventHangout"
103 ]
104 },
105 "kind": "calendar#calendarListEntry"
106 },
107 {
108 "summary": "Holidays in United States",
109 "accessRole": "reader",
110 "etag": "\"0\"",
111 "defaultReminders": [],
112 "colorId": "15",
113 "timeZone": "America/Los_Angeles",
114 "foregroundColor": "#000000",
115 "id": "en.usa#holiday@group.v.calendar.google.com",
116 "selected": true,
117 "backgroundColor": "#9fc6e7",
118 "conferenceProperties": {
119 "allowedConferenceSolutionTypes": [
120 "eventHangout"
121 ]
122 },
123 "kind": "calendar#calendarListEntry"
124 },
125 {
126 "summary": "Sunrise/Sunset: Seattle",
127 "accessRole": "reader",
128 "etag": "\"0\"",
129 "defaultReminders": [],
130 "colorId": "6",
131 "timeZone": "America/Los_Angeles",
132 "foregroundColor": "#000000",
133 "id": "i_71.23.40.78#sunrise@group.v.calendar.google.com",
134 "selected": true,
135 "backgroundColor": "#ffad46",
136 "conferenceProperties": {
137 "allowedConferenceSolutionTypes": [
138 "eventHangout"
139 ]
140 },
141 "kind": "calendar#calendarListEntry"
142 }
143 ],
144 "etag": "\"etag3\"",
145 "kind": "calendar#calendarList"
146 }
+0
-2893
tests/data/cal_service_discovery.json less more
0 {
1 "kind": "discovery#restDescription",
2 "etag": "\"Zkyw9ACJZUvcYmlFaKGChzhmtnE/7dstghQ07UJcHq5LCdThn-VM6-s\"",
3 "discoveryVersion": "v1",
4 "id": "calendar:v3",
5 "name": "calendar",
6 "version": "v3",
7 "revision": "20180515",
8 "title": "Calendar API",
9 "description": "Manipulates events and other calendar data.",
10 "ownerDomain": "google.com",
11 "ownerName": "Google",
12 "icons": {
13 "x16": "http://www.google.com/images/icons/product/calendar-16.png",
14 "x32": "http://www.google.com/images/icons/product/calendar-32.png"
15 },
16 "documentationLink": "https://developers.google.com/google-apps/calendar/firstapp",
17 "protocol": "rest",
18 "baseUrl": "https://www.googleapis.com/calendar/v3/",
19 "basePath": "/calendar/v3/",
20 "rootUrl": "https://www.googleapis.com/",
21 "servicePath": "calendar/v3/",
22 "batchPath": "batch/calendar/v3",
23 "parameters": {
24 "alt": {
25 "type": "string",
26 "description": "Data format for the response.",
27 "default": "json",
28 "enum": [
29 "json"
30 ],
31 "enumDescriptions": [
32 "Responses with Content-Type of application/json"
33 ],
34 "location": "query"
35 },
36 "fields": {
37 "type": "string",
38 "description": "Selector specifying which fields to include in a partial response.",
39 "location": "query"
40 },
41 "key": {
42 "type": "string",
43 "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
44 "location": "query"
45 },
46 "oauth_token": {
47 "type": "string",
48 "description": "OAuth 2.0 token for the current user.",
49 "location": "query"
50 },
51 "prettyPrint": {
52 "type": "boolean",
53 "description": "Returns response with indentations and line breaks.",
54 "default": "true",
55 "location": "query"
56 },
57 "quotaUser": {
58 "type": "string",
59 "description": "An opaque string that represents a user for quota purposes. Must not exceed 40 characters.",
60 "location": "query"
61 },
62 "userIp": {
63 "type": "string",
64 "description": "Deprecated. Please use quotaUser instead.",
65 "location": "query"
66 }
67 },
68 "auth": {
69 "oauth2": {
70 "scopes": {
71 "https://www.googleapis.com/auth/calendar": {
72 "description": "Manage your calendars"
73 },
74 "https://www.googleapis.com/auth/calendar.readonly": {
75 "description": "View your calendars"
76 }
77 }
78 }
79 },
80 "schemas": {
81 "Acl": {
82 "id": "Acl",
83 "type": "object",
84 "properties": {
85 "etag": {
86 "type": "string",
87 "description": "ETag of the collection."
88 },
89 "items": {
90 "type": "array",
91 "description": "List of rules on the access control list.",
92 "items": {
93 "$ref": "AclRule"
94 }
95 },
96 "kind": {
97 "type": "string",
98 "description": "Type of the collection (\"calendar#acl\").",
99 "default": "calendar#acl"
100 },
101 "nextPageToken": {
102 "type": "string",
103 "description": "Token used to access the next page of this result. Omitted if no further results are available, in which case nextSyncToken is provided."
104 },
105 "nextSyncToken": {
106 "type": "string",
107 "description": "Token used at a later point in time to retrieve only the entries that have changed since this result was returned. Omitted if further results are available, in which case nextPageToken is provided."
108 }
109 }
110 },
111 "AclRule": {
112 "id": "AclRule",
113 "type": "object",
114 "properties": {
115 "etag": {
116 "type": "string",
117 "description": "ETag of the resource."
118 },
119 "id": {
120 "type": "string",
121 "description": "Identifier of the ACL rule."
122 },
123 "kind": {
124 "type": "string",
125 "description": "Type of the resource (\"calendar#aclRule\").",
126 "default": "calendar#aclRule"
127 },
128 "role": {
129 "type": "string",
130 "description": "The role assigned to the scope. Possible values are: \n- \"none\" - Provides no access. \n- \"freeBusyReader\" - Provides read access to free/busy information. \n- \"reader\" - Provides read access to the calendar. Private events will appear to users with reader access, but event details will be hidden. \n- \"writer\" - Provides read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible. \n- \"owner\" - Provides ownership of the calendar. This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs.",
131 "annotations": {
132 "required": [
133 "calendar.acl.insert"
134 ]
135 }
136 },
137 "scope": {
138 "type": "object",
139 "description": "The scope of the rule.",
140 "properties": {
141 "type": {
142 "type": "string",
143 "description": "The type of the scope. Possible values are: \n- \"default\" - The public scope. This is the default value. \n- \"user\" - Limits the scope to a single user. \n- \"group\" - Limits the scope to a group. \n- \"domain\" - Limits the scope to a domain. Note: The permissions granted to the \"default\", or public, scope apply to any user, authenticated or not.",
144 "annotations": {
145 "required": [
146 "calendar.acl.insert"
147 ]
148 }
149 },
150 "value": {
151 "type": "string",
152 "description": "The email address of a user or group, or the name of a domain, depending on the scope type. Omitted for type \"default\"."
153 }
154 },
155 "annotations": {
156 "required": [
157 "calendar.acl.insert"
158 ]
159 }
160 }
161 }
162 },
163 "Calendar": {
164 "id": "Calendar",
165 "type": "object",
166 "properties": {
167 "conferenceProperties": {
168 "$ref": "ConferenceProperties",
169 "description": "Conferencing properties for this calendar, for example what types of conferences are allowed."
170 },
171 "description": {
172 "type": "string",
173 "description": "Description of the calendar. Optional."
174 },
175 "etag": {
176 "type": "string",
177 "description": "ETag of the resource."
178 },
179 "id": {
180 "type": "string",
181 "description": "Identifier of the calendar. To retrieve IDs call the calendarList.list() method."
182 },
183 "kind": {
184 "type": "string",
185 "description": "Type of the resource (\"calendar#calendar\").",
186 "default": "calendar#calendar"
187 },
188 "location": {
189 "type": "string",
190 "description": "Geographic location of the calendar as free-form text. Optional."
191 },
192 "summary": {
193 "type": "string",
194 "description": "Title of the calendar.",
195 "annotations": {
196 "required": [
197 "calendar.calendars.insert"
198 ]
199 }
200 },
201 "timeZone": {
202 "type": "string",
203 "description": "The time zone of the calendar. (Formatted as an IANA Time Zone Database name, e.g. \"Europe/Zurich\".) Optional."
204 }
205 }
206 },
207 "CalendarList": {
208 "id": "CalendarList",
209 "type": "object",
210 "properties": {
211 "etag": {
212 "type": "string",
213 "description": "ETag of the collection."
214 },
215 "items": {
216 "type": "array",
217 "description": "Calendars that are present on the user's calendar list.",
218 "items": {
219 "$ref": "CalendarListEntry"
220 }
221 },
222 "kind": {
223 "type": "string",
224 "description": "Type of the collection (\"calendar#calendarList\").",
225 "default": "calendar#calendarList"
226 },
227 "nextPageToken": {
228 "type": "string",
229 "description": "Token used to access the next page of this result. Omitted if no further results are available, in which case nextSyncToken is provided."
230 },
231 "nextSyncToken": {
232 "type": "string",
233 "description": "Token used at a later point in time to retrieve only the entries that have changed since this result was returned. Omitted if further results are available, in which case nextPageToken is provided."
234 }
235 }
236 },
237 "CalendarListEntry": {
238 "id": "CalendarListEntry",
239 "type": "object",
240 "properties": {
241 "accessRole": {
242 "type": "string",
243 "description": "The effective access role that the authenticated user has on the calendar. Read-only. Possible values are: \n- \"freeBusyReader\" - Provides read access to free/busy information. \n- \"reader\" - Provides read access to the calendar. Private events will appear to users with reader access, but event details will be hidden. \n- \"writer\" - Provides read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible. \n- \"owner\" - Provides ownership of the calendar. This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs."
244 },
245 "backgroundColor": {
246 "type": "string",
247 "description": "The main color of the calendar in the hexadecimal format \"#0088aa\". This property supersedes the index-based colorId property. To set or change this property, you need to specify colorRgbFormat=true in the parameters of the insert, update and patch methods. Optional."
248 },
249 "colorId": {
250 "type": "string",
251 "description": "The color of the calendar. This is an ID referring to an entry in the calendar section of the colors definition (see the colors endpoint). This property is superseded by the backgroundColor and foregroundColor properties and can be ignored when using these properties. Optional."
252 },
253 "conferenceProperties": {
254 "$ref": "ConferenceProperties",
255 "description": "Conferencing properties for this calendar, for example what types of conferences are allowed."
256 },
257 "defaultReminders": {
258 "type": "array",
259 "description": "The default reminders that the authenticated user has for this calendar.",
260 "items": {
261 "$ref": "EventReminder"
262 }
263 },
264 "deleted": {
265 "type": "boolean",
266 "description": "Whether this calendar list entry has been deleted from the calendar list. Read-only. Optional. The default is False.",
267 "default": "false"
268 },
269 "description": {
270 "type": "string",
271 "description": "Description of the calendar. Optional. Read-only."
272 },
273 "etag": {
274 "type": "string",
275 "description": "ETag of the resource."
276 },
277 "foregroundColor": {
278 "type": "string",
279 "description": "The foreground color of the calendar in the hexadecimal format \"#ffffff\". This property supersedes the index-based colorId property. To set or change this property, you need to specify colorRgbFormat=true in the parameters of the insert, update and patch methods. Optional."
280 },
281 "hidden": {
282 "type": "boolean",
283 "description": "Whether the calendar has been hidden from the list. Optional. The default is False.",
284 "default": "false"
285 },
286 "id": {
287 "type": "string",
288 "description": "Identifier of the calendar.",
289 "annotations": {
290 "required": [
291 "calendar.calendarList.insert"
292 ]
293 }
294 },
295 "kind": {
296 "type": "string",
297 "description": "Type of the resource (\"calendar#calendarListEntry\").",
298 "default": "calendar#calendarListEntry"
299 },
300 "location": {
301 "type": "string",
302 "description": "Geographic location of the calendar as free-form text. Optional. Read-only."
303 },
304 "notificationSettings": {
305 "type": "object",
306 "description": "The notifications that the authenticated user is receiving for this calendar.",
307 "properties": {
308 "notifications": {
309 "type": "array",
310 "description": "The list of notifications set for this calendar.",
311 "items": {
312 "$ref": "CalendarNotification"
313 }
314 }
315 }
316 },
317 "primary": {
318 "type": "boolean",
319 "description": "Whether the calendar is the primary calendar of the authenticated user. Read-only. Optional. The default is False.",
320 "default": "false"
321 },
322 "selected": {
323 "type": "boolean",
324 "description": "Whether the calendar content shows up in the calendar UI. Optional. The default is False.",
325 "default": "false"
326 },
327 "summary": {
328 "type": "string",
329 "description": "Title of the calendar. Read-only."
330 },
331 "summaryOverride": {
332 "type": "string",
333 "description": "The summary that the authenticated user has set for this calendar. Optional."
334 },
335 "timeZone": {
336 "type": "string",
337 "description": "The time zone of the calendar. Optional. Read-only."
338 }
339 }
340 },
341 "CalendarNotification": {
342 "id": "CalendarNotification",
343 "type": "object",
344 "properties": {
345 "method": {
346 "type": "string",
347 "description": "The method used to deliver the notification. Possible values are: \n- \"email\" - Reminders are sent via email. \n- \"sms\" - Reminders are sent via SMS. This value is read-only and is ignored on inserts and updates. SMS reminders are only available for G Suite customers.",
348 "annotations": {
349 "required": [
350 "calendar.calendarList.insert",
351 "calendar.calendarList.update"
352 ]
353 }
354 },
355 "type": {
356 "type": "string",
357 "description": "The type of notification. Possible values are: \n- \"eventCreation\" - Notification sent when a new event is put on the calendar. \n- \"eventChange\" - Notification sent when an event is changed. \n- \"eventCancellation\" - Notification sent when an event is cancelled. \n- \"eventResponse\" - Notification sent when an event is changed. \n- \"agenda\" - An agenda with the events of the day (sent out in the morning).",
358 "annotations": {
359 "required": [
360 "calendar.calendarList.insert",
361 "calendar.calendarList.update"
362 ]
363 }
364 }
365 }
366 },
367 "Channel": {
368 "id": "Channel",
369 "type": "object",
370 "properties": {
371 "address": {
372 "type": "string",
373 "description": "The address where notifications are delivered for this channel."
374 },
375 "expiration": {
376 "type": "string",
377 "description": "Date and time of notification channel expiration, expressed as a Unix timestamp, in milliseconds. Optional.",
378 "format": "int64"
379 },
380 "id": {
381 "type": "string",
382 "description": "A UUID or similar unique string that identifies this channel."
383 },
384 "kind": {
385 "type": "string",
386 "description": "Identifies this as a notification channel used to watch for changes to a resource. Value: the fixed string \"api#channel\".",
387 "default": "api#channel"
388 },
389 "params": {
390 "type": "object",
391 "description": "Additional parameters controlling delivery channel behavior. Optional.",
392 "additionalProperties": {
393 "type": "string",
394 "description": "Declares a new parameter by name."
395 }
396 },
397 "payload": {
398 "type": "boolean",
399 "description": "A Boolean value to indicate whether payload is wanted. Optional."
400 },
401 "resourceId": {
402 "type": "string",
403 "description": "An opaque ID that identifies the resource being watched on this channel. Stable across different API versions."
404 },
405 "resourceUri": {
406 "type": "string",
407 "description": "A version-specific identifier for the watched resource."
408 },
409 "token": {
410 "type": "string",
411 "description": "An arbitrary string delivered to the target address with each notification delivered over this channel. Optional."
412 },
413 "type": {
414 "type": "string",
415 "description": "The type of delivery mechanism used for this channel."
416 }
417 }
418 },
419 "ColorDefinition": {
420 "id": "ColorDefinition",
421 "type": "object",
422 "properties": {
423 "background": {
424 "type": "string",
425 "description": "The background color associated with this color definition."
426 },
427 "foreground": {
428 "type": "string",
429 "description": "The foreground color that can be used to write on top of a background with 'background' color."
430 }
431 }
432 },
433 "Colors": {
434 "id": "Colors",
435 "type": "object",
436 "properties": {
437 "calendar": {
438 "type": "object",
439 "description": "A global palette of calendar colors, mapping from the color ID to its definition. A calendarListEntry resource refers to one of these color IDs in its color field. Read-only.",
440 "additionalProperties": {
441 "$ref": "ColorDefinition",
442 "description": "A calendar color defintion."
443 }
444 },
445 "event": {
446 "type": "object",
447 "description": "A global palette of event colors, mapping from the color ID to its definition. An event resource may refer to one of these color IDs in its color field. Read-only.",
448 "additionalProperties": {
449 "$ref": "ColorDefinition",
450 "description": "An event color definition."
451 }
452 },
453 "kind": {
454 "type": "string",
455 "description": "Type of the resource (\"calendar#colors\").",
456 "default": "calendar#colors"
457 },
458 "updated": {
459 "type": "string",
460 "description": "Last modification time of the color palette (as a RFC3339 timestamp). Read-only.",
461 "format": "date-time"
462 }
463 }
464 },
465 "ConferenceData": {
466 "id": "ConferenceData",
467 "type": "object",
468 "properties": {
469 "conferenceId": {
470 "type": "string",
471 "description": "The ID of the conference.\nCan be used by developers to keep track of conferences, should not be displayed to users.\nValues for solution types: \n- \"eventHangout\": unset.\n- \"eventNamedHangout\": the name of the Hangout.\n- \"hangoutsMeet\": the 10-letter meeting code, for example \"aaa-bbbb-ccc\". Optional."
472 },
473 "conferenceSolution": {
474 "$ref": "ConferenceSolution",
475 "description": "The conference solution, such as Hangouts or Hangouts Meet.\nUnset for a conference with a failed create request.\nEither conferenceSolution and at least one entryPoint, or createRequest is required."
476 },
477 "createRequest": {
478 "$ref": "CreateConferenceRequest",
479 "description": "A request to generate a new conference and attach it to the event. The data is generated asynchronously. To see whether the data is present check the status field.\nEither conferenceSolution and at least one entryPoint, or createRequest is required."
480 },
481 "entryPoints": {
482 "type": "array",
483 "description": "Information about individual conference entry points, such as URLs or phone numbers.\nAll of them must belong to the same conference.\nEither conferenceSolution and at least one entryPoint, or createRequest is required.",
484 "items": {
485 "$ref": "EntryPoint"
486 }
487 },
488 "notes": {
489 "type": "string",
490 "description": "Additional notes (such as instructions from the domain administrator, legal notices) to display to the user. Can contain HTML. The maximum length is 2048 characters. Optional."
491 },
492 "parameters": {
493 "$ref": "ConferenceParameters",
494 "description": "Additional properties related to a conference. An example would be a solution-specific setting for enabling video streaming."
495 },
496 "signature": {
497 "type": "string",
498 "description": "The signature of the conference data.\nGenereated on server side. Must be preserved while copying the conference data between events, otherwise the conference data will not be copied.\nUnset for a conference with a failed create request.\nOptional for a conference with a pending create request."
499 }
500 }
501 },
502 "ConferenceParameters": {
503 "id": "ConferenceParameters",
504 "type": "object",
505 "properties": {
506 "addOnParameters": {
507 "$ref": "ConferenceParametersAddOnParameters",
508 "description": "Additional add-on specific data."
509 }
510 }
511 },
512 "ConferenceParametersAddOnParameters": {
513 "id": "ConferenceParametersAddOnParameters",
514 "type": "object",
515 "properties": {
516 "parameters": {
517 "type": "object",
518 "additionalProperties": {
519 "type": "string"
520 }
521 }
522 }
523 },
524 "ConferenceProperties": {
525 "id": "ConferenceProperties",
526 "type": "object",
527 "properties": {
528 "allowedConferenceSolutionTypes": {
529 "type": "array",
530 "description": "The types of conference solutions that are supported for this calendar.\nThe possible values are: \n- \"eventHangout\" \n- \"eventNamedHangout\" \n- \"hangoutsMeet\" Optional.",
531 "items": {
532 "type": "string"
533 }
534 }
535 }
536 },
537 "ConferenceRequestStatus": {
538 "id": "ConferenceRequestStatus",
539 "type": "object",
540 "properties": {
541 "statusCode": {
542 "type": "string",
543 "description": "The current status of the conference create request. Read-only.\nThe possible values are: \n- \"pending\": the conference create request is still being processed.\n- \"success\": the conference create request succeeded, the entry points are populated.\n- \"failure\": the conference create request failed, there are no entry points."
544 }
545 }
546 },
547 "ConferenceSolution": {
548 "id": "ConferenceSolution",
549 "type": "object",
550 "properties": {
551 "iconUri": {
552 "type": "string",
553 "description": "The user-visible icon for this solution."
554 },
555 "key": {
556 "$ref": "ConferenceSolutionKey",
557 "description": "The key which can uniquely identify the conference solution for this event."
558 },
559 "name": {
560 "type": "string",
561 "description": "The user-visible name of this solution. Not localized."
562 }
563 }
564 },
565 "ConferenceSolutionKey": {
566 "id": "ConferenceSolutionKey",
567 "type": "object",
568 "properties": {
569 "type": {
570 "type": "string",
571 "description": "The conference solution type.\nIf a client encounters an unfamiliar or empty type, it should still be able to display the entry points. However, it should disallow modifications.\nThe possible values are: \n- \"eventHangout\" for Hangouts for consumers (http://hangouts.google.com)\n- \"eventNamedHangout\" for classic Hangouts for G Suite users (http://hangouts.google.com)\n- \"hangoutsMeet\" for Hangouts Meet (http://meet.google.com)"
572 }
573 }
574 },
575 "CreateConferenceRequest": {
576 "id": "CreateConferenceRequest",
577 "type": "object",
578 "properties": {
579 "conferenceSolutionKey": {
580 "$ref": "ConferenceSolutionKey",
581 "description": "The conference solution, such as Hangouts or Hangouts Meet."
582 },
583 "requestId": {
584 "type": "string",
585 "description": "The client-generated unique ID for this request.\nClients should regenerate this ID for every new request. If an ID provided is the same as for the previous request, the request is ignored."
586 },
587 "status": {
588 "$ref": "ConferenceRequestStatus",
589 "description": "The status of the conference create request."
590 }
591 }
592 },
593 "EntryPoint": {
594 "id": "EntryPoint",
595 "type": "object",
596 "properties": {
597 "accessCode": {
598 "type": "string",
599 "description": "The access code to access the conference. The maximum length is 128 characters.\nWhen creating new conference data, populate only the subset of {meetingCode, accessCode, passcode, password, pin} fields that match the terminology that the conference provider uses. Only the populated fields should be displayed.\nOptional."
600 },
601 "entryPointType": {
602 "type": "string",
603 "description": "The type of the conference entry point.\nPossible values are: \n- \"video\" - joining a conference over HTTP. A conference can have zero or one video entry point.\n- \"phone\" - joining a conference by dialing a phone number. A conference can have zero or more phone entry points.\n- \"sip\" - joining a conference over SIP. A conference can have zero or one sip entry point.\n- \"more\" - further conference joining instructions, for example additional phone numbers. A conference can have zero or one more entry point. A conference with only a more entry point is not a valid conference."
604 },
605 "label": {
606 "type": "string",
607 "description": "The label for the URI. Visible to end users. Not localized. The maximum length is 512 characters.\nExamples: \n- for video: meet.google.com/aaa-bbbb-ccc\n- for phone: +1 123 268 2601\n- for sip: 12345678@altostrat.com\n- for more: should not be filled \nOptional."
608 },
609 "meetingCode": {
610 "type": "string",
611 "description": "The meeting code to access the conference. The maximum length is 128 characters.\nWhen creating new conference data, populate only the subset of {meetingCode, accessCode, passcode, password, pin} fields that match the terminology that the conference provider uses. Only the populated fields should be displayed.\nOptional."
612 },
613 "passcode": {
614 "type": "string",
615 "description": "The passcode to access the conference. The maximum length is 128 characters.\nWhen creating new conference data, populate only the subset of {meetingCode, accessCode, passcode, password, pin} fields that match the terminology that the conference provider uses. Only the populated fields should be displayed."
616 },
617 "password": {
618 "type": "string",
619 "description": "The password to access the conference. The maximum length is 128 characters.\nWhen creating new conference data, populate only the subset of {meetingCode, accessCode, passcode, password, pin} fields that match the terminology that the conference provider uses. Only the populated fields should be displayed.\nOptional."
620 },
621 "pin": {
622 "type": "string",
623 "description": "The PIN to access the conference. The maximum length is 128 characters.\nWhen creating new conference data, populate only the subset of {meetingCode, accessCode, passcode, password, pin} fields that match the terminology that the conference provider uses. Only the populated fields should be displayed.\nOptional."
624 },
625 "uri": {
626 "type": "string",
627 "description": "The URI of the entry point. The maximum length is 1300 characters.\nFormat: \n- for video, http: or https: schema is required.\n- for phone, tel: schema is required. The URI should include the entire dial sequence (e.g., tel:+12345678900,,,123456789;1234).\n- for sip, sip: schema is required, e.g., sip:12345678@myprovider.com.\n- for more, http: or https: schema is required."
628 }
629 }
630 },
631 "Error": {
632 "id": "Error",
633 "type": "object",
634 "properties": {
635 "domain": {
636 "type": "string",
637 "description": "Domain, or broad category, of the error."
638 },
639 "reason": {
640 "type": "string",
641 "description": "Specific reason for the error. Some of the possible values are: \n- \"groupTooBig\" - The group of users requested is too large for a single query. \n- \"tooManyCalendarsRequested\" - The number of calendars requested is too large for a single query. \n- \"notFound\" - The requested resource was not found. \n- \"internalError\" - The API service has encountered an internal error. Additional error types may be added in the future, so clients should gracefully handle additional error statuses not included in this list."
642 }
643 }
644 },
645 "Event": {
646 "id": "Event",
647 "type": "object",
648 "properties": {
649 "anyoneCanAddSelf": {
650 "type": "boolean",
651 "description": "Whether anyone can invite themselves to the event (currently works for Google+ events only). Optional. The default is False.",
652 "default": "false"
653 },
654 "attachments": {
655 "type": "array",
656 "description": "File attachments for the event. Currently only Google Drive attachments are supported.\nIn order to modify attachments the supportsAttachments request parameter should be set to true.\nThere can be at most 25 attachments per event,",
657 "items": {
658 "$ref": "EventAttachment"
659 }
660 },
661 "attendees": {
662 "type": "array",
663 "description": "The attendees of the event. See the Events with attendees guide for more information on scheduling events with other calendar users.",
664 "items": {
665 "$ref": "EventAttendee"
666 }
667 },
668 "attendeesOmitted": {
669 "type": "boolean",
670 "description": "Whether attendees may have been omitted from the event's representation. When retrieving an event, this may be due to a restriction specified by the maxAttendee query parameter. When updating an event, this can be used to only update the participant's response. Optional. The default is False.",
671 "default": "false"
672 },
673 "colorId": {
674 "type": "string",
675 "description": "The color of the event. This is an ID referring to an entry in the event section of the colors definition (see the colors endpoint). Optional."
676 },
677 "conferenceData": {
678 "$ref": "ConferenceData",
679 "description": "The conference-related information, such as details of a Hangouts Meet conference. To create new conference details use the createRequest field. To persist your changes, remember to set the conferenceDataVersion request parameter to 1 for all event modification requests."
680 },
681 "created": {
682 "type": "string",
683 "description": "Creation time of the event (as a RFC3339 timestamp). Read-only.",
684 "format": "date-time"
685 },
686 "creator": {
687 "type": "object",
688 "description": "The creator of the event. Read-only.",
689 "properties": {
690 "displayName": {
691 "type": "string",
692 "description": "The creator's name, if available."
693 },
694 "email": {
695 "type": "string",
696 "description": "The creator's email address, if available."
697 },
698 "id": {
699 "type": "string",
700 "description": "The creator's Profile ID, if available. It corresponds to theid field in the People collection of the Google+ API"
701 },
702 "self": {
703 "type": "boolean",
704 "description": "Whether the creator corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.",
705 "default": "false"
706 }
707 }
708 },
709 "description": {
710 "type": "string",
711 "description": "Description of the event. Optional."
712 },
713 "end": {
714 "$ref": "EventDateTime",
715 "description": "The (exclusive) end time of the event. For a recurring event, this is the end time of the first instance.",
716 "annotations": {
717 "required": [
718 "calendar.events.import",
719 "calendar.events.insert",
720 "calendar.events.update"
721 ]
722 }
723 },
724 "endTimeUnspecified": {
725 "type": "boolean",
726 "description": "Whether the end time is actually unspecified. An end time is still provided for compatibility reasons, even if this attribute is set to True. The default is False.",
727 "default": "false"
728 },
729 "etag": {
730 "type": "string",
731 "description": "ETag of the resource."
732 },
733 "extendedProperties": {
734 "type": "object",
735 "description": "Extended properties of the event.",
736 "properties": {
737 "private": {
738 "type": "object",
739 "description": "Properties that are private to the copy of the event that appears on this calendar.",
740 "additionalProperties": {
741 "type": "string",
742 "description": "The name of the private property and the corresponding value."
743 }
744 },
745 "shared": {
746 "type": "object",
747 "description": "Properties that are shared between copies of the event on other attendees' calendars.",
748 "additionalProperties": {
749 "type": "string",
750 "description": "The name of the shared property and the corresponding value."
751 }
752 }
753 }
754 },
755 "gadget": {
756 "type": "object",
757 "description": "A gadget that extends this event.",
758 "properties": {
759 "display": {
760 "type": "string",
761 "description": "The gadget's display mode. Optional. Possible values are: \n- \"icon\" - The gadget displays next to the event's title in the calendar view. \n- \"chip\" - The gadget displays when the event is clicked."
762 },
763 "height": {
764 "type": "integer",
765 "description": "The gadget's height in pixels. The height must be an integer greater than 0. Optional.",
766 "format": "int32"
767 },
768 "iconLink": {
769 "type": "string",
770 "description": "The gadget's icon URL. The URL scheme must be HTTPS."
771 },
772 "link": {
773 "type": "string",
774 "description": "The gadget's URL. The URL scheme must be HTTPS."
775 },
776 "preferences": {
777 "type": "object",
778 "description": "Preferences.",
779 "additionalProperties": {
780 "type": "string",
781 "description": "The preference name and corresponding value."
782 }
783 },
784 "title": {
785 "type": "string",
786 "description": "The gadget's title."
787 },
788 "type": {
789 "type": "string",
790 "description": "The gadget's type."
791 },
792 "width": {
793 "type": "integer",
794 "description": "The gadget's width in pixels. The width must be an integer greater than 0. Optional.",
795 "format": "int32"
796 }
797 }
798 },
799 "guestsCanInviteOthers": {
800 "type": "boolean",
801 "description": "Whether attendees other than the organizer can invite others to the event. Optional. The default is True.",
802 "default": "true"
803 },
804 "guestsCanModify": {
805 "type": "boolean",
806 "description": "Whether attendees other than the organizer can modify the event. Optional. The default is False.",
807 "default": "false"
808 },
809 "guestsCanSeeOtherGuests": {
810 "type": "boolean",
811 "description": "Whether attendees other than the organizer can see who the event's attendees are. Optional. The default is True.",
812 "default": "true"
813 },
814 "hangoutLink": {
815 "type": "string",
816 "description": "An absolute link to the Google+ hangout associated with this event. Read-only."
817 },
818 "htmlLink": {
819 "type": "string",
820 "description": "An absolute link to this event in the Google Calendar Web UI. Read-only."
821 },
822 "iCalUID": {
823 "type": "string",
824 "description": "Event unique identifier as defined in RFC5545. It is used to uniquely identify events accross calendaring systems and must be supplied when importing events via the import method.\nNote that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs.",
825 "annotations": {
826 "required": [
827 "calendar.events.import"
828 ]
829 }
830 },
831 "id": {
832 "type": "string",
833 "description": "Opaque identifier of the event. When creating new single or recurring events, you can specify their IDs. Provided IDs must follow these rules: \n- characters allowed in the ID are those used in base32hex encoding, i.e. lowercase letters a-v and digits 0-9, see section 3.1.2 in RFC2938 \n- the length of the ID must be between 5 and 1024 characters \n- the ID must be unique per calendar Due to the globally distributed nature of the system, we cannot guarantee that ID collisions will be detected at event creation time. To minimize the risk of collisions we recommend using an established UUID algorithm such as one described in RFC4122.\nIf you do not specify an ID, it will be automatically generated by the server.\nNote that the icalUID and the id are not identical and only one of them should be supplied at event creation time. One difference in their semantics is that in recurring events, all occurrences of one event have different ids while they all share the same icalUIDs."
834 },
835 "kind": {
836 "type": "string",
837 "description": "Type of the resource (\"calendar#event\").",
838 "default": "calendar#event"
839 },
840 "location": {
841 "type": "string",
842 "description": "Geographic location of the event as free-form text. Optional."
843 },
844 "locked": {
845 "type": "boolean",
846 "description": "Whether this is a locked event copy where no changes can be made to the main event fields \"summary\", \"description\", \"location\", \"start\", \"end\" or \"recurrence\". The default is False. Read-Only.",
847 "default": "false"
848 },
849 "organizer": {
850 "type": "object",
851 "description": "The organizer of the event. If the organizer is also an attendee, this is indicated with a separate entry in attendees with the organizer field set to True. To change the organizer, use the move operation. Read-only, except when importing an event.",
852 "properties": {
853 "displayName": {
854 "type": "string",
855 "description": "The organizer's name, if available."
856 },
857 "email": {
858 "type": "string",
859 "description": "The organizer's email address, if available. It must be a valid email address as per RFC5322."
860 },
861 "id": {
862 "type": "string",
863 "description": "The organizer's Profile ID, if available. It corresponds to theid field in the People collection of the Google+ API"
864 },
865 "self": {
866 "type": "boolean",
867 "description": "Whether the organizer corresponds to the calendar on which this copy of the event appears. Read-only. The default is False.",
868 "default": "false"
869 }
870 }
871 },
872 "originalStartTime": {
873 "$ref": "EventDateTime",
874 "description": "For an instance of a recurring event, this is the time at which this event would start according to the recurrence data in the recurring event identified by recurringEventId. Immutable."
875 },
876 "privateCopy": {
877 "type": "boolean",
878 "description": "Whether this is a private event copy where changes are not shared with other copies on other calendars. Optional. Immutable. The default is False.",
879 "default": "false"
880 },
881 "recurrence": {
882 "type": "array",
883 "description": "List of RRULE, EXRULE, RDATE and EXDATE lines for a recurring event, as specified in RFC5545. Note that DTSTART and DTEND lines are not allowed in this field; event start and end times are specified in the start and end fields. This field is omitted for single events or instances of recurring events.",
884 "items": {
885 "type": "string"
886 }
887 },
888 "recurringEventId": {
889 "type": "string",
890 "description": "For an instance of a recurring event, this is the id of the recurring event to which this instance belongs. Immutable."
891 },
892 "reminders": {
893 "type": "object",
894 "description": "Information about the event's reminders for the authenticated user.",
895 "properties": {
896 "overrides": {
897 "type": "array",
898 "description": "If the event doesn't use the default reminders, this lists the reminders specific to the event, or, if not set, indicates that no reminders are set for this event. The maximum number of override reminders is 5.",
899 "items": {
900 "$ref": "EventReminder"
901 }
902 },
903 "useDefault": {
904 "type": "boolean",
905 "description": "Whether the default reminders of the calendar apply to the event."
906 }
907 }
908 },
909 "sequence": {
910 "type": "integer",
911 "description": "Sequence number as per iCalendar.",
912 "format": "int32"
913 },
914 "source": {
915 "type": "object",
916 "description": "Source from which the event was created. For example, a web page, an email message or any document identifiable by an URL with HTTP or HTTPS scheme. Can only be seen or modified by the creator of the event.",
917 "properties": {
918 "title": {
919 "type": "string",
920 "description": "Title of the source; for example a title of a web page or an email subject."
921 },
922 "url": {
923 "type": "string",
924 "description": "URL of the source pointing to a resource. The URL scheme must be HTTP or HTTPS."
925 }
926 }
927 },
928 "start": {
929 "$ref": "EventDateTime",
930 "description": "The (inclusive) start time of the event. For a recurring event, this is the start time of the first instance.",
931 "annotations": {
932 "required": [
933 "calendar.events.import",
934 "calendar.events.insert",
935 "calendar.events.update"
936 ]
937 }
938 },
939 "status": {
940 "type": "string",
941 "description": "Status of the event. Optional. Possible values are: \n- \"confirmed\" - The event is confirmed. This is the default status. \n- \"tentative\" - The event is tentatively confirmed. \n- \"cancelled\" - The event is cancelled."
942 },
943 "summary": {
944 "type": "string",
945 "description": "Title of the event."
946 },
947 "transparency": {
948 "type": "string",
949 "description": "Whether the event blocks time on the calendar. Optional. Possible values are: \n- \"opaque\" - Default value. The event does block time on the calendar. This is equivalent to setting Show me as to Busy in the Calendar UI. \n- \"transparent\" - The event does not block time on the calendar. This is equivalent to setting Show me as to Available in the Calendar UI.",
950 "default": "opaque"
951 },
952 "updated": {
953 "type": "string",
954 "description": "Last modification time of the event (as a RFC3339 timestamp). Read-only.",
955 "format": "date-time"
956 },
957 "visibility": {
958 "type": "string",
959 "description": "Visibility of the event. Optional. Possible values are: \n- \"default\" - Uses the default visibility for events on the calendar. This is the default value. \n- \"public\" - The event is public and event details are visible to all readers of the calendar. \n- \"private\" - The event is private and only event attendees may view event details. \n- \"confidential\" - The event is private. This value is provided for compatibility reasons.",
960 "default": "default"
961 }
962 }
963 },
964 "EventAttachment": {
965 "id": "EventAttachment",
966 "type": "object",
967 "properties": {
968 "fileId": {
969 "type": "string",
970 "description": "ID of the attached file. Read-only.\nFor Google Drive files, this is the ID of the corresponding Files resource entry in the Drive API."
971 },
972 "fileUrl": {
973 "type": "string",
974 "description": "URL link to the attachment.\nFor adding Google Drive file attachments use the same format as in alternateLink property of the Files resource in the Drive API.",
975 "annotations": {
976 "required": [
977 "calendar.events.import",
978 "calendar.events.insert",
979 "calendar.events.update"
980 ]
981 }
982 },
983 "iconLink": {
984 "type": "string",
985 "description": "URL link to the attachment's icon. Read-only."
986 },
987 "mimeType": {
988 "type": "string",
989 "description": "Internet media type (MIME type) of the attachment."
990 },
991 "title": {
992 "type": "string",
993 "description": "Attachment title."
994 }
995 }
996 },
997 "EventAttendee": {
998 "id": "EventAttendee",
999 "type": "object",
1000 "properties": {
1001 "additionalGuests": {
1002 "type": "integer",
1003 "description": "Number of additional guests. Optional. The default is 0.",
1004 "default": "0",
1005 "format": "int32"
1006 },
1007 "comment": {
1008 "type": "string",
1009 "description": "The attendee's response comment. Optional."
1010 },
1011 "displayName": {
1012 "type": "string",
1013 "description": "The attendee's name, if available. Optional."
1014 },
1015 "email": {
1016 "type": "string",
1017 "description": "The attendee's email address, if available. This field must be present when adding an attendee. It must be a valid email address as per RFC5322.",
1018 "annotations": {
1019 "required": [
1020 "calendar.events.import",
1021 "calendar.events.insert",
1022 "calendar.events.update"
1023 ]
1024 }
1025 },
1026 "id": {
1027 "type": "string",
1028 "description": "The attendee's Profile ID, if available. It corresponds to theid field in the People collection of the Google+ API"
1029 },
1030 "optional": {
1031 "type": "boolean",
1032 "description": "Whether this is an optional attendee. Optional. The default is False.",
1033 "default": "false"
1034 },
1035 "organizer": {
1036 "type": "boolean",
1037 "description": "Whether the attendee is the organizer of the event. Read-only. The default is False."
1038 },
1039 "resource": {
1040 "type": "boolean",
1041 "description": "Whether the attendee is a resource. Can only be set when the attendee is added to the event for the first time. Subsequent modifications are ignored. Optional. The default is False.",
1042 "default": "false"
1043 },
1044 "responseStatus": {
1045 "type": "string",
1046 "description": "The attendee's response status. Possible values are: \n- \"needsAction\" - The attendee has not responded to the invitation. \n- \"declined\" - The attendee has declined the invitation. \n- \"tentative\" - The attendee has tentatively accepted the invitation. \n- \"accepted\" - The attendee has accepted the invitation."
1047 },
1048 "self": {
1049 "type": "boolean",
1050 "description": "Whether this entry represents the calendar on which this copy of the event appears. Read-only. The default is False.",
1051 "default": "false"
1052 }
1053 }
1054 },
1055 "EventDateTime": {
1056 "id": "EventDateTime",
1057 "type": "object",
1058 "properties": {
1059 "date": {
1060 "type": "string",
1061 "description": "The date, in the format \"yyyy-mm-dd\", if this is an all-day event.",
1062 "format": "date"
1063 },
1064 "dateTime": {
1065 "type": "string",
1066 "description": "The time, as a combined date-time value (formatted according to RFC3339). A time zone offset is required unless a time zone is explicitly specified in timeZone.",
1067 "format": "date-time"
1068 },
1069 "timeZone": {
1070 "type": "string",
1071 "description": "The time zone in which the time is specified. (Formatted as an IANA Time Zone Database name, e.g. \"Europe/Zurich\".) For recurring events this field is required and specifies the time zone in which the recurrence is expanded. For single events this field is optional and indicates a custom time zone for the event start/end."
1072 }
1073 }
1074 },
1075 "EventReminder": {
1076 "id": "EventReminder",
1077 "type": "object",
1078 "properties": {
1079 "method": {
1080 "type": "string",
1081 "description": "The method used by this reminder. Possible values are: \n- \"email\" - Reminders are sent via email. \n- \"sms\" - Reminders are sent via SMS. These are only available for G Suite customers. Requests to set SMS reminders for other account types are ignored. \n- \"popup\" - Reminders are sent via a UI popup.",
1082 "annotations": {
1083 "required": [
1084 "calendar.calendarList.insert",
1085 "calendar.calendarList.update",
1086 "calendar.events.import",
1087 "calendar.events.insert",
1088 "calendar.events.update"
1089 ]
1090 }
1091 },
1092 "minutes": {
1093 "type": "integer",
1094 "description": "Number of minutes before the start of the event when the reminder should trigger. Valid values are between 0 and 40320 (4 weeks in minutes).",
1095 "format": "int32",
1096 "annotations": {
1097 "required": [
1098 "calendar.calendarList.insert",
1099 "calendar.calendarList.update",
1100 "calendar.events.import",
1101 "calendar.events.insert",
1102 "calendar.events.update"
1103 ]
1104 }
1105 }
1106 }
1107 },
1108 "Events": {
1109 "id": "Events",
1110 "type": "object",
1111 "properties": {
1112 "accessRole": {
1113 "type": "string",
1114 "description": "The user's access role for this calendar. Read-only. Possible values are: \n- \"none\" - The user has no access. \n- \"freeBusyReader\" - The user has read access to free/busy information. \n- \"reader\" - The user has read access to the calendar. Private events will appear to users with reader access, but event details will be hidden. \n- \"writer\" - The user has read and write access to the calendar. Private events will appear to users with writer access, and event details will be visible. \n- \"owner\" - The user has ownership of the calendar. This role has all of the permissions of the writer role with the additional ability to see and manipulate ACLs."
1115 },
1116 "defaultReminders": {
1117 "type": "array",
1118 "description": "The default reminders on the calendar for the authenticated user. These reminders apply to all events on this calendar that do not explicitly override them (i.e. do not have reminders.useDefault set to True).",
1119 "items": {
1120 "$ref": "EventReminder"
1121 }
1122 },
1123 "description": {
1124 "type": "string",
1125 "description": "Description of the calendar. Read-only."
1126 },
1127 "etag": {
1128 "type": "string",
1129 "description": "ETag of the collection."
1130 },
1131 "items": {
1132 "type": "array",
1133 "description": "List of events on the calendar.",
1134 "items": {
1135 "$ref": "Event"
1136 }
1137 },
1138 "kind": {
1139 "type": "string",
1140 "description": "Type of the collection (\"calendar#events\").",
1141 "default": "calendar#events"
1142 },
1143 "nextPageToken": {
1144 "type": "string",
1145 "description": "Token used to access the next page of this result. Omitted if no further results are available, in which case nextSyncToken is provided."
1146 },
1147 "nextSyncToken": {
1148 "type": "string",
1149 "description": "Token used at a later point in time to retrieve only the entries that have changed since this result was returned. Omitted if further results are available, in which case nextPageToken is provided."
1150 },
1151 "summary": {
1152 "type": "string",
1153 "description": "Title of the calendar. Read-only."
1154 },
1155 "timeZone": {
1156 "type": "string",
1157 "description": "The time zone of the calendar. Read-only."
1158 },
1159 "updated": {
1160 "type": "string",
1161 "description": "Last modification time of the calendar (as a RFC3339 timestamp). Read-only.",
1162 "format": "date-time"
1163 }
1164 }
1165 },
1166 "FreeBusyCalendar": {
1167 "id": "FreeBusyCalendar",
1168 "type": "object",
1169 "properties": {
1170 "busy": {
1171 "type": "array",
1172 "description": "List of time ranges during which this calendar should be regarded as busy.",
1173 "items": {
1174 "$ref": "TimePeriod"
1175 }
1176 },
1177 "errors": {
1178 "type": "array",
1179 "description": "Optional error(s) (if computation for the calendar failed).",
1180 "items": {
1181 "$ref": "Error"
1182 }
1183 }
1184 }
1185 },
1186 "FreeBusyGroup": {
1187 "id": "FreeBusyGroup",
1188 "type": "object",
1189 "properties": {
1190 "calendars": {
1191 "type": "array",
1192 "description": "List of calendars' identifiers within a group.",
1193 "items": {
1194 "type": "string"
1195 }
1196 },
1197 "errors": {
1198 "type": "array",
1199 "description": "Optional error(s) (if computation for the group failed).",
1200 "items": {
1201 "$ref": "Error"
1202 }
1203 }
1204 }
1205 },
1206 "FreeBusyRequest": {
1207 "id": "FreeBusyRequest",
1208 "type": "object",
1209 "properties": {
1210 "calendarExpansionMax": {
1211 "type": "integer",
1212 "description": "Maximal number of calendars for which FreeBusy information is to be provided. Optional.",
1213 "format": "int32"
1214 },
1215 "groupExpansionMax": {
1216 "type": "integer",
1217 "description": "Maximal number of calendar identifiers to be provided for a single group. Optional. An error will be returned for a group with more members than this value.",
1218 "format": "int32"
1219 },
1220 "items": {
1221 "type": "array",
1222 "description": "List of calendars and/or groups to query.",
1223 "items": {
1224 "$ref": "FreeBusyRequestItem"
1225 }
1226 },
1227 "timeMax": {
1228 "type": "string",
1229 "description": "The end of the interval for the query.",
1230 "format": "date-time"
1231 },
1232 "timeMin": {
1233 "type": "string",
1234 "description": "The start of the interval for the query.",
1235 "format": "date-time"
1236 },
1237 "timeZone": {
1238 "type": "string",
1239 "description": "Time zone used in the response. Optional. The default is UTC.",
1240 "default": "UTC"
1241 }
1242 }
1243 },
1244 "FreeBusyRequestItem": {
1245 "id": "FreeBusyRequestItem",
1246 "type": "object",
1247 "properties": {
1248 "id": {
1249 "type": "string",
1250 "description": "The identifier of a calendar or a group."
1251 }
1252 }
1253 },
1254 "FreeBusyResponse": {
1255 "id": "FreeBusyResponse",
1256 "type": "object",
1257 "properties": {
1258 "calendars": {
1259 "type": "object",
1260 "description": "List of free/busy information for calendars.",
1261 "additionalProperties": {
1262 "$ref": "FreeBusyCalendar",
1263 "description": "Free/busy expansions for a single calendar."
1264 }
1265 },
1266 "groups": {
1267 "type": "object",
1268 "description": "Expansion of groups.",
1269 "additionalProperties": {
1270 "$ref": "FreeBusyGroup",
1271 "description": "List of calendars that are members of this group."
1272 }
1273 },
1274 "kind": {
1275 "type": "string",
1276 "description": "Type of the resource (\"calendar#freeBusy\").",
1277 "default": "calendar#freeBusy"
1278 },
1279 "timeMax": {
1280 "type": "string",
1281 "description": "The end of the interval.",
1282 "format": "date-time"
1283 },
1284 "timeMin": {
1285 "type": "string",
1286 "description": "The start of the interval.",
1287 "format": "date-time"
1288 }
1289 }
1290 },
1291 "Setting": {
1292 "id": "Setting",
1293 "type": "object",
1294 "properties": {
1295 "etag": {
1296 "type": "string",
1297 "description": "ETag of the resource."
1298 },
1299 "id": {
1300 "type": "string",
1301 "description": "The id of the user setting."
1302 },
1303 "kind": {
1304 "type": "string",
1305 "description": "Type of the resource (\"calendar#setting\").",
1306 "default": "calendar#setting"
1307 },
1308 "value": {
1309 "type": "string",
1310 "description": "Value of the user setting. The format of the value depends on the ID of the setting. It must always be a UTF-8 string of length up to 1024 characters."
1311 }
1312 }
1313 },
1314 "Settings": {
1315 "id": "Settings",
1316 "type": "object",
1317 "properties": {
1318 "etag": {
1319 "type": "string",
1320 "description": "Etag of the collection."
1321 },
1322 "items": {
1323 "type": "array",
1324 "description": "List of user settings.",
1325 "items": {
1326 "$ref": "Setting"
1327 }
1328 },
1329 "kind": {
1330 "type": "string",
1331 "description": "Type of the collection (\"calendar#settings\").",
1332 "default": "calendar#settings"
1333 },
1334 "nextPageToken": {
1335 "type": "string",
1336 "description": "Token used to access the next page of this result. Omitted if no further results are available, in which case nextSyncToken is provided."
1337 },
1338 "nextSyncToken": {
1339 "type": "string",
1340 "description": "Token used at a later point in time to retrieve only the entries that have changed since this result was returned. Omitted if further results are available, in which case nextPageToken is provided."
1341 }
1342 }
1343 },
1344 "TimePeriod": {
1345 "id": "TimePeriod",
1346 "type": "object",
1347 "properties": {
1348 "end": {
1349 "type": "string",
1350 "description": "The (exclusive) end of the time period.",
1351 "format": "date-time"
1352 },
1353 "start": {
1354 "type": "string",
1355 "description": "The (inclusive) start of the time period.",
1356 "format": "date-time"
1357 }
1358 }
1359 }
1360 },
1361 "resources": {
1362 "acl": {
1363 "methods": {
1364 "delete": {
1365 "id": "calendar.acl.delete",
1366 "path": "calendars/{calendarId}/acl/{ruleId}",
1367 "httpMethod": "DELETE",
1368 "description": "Deletes an access control rule.",
1369 "parameters": {
1370 "calendarId": {
1371 "type": "string",
1372 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1373 "required": true,
1374 "location": "path"
1375 },
1376 "ruleId": {
1377 "type": "string",
1378 "description": "ACL rule identifier.",
1379 "required": true,
1380 "location": "path"
1381 }
1382 },
1383 "parameterOrder": [
1384 "calendarId",
1385 "ruleId"
1386 ],
1387 "scopes": [
1388 "https://www.googleapis.com/auth/calendar"
1389 ]
1390 },
1391 "get": {
1392 "id": "calendar.acl.get",
1393 "path": "calendars/{calendarId}/acl/{ruleId}",
1394 "httpMethod": "GET",
1395 "description": "Returns an access control rule.",
1396 "parameters": {
1397 "calendarId": {
1398 "type": "string",
1399 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1400 "required": true,
1401 "location": "path"
1402 },
1403 "ruleId": {
1404 "type": "string",
1405 "description": "ACL rule identifier.",
1406 "required": true,
1407 "location": "path"
1408 }
1409 },
1410 "parameterOrder": [
1411 "calendarId",
1412 "ruleId"
1413 ],
1414 "response": {
1415 "$ref": "AclRule"
1416 },
1417 "scopes": [
1418 "https://www.googleapis.com/auth/calendar",
1419 "https://www.googleapis.com/auth/calendar.readonly"
1420 ]
1421 },
1422 "insert": {
1423 "id": "calendar.acl.insert",
1424 "path": "calendars/{calendarId}/acl",
1425 "httpMethod": "POST",
1426 "description": "Creates an access control rule.",
1427 "parameters": {
1428 "calendarId": {
1429 "type": "string",
1430 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1431 "required": true,
1432 "location": "path"
1433 },
1434 "sendNotifications": {
1435 "type": "boolean",
1436 "description": "Whether to send notifications about the calendar sharing change. Optional. The default is True.",
1437 "location": "query"
1438 }
1439 },
1440 "parameterOrder": [
1441 "calendarId"
1442 ],
1443 "request": {
1444 "$ref": "AclRule"
1445 },
1446 "response": {
1447 "$ref": "AclRule"
1448 },
1449 "scopes": [
1450 "https://www.googleapis.com/auth/calendar"
1451 ]
1452 },
1453 "list": {
1454 "id": "calendar.acl.list",
1455 "path": "calendars/{calendarId}/acl",
1456 "httpMethod": "GET",
1457 "description": "Returns the rules in the access control list for the calendar.",
1458 "parameters": {
1459 "calendarId": {
1460 "type": "string",
1461 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1462 "required": true,
1463 "location": "path"
1464 },
1465 "maxResults": {
1466 "type": "integer",
1467 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
1468 "format": "int32",
1469 "minimum": "1",
1470 "location": "query"
1471 },
1472 "pageToken": {
1473 "type": "string",
1474 "description": "Token specifying which result page to return. Optional.",
1475 "location": "query"
1476 },
1477 "showDeleted": {
1478 "type": "boolean",
1479 "description": "Whether to include deleted ACLs in the result. Deleted ACLs are represented by role equal to \"none\". Deleted ACLs will always be included if syncToken is provided. Optional. The default is False.",
1480 "location": "query"
1481 },
1482 "syncToken": {
1483 "type": "string",
1484 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All entries deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
1485 "location": "query"
1486 }
1487 },
1488 "parameterOrder": [
1489 "calendarId"
1490 ],
1491 "response": {
1492 "$ref": "Acl"
1493 },
1494 "scopes": [
1495 "https://www.googleapis.com/auth/calendar"
1496 ],
1497 "supportsSubscription": true
1498 },
1499 "patch": {
1500 "id": "calendar.acl.patch",
1501 "path": "calendars/{calendarId}/acl/{ruleId}",
1502 "httpMethod": "PATCH",
1503 "description": "Updates an access control rule. This method supports patch semantics.",
1504 "parameters": {
1505 "calendarId": {
1506 "type": "string",
1507 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1508 "required": true,
1509 "location": "path"
1510 },
1511 "ruleId": {
1512 "type": "string",
1513 "description": "ACL rule identifier.",
1514 "required": true,
1515 "location": "path"
1516 },
1517 "sendNotifications": {
1518 "type": "boolean",
1519 "description": "Whether to send notifications about the calendar sharing change. Note that there are no notifications on access removal. Optional. The default is True.",
1520 "location": "query"
1521 }
1522 },
1523 "parameterOrder": [
1524 "calendarId",
1525 "ruleId"
1526 ],
1527 "request": {
1528 "$ref": "AclRule"
1529 },
1530 "response": {
1531 "$ref": "AclRule"
1532 },
1533 "scopes": [
1534 "https://www.googleapis.com/auth/calendar"
1535 ]
1536 },
1537 "update": {
1538 "id": "calendar.acl.update",
1539 "path": "calendars/{calendarId}/acl/{ruleId}",
1540 "httpMethod": "PUT",
1541 "description": "Updates an access control rule.",
1542 "parameters": {
1543 "calendarId": {
1544 "type": "string",
1545 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1546 "required": true,
1547 "location": "path"
1548 },
1549 "ruleId": {
1550 "type": "string",
1551 "description": "ACL rule identifier.",
1552 "required": true,
1553 "location": "path"
1554 },
1555 "sendNotifications": {
1556 "type": "boolean",
1557 "description": "Whether to send notifications about the calendar sharing change. Note that there are no notifications on access removal. Optional. The default is True.",
1558 "location": "query"
1559 }
1560 },
1561 "parameterOrder": [
1562 "calendarId",
1563 "ruleId"
1564 ],
1565 "request": {
1566 "$ref": "AclRule"
1567 },
1568 "response": {
1569 "$ref": "AclRule"
1570 },
1571 "scopes": [
1572 "https://www.googleapis.com/auth/calendar"
1573 ]
1574 },
1575 "watch": {
1576 "id": "calendar.acl.watch",
1577 "path": "calendars/{calendarId}/acl/watch",
1578 "httpMethod": "POST",
1579 "description": "Watch for changes to ACL resources.",
1580 "parameters": {
1581 "calendarId": {
1582 "type": "string",
1583 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1584 "required": true,
1585 "location": "path"
1586 },
1587 "maxResults": {
1588 "type": "integer",
1589 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
1590 "format": "int32",
1591 "minimum": "1",
1592 "location": "query"
1593 },
1594 "pageToken": {
1595 "type": "string",
1596 "description": "Token specifying which result page to return. Optional.",
1597 "location": "query"
1598 },
1599 "showDeleted": {
1600 "type": "boolean",
1601 "description": "Whether to include deleted ACLs in the result. Deleted ACLs are represented by role equal to \"none\". Deleted ACLs will always be included if syncToken is provided. Optional. The default is False.",
1602 "location": "query"
1603 },
1604 "syncToken": {
1605 "type": "string",
1606 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All entries deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
1607 "location": "query"
1608 }
1609 },
1610 "parameterOrder": [
1611 "calendarId"
1612 ],
1613 "request": {
1614 "$ref": "Channel",
1615 "parameterName": "resource"
1616 },
1617 "response": {
1618 "$ref": "Channel"
1619 },
1620 "scopes": [
1621 "https://www.googleapis.com/auth/calendar"
1622 ],
1623 "supportsSubscription": true
1624 }
1625 }
1626 },
1627 "calendarList": {
1628 "methods": {
1629 "delete": {
1630 "id": "calendar.calendarList.delete",
1631 "path": "users/me/calendarList/{calendarId}",
1632 "httpMethod": "DELETE",
1633 "description": "Deletes an entry on the user's calendar list.",
1634 "parameters": {
1635 "calendarId": {
1636 "type": "string",
1637 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1638 "required": true,
1639 "location": "path"
1640 }
1641 },
1642 "parameterOrder": [
1643 "calendarId"
1644 ],
1645 "scopes": [
1646 "https://www.googleapis.com/auth/calendar"
1647 ]
1648 },
1649 "get": {
1650 "id": "calendar.calendarList.get",
1651 "path": "users/me/calendarList/{calendarId}",
1652 "httpMethod": "GET",
1653 "description": "Returns an entry on the user's calendar list.",
1654 "parameters": {
1655 "calendarId": {
1656 "type": "string",
1657 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1658 "required": true,
1659 "location": "path"
1660 }
1661 },
1662 "parameterOrder": [
1663 "calendarId"
1664 ],
1665 "response": {
1666 "$ref": "CalendarListEntry"
1667 },
1668 "scopes": [
1669 "https://www.googleapis.com/auth/calendar",
1670 "https://www.googleapis.com/auth/calendar.readonly"
1671 ]
1672 },
1673 "insert": {
1674 "id": "calendar.calendarList.insert",
1675 "path": "users/me/calendarList",
1676 "httpMethod": "POST",
1677 "description": "Adds an entry to the user's calendar list.",
1678 "parameters": {
1679 "colorRgbFormat": {
1680 "type": "boolean",
1681 "description": "Whether to use the foregroundColor and backgroundColor fields to write the calendar colors (RGB). If this feature is used, the index-based colorId field will be set to the best matching option automatically. Optional. The default is False.",
1682 "location": "query"
1683 }
1684 },
1685 "request": {
1686 "$ref": "CalendarListEntry"
1687 },
1688 "response": {
1689 "$ref": "CalendarListEntry"
1690 },
1691 "scopes": [
1692 "https://www.googleapis.com/auth/calendar"
1693 ]
1694 },
1695 "list": {
1696 "id": "calendar.calendarList.list",
1697 "path": "users/me/calendarList",
1698 "httpMethod": "GET",
1699 "description": "Returns entries on the user's calendar list.",
1700 "parameters": {
1701 "maxResults": {
1702 "type": "integer",
1703 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
1704 "format": "int32",
1705 "minimum": "1",
1706 "location": "query"
1707 },
1708 "minAccessRole": {
1709 "type": "string",
1710 "description": "The minimum access role for the user in the returned entries. Optional. The default is no restriction.",
1711 "enum": [
1712 "freeBusyReader",
1713 "owner",
1714 "reader",
1715 "writer"
1716 ],
1717 "enumDescriptions": [
1718 "The user can read free/busy information.",
1719 "The user can read and modify events and access control lists.",
1720 "The user can read events that are not private.",
1721 "The user can read and modify events."
1722 ],
1723 "location": "query"
1724 },
1725 "pageToken": {
1726 "type": "string",
1727 "description": "Token specifying which result page to return. Optional.",
1728 "location": "query"
1729 },
1730 "showDeleted": {
1731 "type": "boolean",
1732 "description": "Whether to include deleted calendar list entries in the result. Optional. The default is False.",
1733 "location": "query"
1734 },
1735 "showHidden": {
1736 "type": "boolean",
1737 "description": "Whether to show hidden entries. Optional. The default is False.",
1738 "location": "query"
1739 },
1740 "syncToken": {
1741 "type": "string",
1742 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. If only read-only fields such as calendar properties or ACLs have changed, the entry won't be returned. All entries deleted and hidden since the previous list request will always be in the result set and it is not allowed to set showDeleted neither showHidden to False.\nTo ensure client state consistency minAccessRole query parameter cannot be specified together with nextSyncToken.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
1743 "location": "query"
1744 }
1745 },
1746 "response": {
1747 "$ref": "CalendarList"
1748 },
1749 "scopes": [
1750 "https://www.googleapis.com/auth/calendar",
1751 "https://www.googleapis.com/auth/calendar.readonly"
1752 ],
1753 "supportsSubscription": true
1754 },
1755 "patch": {
1756 "id": "calendar.calendarList.patch",
1757 "path": "users/me/calendarList/{calendarId}",
1758 "httpMethod": "PATCH",
1759 "description": "Updates an entry on the user's calendar list. This method supports patch semantics.",
1760 "parameters": {
1761 "calendarId": {
1762 "type": "string",
1763 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1764 "required": true,
1765 "location": "path"
1766 },
1767 "colorRgbFormat": {
1768 "type": "boolean",
1769 "description": "Whether to use the foregroundColor and backgroundColor fields to write the calendar colors (RGB). If this feature is used, the index-based colorId field will be set to the best matching option automatically. Optional. The default is False.",
1770 "location": "query"
1771 }
1772 },
1773 "parameterOrder": [
1774 "calendarId"
1775 ],
1776 "request": {
1777 "$ref": "CalendarListEntry"
1778 },
1779 "response": {
1780 "$ref": "CalendarListEntry"
1781 },
1782 "scopes": [
1783 "https://www.googleapis.com/auth/calendar"
1784 ]
1785 },
1786 "update": {
1787 "id": "calendar.calendarList.update",
1788 "path": "users/me/calendarList/{calendarId}",
1789 "httpMethod": "PUT",
1790 "description": "Updates an entry on the user's calendar list.",
1791 "parameters": {
1792 "calendarId": {
1793 "type": "string",
1794 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1795 "required": true,
1796 "location": "path"
1797 },
1798 "colorRgbFormat": {
1799 "type": "boolean",
1800 "description": "Whether to use the foregroundColor and backgroundColor fields to write the calendar colors (RGB). If this feature is used, the index-based colorId field will be set to the best matching option automatically. Optional. The default is False.",
1801 "location": "query"
1802 }
1803 },
1804 "parameterOrder": [
1805 "calendarId"
1806 ],
1807 "request": {
1808 "$ref": "CalendarListEntry"
1809 },
1810 "response": {
1811 "$ref": "CalendarListEntry"
1812 },
1813 "scopes": [
1814 "https://www.googleapis.com/auth/calendar"
1815 ]
1816 },
1817 "watch": {
1818 "id": "calendar.calendarList.watch",
1819 "path": "users/me/calendarList/watch",
1820 "httpMethod": "POST",
1821 "description": "Watch for changes to CalendarList resources.",
1822 "parameters": {
1823 "maxResults": {
1824 "type": "integer",
1825 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
1826 "format": "int32",
1827 "minimum": "1",
1828 "location": "query"
1829 },
1830 "minAccessRole": {
1831 "type": "string",
1832 "description": "The minimum access role for the user in the returned entries. Optional. The default is no restriction.",
1833 "enum": [
1834 "freeBusyReader",
1835 "owner",
1836 "reader",
1837 "writer"
1838 ],
1839 "enumDescriptions": [
1840 "The user can read free/busy information.",
1841 "The user can read and modify events and access control lists.",
1842 "The user can read events that are not private.",
1843 "The user can read and modify events."
1844 ],
1845 "location": "query"
1846 },
1847 "pageToken": {
1848 "type": "string",
1849 "description": "Token specifying which result page to return. Optional.",
1850 "location": "query"
1851 },
1852 "showDeleted": {
1853 "type": "boolean",
1854 "description": "Whether to include deleted calendar list entries in the result. Optional. The default is False.",
1855 "location": "query"
1856 },
1857 "showHidden": {
1858 "type": "boolean",
1859 "description": "Whether to show hidden entries. Optional. The default is False.",
1860 "location": "query"
1861 },
1862 "syncToken": {
1863 "type": "string",
1864 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. If only read-only fields such as calendar properties or ACLs have changed, the entry won't be returned. All entries deleted and hidden since the previous list request will always be in the result set and it is not allowed to set showDeleted neither showHidden to False.\nTo ensure client state consistency minAccessRole query parameter cannot be specified together with nextSyncToken.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
1865 "location": "query"
1866 }
1867 },
1868 "request": {
1869 "$ref": "Channel",
1870 "parameterName": "resource"
1871 },
1872 "response": {
1873 "$ref": "Channel"
1874 },
1875 "scopes": [
1876 "https://www.googleapis.com/auth/calendar",
1877 "https://www.googleapis.com/auth/calendar.readonly"
1878 ],
1879 "supportsSubscription": true
1880 }
1881 }
1882 },
1883 "calendars": {
1884 "methods": {
1885 "clear": {
1886 "id": "calendar.calendars.clear",
1887 "path": "calendars/{calendarId}/clear",
1888 "httpMethod": "POST",
1889 "description": "Clears a primary calendar. This operation deletes all events associated with the primary calendar of an account.",
1890 "parameters": {
1891 "calendarId": {
1892 "type": "string",
1893 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1894 "required": true,
1895 "location": "path"
1896 }
1897 },
1898 "parameterOrder": [
1899 "calendarId"
1900 ],
1901 "scopes": [
1902 "https://www.googleapis.com/auth/calendar"
1903 ]
1904 },
1905 "delete": {
1906 "id": "calendar.calendars.delete",
1907 "path": "calendars/{calendarId}",
1908 "httpMethod": "DELETE",
1909 "description": "Deletes a secondary calendar. Use calendars.clear for clearing all events on primary calendars.",
1910 "parameters": {
1911 "calendarId": {
1912 "type": "string",
1913 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1914 "required": true,
1915 "location": "path"
1916 }
1917 },
1918 "parameterOrder": [
1919 "calendarId"
1920 ],
1921 "scopes": [
1922 "https://www.googleapis.com/auth/calendar"
1923 ]
1924 },
1925 "get": {
1926 "id": "calendar.calendars.get",
1927 "path": "calendars/{calendarId}",
1928 "httpMethod": "GET",
1929 "description": "Returns metadata for a calendar.",
1930 "parameters": {
1931 "calendarId": {
1932 "type": "string",
1933 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1934 "required": true,
1935 "location": "path"
1936 }
1937 },
1938 "parameterOrder": [
1939 "calendarId"
1940 ],
1941 "response": {
1942 "$ref": "Calendar"
1943 },
1944 "scopes": [
1945 "https://www.googleapis.com/auth/calendar",
1946 "https://www.googleapis.com/auth/calendar.readonly"
1947 ]
1948 },
1949 "insert": {
1950 "id": "calendar.calendars.insert",
1951 "path": "calendars",
1952 "httpMethod": "POST",
1953 "description": "Creates a secondary calendar.",
1954 "request": {
1955 "$ref": "Calendar"
1956 },
1957 "response": {
1958 "$ref": "Calendar"
1959 },
1960 "scopes": [
1961 "https://www.googleapis.com/auth/calendar"
1962 ]
1963 },
1964 "patch": {
1965 "id": "calendar.calendars.patch",
1966 "path": "calendars/{calendarId}",
1967 "httpMethod": "PATCH",
1968 "description": "Updates metadata for a calendar. This method supports patch semantics.",
1969 "parameters": {
1970 "calendarId": {
1971 "type": "string",
1972 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1973 "required": true,
1974 "location": "path"
1975 }
1976 },
1977 "parameterOrder": [
1978 "calendarId"
1979 ],
1980 "request": {
1981 "$ref": "Calendar"
1982 },
1983 "response": {
1984 "$ref": "Calendar"
1985 },
1986 "scopes": [
1987 "https://www.googleapis.com/auth/calendar"
1988 ]
1989 },
1990 "update": {
1991 "id": "calendar.calendars.update",
1992 "path": "calendars/{calendarId}",
1993 "httpMethod": "PUT",
1994 "description": "Updates metadata for a calendar.",
1995 "parameters": {
1996 "calendarId": {
1997 "type": "string",
1998 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
1999 "required": true,
2000 "location": "path"
2001 }
2002 },
2003 "parameterOrder": [
2004 "calendarId"
2005 ],
2006 "request": {
2007 "$ref": "Calendar"
2008 },
2009 "response": {
2010 "$ref": "Calendar"
2011 },
2012 "scopes": [
2013 "https://www.googleapis.com/auth/calendar"
2014 ]
2015 }
2016 }
2017 },
2018 "channels": {
2019 "methods": {
2020 "stop": {
2021 "id": "calendar.channels.stop",
2022 "path": "channels/stop",
2023 "httpMethod": "POST",
2024 "description": "Stop watching resources through this channel",
2025 "request": {
2026 "$ref": "Channel",
2027 "parameterName": "resource"
2028 },
2029 "scopes": [
2030 "https://www.googleapis.com/auth/calendar",
2031 "https://www.googleapis.com/auth/calendar.readonly"
2032 ]
2033 }
2034 }
2035 },
2036 "colors": {
2037 "methods": {
2038 "get": {
2039 "id": "calendar.colors.get",
2040 "path": "colors",
2041 "httpMethod": "GET",
2042 "description": "Returns the color definitions for calendars and events.",
2043 "response": {
2044 "$ref": "Colors"
2045 },
2046 "scopes": [
2047 "https://www.googleapis.com/auth/calendar",
2048 "https://www.googleapis.com/auth/calendar.readonly"
2049 ]
2050 }
2051 }
2052 },
2053 "events": {
2054 "methods": {
2055 "delete": {
2056 "id": "calendar.events.delete",
2057 "path": "calendars/{calendarId}/events/{eventId}",
2058 "httpMethod": "DELETE",
2059 "description": "Deletes an event.",
2060 "parameters": {
2061 "calendarId": {
2062 "type": "string",
2063 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2064 "required": true,
2065 "location": "path"
2066 },
2067 "eventId": {
2068 "type": "string",
2069 "description": "Event identifier.",
2070 "required": true,
2071 "location": "path"
2072 },
2073 "sendNotifications": {
2074 "type": "boolean",
2075 "description": "Whether to send notifications about the deletion of the event. Optional. The default is False.",
2076 "location": "query"
2077 }
2078 },
2079 "parameterOrder": [
2080 "calendarId",
2081 "eventId"
2082 ],
2083 "scopes": [
2084 "https://www.googleapis.com/auth/calendar"
2085 ]
2086 },
2087 "get": {
2088 "id": "calendar.events.get",
2089 "path": "calendars/{calendarId}/events/{eventId}",
2090 "httpMethod": "GET",
2091 "description": "Returns an event.",
2092 "parameters": {
2093 "alwaysIncludeEmail": {
2094 "type": "boolean",
2095 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2096 "location": "query"
2097 },
2098 "calendarId": {
2099 "type": "string",
2100 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2101 "required": true,
2102 "location": "path"
2103 },
2104 "eventId": {
2105 "type": "string",
2106 "description": "Event identifier.",
2107 "required": true,
2108 "location": "path"
2109 },
2110 "maxAttendees": {
2111 "type": "integer",
2112 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2113 "format": "int32",
2114 "minimum": "1",
2115 "location": "query"
2116 },
2117 "timeZone": {
2118 "type": "string",
2119 "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
2120 "location": "query"
2121 }
2122 },
2123 "parameterOrder": [
2124 "calendarId",
2125 "eventId"
2126 ],
2127 "response": {
2128 "$ref": "Event"
2129 },
2130 "scopes": [
2131 "https://www.googleapis.com/auth/calendar",
2132 "https://www.googleapis.com/auth/calendar.readonly"
2133 ]
2134 },
2135 "import": {
2136 "id": "calendar.events.import",
2137 "path": "calendars/{calendarId}/events/import",
2138 "httpMethod": "POST",
2139 "description": "Imports an event. This operation is used to add a private copy of an existing event to a calendar.",
2140 "parameters": {
2141 "calendarId": {
2142 "type": "string",
2143 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2144 "required": true,
2145 "location": "path"
2146 },
2147 "conferenceDataVersion": {
2148 "type": "integer",
2149 "description": "Version number of conference data supported by the API client. Version 0 assumes no conference data support and ignores conference data in the event's body. Version 1 enables support for copying of ConferenceData as well as for creating new conferences using the createRequest field of conferenceData. The default is 0.",
2150 "format": "int32",
2151 "minimum": "0",
2152 "maximum": "1",
2153 "location": "query"
2154 },
2155 "supportsAttachments": {
2156 "type": "boolean",
2157 "description": "Whether API client performing operation supports event attachments. Optional. The default is False.",
2158 "location": "query"
2159 }
2160 },
2161 "parameterOrder": [
2162 "calendarId"
2163 ],
2164 "request": {
2165 "$ref": "Event"
2166 },
2167 "response": {
2168 "$ref": "Event"
2169 },
2170 "scopes": [
2171 "https://www.googleapis.com/auth/calendar"
2172 ]
2173 },
2174 "insert": {
2175 "id": "calendar.events.insert",
2176 "path": "calendars/{calendarId}/events",
2177 "httpMethod": "POST",
2178 "description": "Creates an event.",
2179 "parameters": {
2180 "calendarId": {
2181 "type": "string",
2182 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2183 "required": true,
2184 "location": "path"
2185 },
2186 "conferenceDataVersion": {
2187 "type": "integer",
2188 "description": "Version number of conference data supported by the API client. Version 0 assumes no conference data support and ignores conference data in the event's body. Version 1 enables support for copying of ConferenceData as well as for creating new conferences using the createRequest field of conferenceData. The default is 0.",
2189 "format": "int32",
2190 "minimum": "0",
2191 "maximum": "1",
2192 "location": "query"
2193 },
2194 "maxAttendees": {
2195 "type": "integer",
2196 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2197 "format": "int32",
2198 "minimum": "1",
2199 "location": "query"
2200 },
2201 "sendNotifications": {
2202 "type": "boolean",
2203 "description": "Whether to send notifications about the creation of the new event. Optional. The default is False.",
2204 "location": "query"
2205 },
2206 "supportsAttachments": {
2207 "type": "boolean",
2208 "description": "Whether API client performing operation supports event attachments. Optional. The default is False.",
2209 "location": "query"
2210 }
2211 },
2212 "parameterOrder": [
2213 "calendarId"
2214 ],
2215 "request": {
2216 "$ref": "Event"
2217 },
2218 "response": {
2219 "$ref": "Event"
2220 },
2221 "scopes": [
2222 "https://www.googleapis.com/auth/calendar"
2223 ]
2224 },
2225 "instances": {
2226 "id": "calendar.events.instances",
2227 "path": "calendars/{calendarId}/events/{eventId}/instances",
2228 "httpMethod": "GET",
2229 "description": "Returns instances of the specified recurring event.",
2230 "parameters": {
2231 "alwaysIncludeEmail": {
2232 "type": "boolean",
2233 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2234 "location": "query"
2235 },
2236 "calendarId": {
2237 "type": "string",
2238 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2239 "required": true,
2240 "location": "path"
2241 },
2242 "eventId": {
2243 "type": "string",
2244 "description": "Recurring event identifier.",
2245 "required": true,
2246 "location": "path"
2247 },
2248 "maxAttendees": {
2249 "type": "integer",
2250 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2251 "format": "int32",
2252 "minimum": "1",
2253 "location": "query"
2254 },
2255 "maxResults": {
2256 "type": "integer",
2257 "description": "Maximum number of events returned on one result page. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
2258 "format": "int32",
2259 "minimum": "1",
2260 "location": "query"
2261 },
2262 "originalStart": {
2263 "type": "string",
2264 "description": "The original start time of the instance in the result. Optional.",
2265 "location": "query"
2266 },
2267 "pageToken": {
2268 "type": "string",
2269 "description": "Token specifying which result page to return. Optional.",
2270 "location": "query"
2271 },
2272 "showDeleted": {
2273 "type": "boolean",
2274 "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events will still be included if singleEvents is False. Optional. The default is False.",
2275 "location": "query"
2276 },
2277 "timeMax": {
2278 "type": "string",
2279 "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time. Must be an RFC3339 timestamp with mandatory time zone offset.",
2280 "format": "date-time",
2281 "location": "query"
2282 },
2283 "timeMin": {
2284 "type": "string",
2285 "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time. Must be an RFC3339 timestamp with mandatory time zone offset.",
2286 "format": "date-time",
2287 "location": "query"
2288 },
2289 "timeZone": {
2290 "type": "string",
2291 "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
2292 "location": "query"
2293 }
2294 },
2295 "parameterOrder": [
2296 "calendarId",
2297 "eventId"
2298 ],
2299 "response": {
2300 "$ref": "Events"
2301 },
2302 "scopes": [
2303 "https://www.googleapis.com/auth/calendar",
2304 "https://www.googleapis.com/auth/calendar.readonly"
2305 ],
2306 "supportsSubscription": true
2307 },
2308 "list": {
2309 "id": "calendar.events.list",
2310 "path": "calendars/{calendarId}/events",
2311 "httpMethod": "GET",
2312 "description": "Returns events on the specified calendar.",
2313 "parameters": {
2314 "alwaysIncludeEmail": {
2315 "type": "boolean",
2316 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2317 "location": "query"
2318 },
2319 "calendarId": {
2320 "type": "string",
2321 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2322 "required": true,
2323 "location": "path"
2324 },
2325 "iCalUID": {
2326 "type": "string",
2327 "description": "Specifies event ID in the iCalendar format to be included in the response. Optional.",
2328 "location": "query"
2329 },
2330 "maxAttendees": {
2331 "type": "integer",
2332 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2333 "format": "int32",
2334 "minimum": "1",
2335 "location": "query"
2336 },
2337 "maxResults": {
2338 "type": "integer",
2339 "description": "Maximum number of events returned on one result page. The number of events in the resulting page may be less than this value, or none at all, even if there are more events matching the query. Incomplete pages can be detected by a non-empty nextPageToken field in the response. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
2340 "default": "250",
2341 "format": "int32",
2342 "minimum": "1",
2343 "location": "query"
2344 },
2345 "orderBy": {
2346 "type": "string",
2347 "description": "The order of the events returned in the result. Optional. The default is an unspecified, stable order.",
2348 "enum": [
2349 "startTime",
2350 "updated"
2351 ],
2352 "enumDescriptions": [
2353 "Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)",
2354 "Order by last modification time (ascending)."
2355 ],
2356 "location": "query"
2357 },
2358 "pageToken": {
2359 "type": "string",
2360 "description": "Token specifying which result page to return. Optional.",
2361 "location": "query"
2362 },
2363 "privateExtendedProperty": {
2364 "type": "string",
2365 "description": "Extended properties constraint specified as propertyName=value. Matches only private properties. This parameter might be repeated multiple times to return events that match all given constraints.",
2366 "repeated": true,
2367 "location": "query"
2368 },
2369 "q": {
2370 "type": "string",
2371 "description": "Free text search terms to find events that match these terms in any field, except for extended properties. Optional.",
2372 "location": "query"
2373 },
2374 "sharedExtendedProperty": {
2375 "type": "string",
2376 "description": "Extended properties constraint specified as propertyName=value. Matches only shared properties. This parameter might be repeated multiple times to return events that match all given constraints.",
2377 "repeated": true,
2378 "location": "query"
2379 },
2380 "showDeleted": {
2381 "type": "boolean",
2382 "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned. Optional. The default is False.",
2383 "location": "query"
2384 },
2385 "showHiddenInvitations": {
2386 "type": "boolean",
2387 "description": "Whether to include hidden invitations in the result. Optional. The default is False.",
2388 "location": "query"
2389 },
2390 "singleEvents": {
2391 "type": "boolean",
2392 "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves. Optional. The default is False.",
2393 "location": "query"
2394 },
2395 "syncToken": {
2396 "type": "string",
2397 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All events deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nThere are several query parameters that cannot be specified together with nextSyncToken to ensure consistency of the client state.\n\nThese are: \n- iCalUID \n- orderBy \n- privateExtendedProperty \n- q \n- sharedExtendedProperty \n- timeMin \n- timeMax \n- updatedMin If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
2398 "location": "query"
2399 },
2400 "timeMax": {
2401 "type": "string",
2402 "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time. Must be an RFC3339 timestamp with mandatory time zone offset, e.g., 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but will be ignored. If timeMin is set, timeMax must be greater than timeMin.",
2403 "format": "date-time",
2404 "location": "query"
2405 },
2406 "timeMin": {
2407 "type": "string",
2408 "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time. Must be an RFC3339 timestamp with mandatory time zone offset, e.g., 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but will be ignored. If timeMax is set, timeMin must be smaller than timeMax.",
2409 "format": "date-time",
2410 "location": "query"
2411 },
2412 "timeZone": {
2413 "type": "string",
2414 "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
2415 "location": "query"
2416 },
2417 "updatedMin": {
2418 "type": "string",
2419 "description": "Lower bound for an event's last modification time (as a RFC3339 timestamp) to filter by. When specified, entries deleted since this time will always be included regardless of showDeleted. Optional. The default is not to filter by last modification time.",
2420 "format": "date-time",
2421 "location": "query"
2422 }
2423 },
2424 "parameterOrder": [
2425 "calendarId"
2426 ],
2427 "response": {
2428 "$ref": "Events"
2429 },
2430 "scopes": [
2431 "https://www.googleapis.com/auth/calendar",
2432 "https://www.googleapis.com/auth/calendar.readonly"
2433 ],
2434 "supportsSubscription": true
2435 },
2436 "move": {
2437 "id": "calendar.events.move",
2438 "path": "calendars/{calendarId}/events/{eventId}/move",
2439 "httpMethod": "POST",
2440 "description": "Moves an event to another calendar, i.e. changes an event's organizer.",
2441 "parameters": {
2442 "calendarId": {
2443 "type": "string",
2444 "description": "Calendar identifier of the source calendar where the event currently is on.",
2445 "required": true,
2446 "location": "path"
2447 },
2448 "destination": {
2449 "type": "string",
2450 "description": "Calendar identifier of the target calendar where the event is to be moved to.",
2451 "required": true,
2452 "location": "query"
2453 },
2454 "eventId": {
2455 "type": "string",
2456 "description": "Event identifier.",
2457 "required": true,
2458 "location": "path"
2459 },
2460 "sendNotifications": {
2461 "type": "boolean",
2462 "description": "Whether to send notifications about the change of the event's organizer. Optional. The default is False.",
2463 "location": "query"
2464 }
2465 },
2466 "parameterOrder": [
2467 "calendarId",
2468 "eventId",
2469 "destination"
2470 ],
2471 "response": {
2472 "$ref": "Event"
2473 },
2474 "scopes": [
2475 "https://www.googleapis.com/auth/calendar"
2476 ]
2477 },
2478 "patch": {
2479 "id": "calendar.events.patch",
2480 "path": "calendars/{calendarId}/events/{eventId}",
2481 "httpMethod": "PATCH",
2482 "description": "Updates an event. This method supports patch semantics.",
2483 "parameters": {
2484 "alwaysIncludeEmail": {
2485 "type": "boolean",
2486 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2487 "location": "query"
2488 },
2489 "calendarId": {
2490 "type": "string",
2491 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2492 "required": true,
2493 "location": "path"
2494 },
2495 "conferenceDataVersion": {
2496 "type": "integer",
2497 "description": "Version number of conference data supported by the API client. Version 0 assumes no conference data support and ignores conference data in the event's body. Version 1 enables support for copying of ConferenceData as well as for creating new conferences using the createRequest field of conferenceData. The default is 0.",
2498 "format": "int32",
2499 "minimum": "0",
2500 "maximum": "1",
2501 "location": "query"
2502 },
2503 "eventId": {
2504 "type": "string",
2505 "description": "Event identifier.",
2506 "required": true,
2507 "location": "path"
2508 },
2509 "maxAttendees": {
2510 "type": "integer",
2511 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2512 "format": "int32",
2513 "minimum": "1",
2514 "location": "query"
2515 },
2516 "sendNotifications": {
2517 "type": "boolean",
2518 "description": "Whether to send notifications about the event update (e.g. attendee's responses, title changes, etc.). Optional. The default is False.",
2519 "location": "query"
2520 },
2521 "supportsAttachments": {
2522 "type": "boolean",
2523 "description": "Whether API client performing operation supports event attachments. Optional. The default is False.",
2524 "location": "query"
2525 }
2526 },
2527 "parameterOrder": [
2528 "calendarId",
2529 "eventId"
2530 ],
2531 "request": {
2532 "$ref": "Event"
2533 },
2534 "response": {
2535 "$ref": "Event"
2536 },
2537 "scopes": [
2538 "https://www.googleapis.com/auth/calendar"
2539 ]
2540 },
2541 "quickAdd": {
2542 "id": "calendar.events.quickAdd",
2543 "path": "calendars/{calendarId}/events/quickAdd",
2544 "httpMethod": "POST",
2545 "description": "Creates an event based on a simple text string.",
2546 "parameters": {
2547 "calendarId": {
2548 "type": "string",
2549 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2550 "required": true,
2551 "location": "path"
2552 },
2553 "sendNotifications": {
2554 "type": "boolean",
2555 "description": "Whether to send notifications about the creation of the event. Optional. The default is False.",
2556 "location": "query"
2557 },
2558 "text": {
2559 "type": "string",
2560 "description": "The text describing the event to be created.",
2561 "required": true,
2562 "location": "query"
2563 }
2564 },
2565 "parameterOrder": [
2566 "calendarId",
2567 "text"
2568 ],
2569 "response": {
2570 "$ref": "Event"
2571 },
2572 "scopes": [
2573 "https://www.googleapis.com/auth/calendar"
2574 ]
2575 },
2576 "update": {
2577 "id": "calendar.events.update",
2578 "path": "calendars/{calendarId}/events/{eventId}",
2579 "httpMethod": "PUT",
2580 "description": "Updates an event.",
2581 "parameters": {
2582 "alwaysIncludeEmail": {
2583 "type": "boolean",
2584 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2585 "location": "query"
2586 },
2587 "calendarId": {
2588 "type": "string",
2589 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2590 "required": true,
2591 "location": "path"
2592 },
2593 "conferenceDataVersion": {
2594 "type": "integer",
2595 "description": "Version number of conference data supported by the API client. Version 0 assumes no conference data support and ignores conference data in the event's body. Version 1 enables support for copying of ConferenceData as well as for creating new conferences using the createRequest field of conferenceData. The default is 0.",
2596 "format": "int32",
2597 "minimum": "0",
2598 "maximum": "1",
2599 "location": "query"
2600 },
2601 "eventId": {
2602 "type": "string",
2603 "description": "Event identifier.",
2604 "required": true,
2605 "location": "path"
2606 },
2607 "maxAttendees": {
2608 "type": "integer",
2609 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2610 "format": "int32",
2611 "minimum": "1",
2612 "location": "query"
2613 },
2614 "sendNotifications": {
2615 "type": "boolean",
2616 "description": "Whether to send notifications about the event update (e.g. attendee's responses, title changes, etc.). Optional. The default is False.",
2617 "location": "query"
2618 },
2619 "supportsAttachments": {
2620 "type": "boolean",
2621 "description": "Whether API client performing operation supports event attachments. Optional. The default is False.",
2622 "location": "query"
2623 }
2624 },
2625 "parameterOrder": [
2626 "calendarId",
2627 "eventId"
2628 ],
2629 "request": {
2630 "$ref": "Event"
2631 },
2632 "response": {
2633 "$ref": "Event"
2634 },
2635 "scopes": [
2636 "https://www.googleapis.com/auth/calendar"
2637 ]
2638 },
2639 "watch": {
2640 "id": "calendar.events.watch",
2641 "path": "calendars/{calendarId}/events/watch",
2642 "httpMethod": "POST",
2643 "description": "Watch for changes to Events resources.",
2644 "parameters": {
2645 "alwaysIncludeEmail": {
2646 "type": "boolean",
2647 "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
2648 "location": "query"
2649 },
2650 "calendarId": {
2651 "type": "string",
2652 "description": "Calendar identifier. To retrieve calendar IDs call the calendarList.list method. If you want to access the primary calendar of the currently logged in user, use the \"primary\" keyword.",
2653 "required": true,
2654 "location": "path"
2655 },
2656 "iCalUID": {
2657 "type": "string",
2658 "description": "Specifies event ID in the iCalendar format to be included in the response. Optional.",
2659 "location": "query"
2660 },
2661 "maxAttendees": {
2662 "type": "integer",
2663 "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
2664 "format": "int32",
2665 "minimum": "1",
2666 "location": "query"
2667 },
2668 "maxResults": {
2669 "type": "integer",
2670 "description": "Maximum number of events returned on one result page. The number of events in the resulting page may be less than this value, or none at all, even if there are more events matching the query. Incomplete pages can be detected by a non-empty nextPageToken field in the response. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
2671 "default": "250",
2672 "format": "int32",
2673 "minimum": "1",
2674 "location": "query"
2675 },
2676 "orderBy": {
2677 "type": "string",
2678 "description": "The order of the events returned in the result. Optional. The default is an unspecified, stable order.",
2679 "enum": [
2680 "startTime",
2681 "updated"
2682 ],
2683 "enumDescriptions": [
2684 "Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)",
2685 "Order by last modification time (ascending)."
2686 ],
2687 "location": "query"
2688 },
2689 "pageToken": {
2690 "type": "string",
2691 "description": "Token specifying which result page to return. Optional.",
2692 "location": "query"
2693 },
2694 "privateExtendedProperty": {
2695 "type": "string",
2696 "description": "Extended properties constraint specified as propertyName=value. Matches only private properties. This parameter might be repeated multiple times to return events that match all given constraints.",
2697 "repeated": true,
2698 "location": "query"
2699 },
2700 "q": {
2701 "type": "string",
2702 "description": "Free text search terms to find events that match these terms in any field, except for extended properties. Optional.",
2703 "location": "query"
2704 },
2705 "sharedExtendedProperty": {
2706 "type": "string",
2707 "description": "Extended properties constraint specified as propertyName=value. Matches only shared properties. This parameter might be repeated multiple times to return events that match all given constraints.",
2708 "repeated": true,
2709 "location": "query"
2710 },
2711 "showDeleted": {
2712 "type": "boolean",
2713 "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned. Optional. The default is False.",
2714 "location": "query"
2715 },
2716 "showHiddenInvitations": {
2717 "type": "boolean",
2718 "description": "Whether to include hidden invitations in the result. Optional. The default is False.",
2719 "location": "query"
2720 },
2721 "singleEvents": {
2722 "type": "boolean",
2723 "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves. Optional. The default is False.",
2724 "location": "query"
2725 },
2726 "syncToken": {
2727 "type": "string",
2728 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All events deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nThere are several query parameters that cannot be specified together with nextSyncToken to ensure consistency of the client state.\n\nThese are: \n- iCalUID \n- orderBy \n- privateExtendedProperty \n- q \n- sharedExtendedProperty \n- timeMin \n- timeMax \n- updatedMin If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
2729 "location": "query"
2730 },
2731 "timeMax": {
2732 "type": "string",
2733 "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time. Must be an RFC3339 timestamp with mandatory time zone offset, e.g., 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but will be ignored. If timeMin is set, timeMax must be greater than timeMin.",
2734 "format": "date-time",
2735 "location": "query"
2736 },
2737 "timeMin": {
2738 "type": "string",
2739 "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time. Must be an RFC3339 timestamp with mandatory time zone offset, e.g., 2011-06-03T10:00:00-07:00, 2011-06-03T10:00:00Z. Milliseconds may be provided but will be ignored. If timeMax is set, timeMin must be smaller than timeMax.",
2740 "format": "date-time",
2741 "location": "query"
2742 },
2743 "timeZone": {
2744 "type": "string",
2745 "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
2746 "location": "query"
2747 },
2748 "updatedMin": {
2749 "type": "string",
2750 "description": "Lower bound for an event's last modification time (as a RFC3339 timestamp) to filter by. When specified, entries deleted since this time will always be included regardless of showDeleted. Optional. The default is not to filter by last modification time.",
2751 "format": "date-time",
2752 "location": "query"
2753 }
2754 },
2755 "parameterOrder": [
2756 "calendarId"
2757 ],
2758 "request": {
2759 "$ref": "Channel",
2760 "parameterName": "resource"
2761 },
2762 "response": {
2763 "$ref": "Channel"
2764 },
2765 "scopes": [
2766 "https://www.googleapis.com/auth/calendar",
2767 "https://www.googleapis.com/auth/calendar.readonly"
2768 ],
2769 "supportsSubscription": true
2770 }
2771 }
2772 },
2773 "freebusy": {
2774 "methods": {
2775 "query": {
2776 "id": "calendar.freebusy.query",
2777 "path": "freeBusy",
2778 "httpMethod": "POST",
2779 "description": "Returns free/busy information for a set of calendars.",
2780 "request": {
2781 "$ref": "FreeBusyRequest"
2782 },
2783 "response": {
2784 "$ref": "FreeBusyResponse"
2785 },
2786 "scopes": [
2787 "https://www.googleapis.com/auth/calendar",
2788 "https://www.googleapis.com/auth/calendar.readonly"
2789 ]
2790 }
2791 }
2792 },
2793 "settings": {
2794 "methods": {
2795 "get": {
2796 "id": "calendar.settings.get",
2797 "path": "users/me/settings/{setting}",
2798 "httpMethod": "GET",
2799 "description": "Returns a single user setting.",
2800 "parameters": {
2801 "setting": {
2802 "type": "string",
2803 "description": "The id of the user setting.",
2804 "required": true,
2805 "location": "path"
2806 }
2807 },
2808 "parameterOrder": [
2809 "setting"
2810 ],
2811 "response": {
2812 "$ref": "Setting"
2813 },
2814 "scopes": [
2815 "https://www.googleapis.com/auth/calendar",
2816 "https://www.googleapis.com/auth/calendar.readonly"
2817 ]
2818 },
2819 "list": {
2820 "id": "calendar.settings.list",
2821 "path": "users/me/settings",
2822 "httpMethod": "GET",
2823 "description": "Returns all user settings for the authenticated user.",
2824 "parameters": {
2825 "maxResults": {
2826 "type": "integer",
2827 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
2828 "format": "int32",
2829 "minimum": "1",
2830 "location": "query"
2831 },
2832 "pageToken": {
2833 "type": "string",
2834 "description": "Token specifying which result page to return. Optional.",
2835 "location": "query"
2836 },
2837 "syncToken": {
2838 "type": "string",
2839 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
2840 "location": "query"
2841 }
2842 },
2843 "response": {
2844 "$ref": "Settings"
2845 },
2846 "scopes": [
2847 "https://www.googleapis.com/auth/calendar",
2848 "https://www.googleapis.com/auth/calendar.readonly"
2849 ],
2850 "supportsSubscription": true
2851 },
2852 "watch": {
2853 "id": "calendar.settings.watch",
2854 "path": "users/me/settings/watch",
2855 "httpMethod": "POST",
2856 "description": "Watch for changes to Settings resources.",
2857 "parameters": {
2858 "maxResults": {
2859 "type": "integer",
2860 "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
2861 "format": "int32",
2862 "minimum": "1",
2863 "location": "query"
2864 },
2865 "pageToken": {
2866 "type": "string",
2867 "description": "Token specifying which result page to return. Optional.",
2868 "location": "query"
2869 },
2870 "syncToken": {
2871 "type": "string",
2872 "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
2873 "location": "query"
2874 }
2875 },
2876 "request": {
2877 "$ref": "Channel",
2878 "parameterName": "resource"
2879 },
2880 "response": {
2881 "$ref": "Channel"
2882 },
2883 "scopes": [
2884 "https://www.googleapis.com/auth/calendar",
2885 "https://www.googleapis.com/auth/calendar.readonly"
2886 ],
2887 "supportsSubscription": true
2888 }
2889 }
2890 }
2891 }
2892 }
+0
-54
tests/data/vv.txt less more
0 BEGIN:VCALENDAR
1 METHOD:REQUEST
2 PRODID:Microsoft Exchange Server 2010
3 VERSION:2.0
4 BEGIN:VTIMEZONE
5 TZID:Israel Standard Time
6 BEGIN:STANDARD
7 DTSTART:16010101T020000
8 TZOFFSETFROM:+0300
9 TZOFFSETTO:+0200
10 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
11 END:STANDARD
12 BEGIN:DAYLIGHT
13 DTSTART:16010101T020000
14 TZOFFSETFROM:+0200
15 TZOFFSETTO:+0300
16 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=4FR;BYMONTH=3
17 END:DAYLIGHT
18 END:VTIMEZONE
19 BEGIN:VEVENT
20 ORGANIZER;CN=שמואל גרובר;SENT-BY="MAILTO:ganon@bgu.ac.il":MAILTO:sh
21 muel@bgu.ac.il
22 ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=משה ק
23 מנסקי:MAILTO:kamenskm@bgu.ac.il
24 ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=ערן ל
25 קס:MAILTO:eran@bgu.ac.il
26 DESCRIPTION;LANGUAGE=he-IL:משה 0558956195\n
27 UID:040000008200E00074C5B7101A82E0080000000010FAD95E7008D401000000000000000
28 01000000053FEBBF5E4391447865D627F95C692CE
29 SUMMARY;LANGUAGE=he-IL:גישה לשרתי ssh
30 DTSTART;TZID=Israel Standard Time:20180701T130000
31 DTEND;TZID=Israel Standard Time:20180701T133000
32 CLASS:PUBLIC
33 PRIORITY:5
34 DTSTAMP:20180701T070519Z
35 TRANSP:OPAQUE
36 STATUS:CONFIRMED
37 SEQUENCE:3
38 LOCATION;LANGUAGE=he-IL:שמוליק
39 X-MICROSOFT-CDO-APPT-SEQUENCE:3
40 X-MICROSOFT-CDO-OWNERAPPTID:-279681054
41 X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
42 X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
43 X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
44 X-MICROSOFT-CDO-IMPORTANCE:1
45 X-MICROSOFT-CDO-INSTTYPE:0
46 X-MICROSOFT-DISALLOW-COUNTER:FALSE
47 BEGIN:VALARM
48 DESCRIPTION:REMINDER
49 TRIGGER;RELATED=START:-PT15M
50 ACTION:DISPLAY
51 END:VALARM
52 END:VEVENT
53 END:VCALENDAR
+0
-101
tests/test_argparsers.py less more
0 from gcalcli import argparsers
1 from collections import namedtuple
2 import shlex
3 import pytest
4
5
6 def test_get_argparser():
7 """Just asserts no errors have been introduced"""
8 argparser = argparsers.get_argument_parser()
9 assert argparser
10
11
12 def test_reminder_parser():
13 remind_parser = argparsers.get_remind_parser()
14 argv = shlex.split('--reminder invalid reminder')
15 with pytest.raises(SystemExit):
16 remind_parser.parse_args(argv)
17
18 argv = shlex.split('--reminder "5m sms"')
19 assert len(remind_parser.parse_args(argv).reminders) == 1
20
21
22 def test_output_parser(monkeypatch):
23 def sub_terminal_size(columns):
24 ts = namedtuple('terminal_size', ['lines', 'columns'])
25
26 def fake_get_terminal_size():
27 return ts(123, columns)
28
29 return fake_get_terminal_size
30
31 output_parser = argparsers.get_output_parser()
32 argv = shlex.split('-w 9')
33 with pytest.raises(SystemExit):
34 output_parser.parse_args(argv)
35
36 argv = shlex.split('-w 10')
37 assert output_parser.parse_args(argv).cal_width == 10
38
39 argv = shlex.split('')
40 monkeypatch.setattr(argparsers, 'get_terminal_size', sub_terminal_size(70))
41 output_parser = argparsers.get_output_parser()
42 assert output_parser.parse_args(argv).cal_width == 10
43
44 argv = shlex.split('')
45 monkeypatch.setattr(argparsers, 'get_terminal_size',
46 sub_terminal_size(100))
47 output_parser = argparsers.get_output_parser()
48 assert output_parser.parse_args(argv).cal_width == 13
49
50
51 def test_search_parser():
52 search_parser = argparsers.get_search_parser()
53 with pytest.raises(SystemExit):
54 search_parser.parse_args([])
55
56
57 def test_updates_parser():
58 updates_parser = argparsers.get_updates_parser()
59
60 argv = shlex.split('2019-07-18 2019-08-01 2019-09-01')
61 parsed_updates = updates_parser.parse_args(argv)
62 assert parsed_updates.since
63 assert parsed_updates.start
64 assert parsed_updates.end
65
66
67 def test_conflicts_parser():
68 updates_parser = argparsers.get_conflicts_parser()
69
70 argv = shlex.split('search 2019-08-01 2019-09-01')
71 parsed_conflicts = updates_parser.parse_args(argv)
72 assert parsed_conflicts.text
73 assert parsed_conflicts.start
74 assert parsed_conflicts.end
75
76
77 def test_details_parser():
78 details_parser = argparsers.get_details_parser()
79
80 argv = shlex.split('--details attendees --details url '
81 '--details location --details end')
82 parsed_details = details_parser.parse_args(argv).details
83 assert parsed_details['attendees']
84 assert parsed_details['location']
85 assert parsed_details['url']
86 assert parsed_details['end']
87
88 argv = shlex.split('--details all')
89 parsed_details = details_parser.parse_args(argv).details
90 assert all(parsed_details[d] for d in argparsers.DETAILS)
91
92
93 def test_handle_unparsed():
94 # minimal test showing that we can parse a global option after the
95 # subcommand (in some cases)
96 parser = argparsers.get_argument_parser()
97 argv = shlex.split('delete --calendar=test "search text"')
98 parsed, unparsed = parser.parse_known_args(argv)
99 parsed = argparsers.handle_unparsed(unparsed, parsed)
100 assert parsed.calendar == ['test']
+0
-12
tests/test_cli.py less more
0 from gcalcli.cli import run_add_prompt
1 from argparse import Namespace
2 from gcalcli.printer import Printer
3
4
5 def test_run_add_prompt():
6 """Basic test that only ensures the function can be run without error"""
7 printer = Printer()
8 min_keys = ['title', 'where', 'when', 'duration', 'description',
9 'event_color', 'reminders']
10 parsed_args = Namespace(**{k: 'test' for k in min_keys})
11 run_add_prompt(parsed_args, printer)
+0
-43
tests/test_conflicts.py less more
0 from gcalcli.conflicts import ShowConflicts
1 from datetime import datetime
2 from dateutil.tz import tzlocal
3
4 minimal_event = {
5 'e': datetime(2019, 1, 8, 15, 15, tzinfo=tzlocal()),
6 'id': 'minimial_event',
7 's': datetime(2019, 1, 8, 14, 15, tzinfo=tzlocal())
8 }
9 minimal_event_overlapping = {
10 'e': datetime(2019, 1, 8, 16, 15, tzinfo=tzlocal()),
11 'id': 'minimial_event_overlapping',
12 's': datetime(2019, 1, 8, 14, 30, tzinfo=tzlocal())
13 }
14 minimal_event_nonoverlapping = {
15 'e': datetime(2019, 1, 8, 16, 15, tzinfo=tzlocal()),
16 'id': 'minimal_event_nonoverlapping',
17 's': datetime(2019, 1, 8, 15, 30, tzinfo=tzlocal())
18 }
19
20
21 def test_finds_no_conflicts_for_one_event():
22 """Basic test that only ensures the function can be run without error"""
23 conflicts = []
24 show_conflicts = ShowConflicts(conflicts.append)
25 show_conflicts.show_conflicts(minimal_event)
26 assert conflicts == []
27
28
29 def test_finds_conflicts_for_second_overlapping_event():
30 conflicts = []
31 show_conflicts = ShowConflicts(conflicts.append)
32 show_conflicts.show_conflicts(minimal_event)
33 show_conflicts.show_conflicts(minimal_event_overlapping)
34 assert conflicts == [minimal_event]
35
36
37 def test_does_not_find_conflict_for_second_non_overlapping_event():
38 conflicts = []
39 show_conflicts = ShowConflicts(conflicts.append)
40 show_conflicts.show_conflicts(minimal_event)
41 show_conflicts.show_conflicts(minimal_event_nonoverlapping)
42 assert conflicts == []
+0
-314
tests/test_gcalcli.py less more
0 from __future__ import absolute_import
1
2 import os
3 from json import load
4
5 from dateutil.tz import tzutc
6 from datetime import datetime
7
8 from gcalcli.utils import parse_reminder
9 from gcalcli.argparsers import (get_start_end_parser,
10 get_color_parser,
11 get_cal_query_parser,
12 get_output_parser,
13 get_updates_parser,
14 get_conflicts_parser,
15 get_search_parser)
16 from gcalcli.gcal import GoogleCalendarInterface
17 from gcalcli.cli import parse_cal_names
18
19
20 TEST_DATA_DIR = os.path.dirname(os.path.abspath(__file__)) + '/data'
21
22
23 # TODO: These are more like placeholders for proper unit tests
24 # We just try the commands and make sure no errors occur.
25 def test_list(capsys, PatchedGCalI):
26 gcal = PatchedGCalI(**vars(get_color_parser().parse_args([])))
27 with open(TEST_DATA_DIR + '/cal_list.json') as cl:
28 cal_count = len(load(cl)['items'])
29
30 # test data has 6 cals
31 assert cal_count == len(gcal.all_cals)
32 expected_header = gcal.printer.get_colorcode(
33 gcal.options['color_title']) + ' Access Title\n'
34
35 gcal.ListAllCalendars()
36 captured = capsys.readouterr()
37 assert captured.out.startswith(expected_header)
38
39 # +3 cos one for the header, one for the '----' decorations,
40 # and one for the eom
41 assert len(captured.out.split('\n')) == cal_count + 3
42
43
44 def test_agenda(PatchedGCalI):
45 assert PatchedGCalI().AgendaQuery() == 0
46
47 opts = get_start_end_parser().parse_args(['tomorrow'])
48 assert PatchedGCalI().AgendaQuery(start=opts.start, end=opts.end) == 0
49
50 opts = get_start_end_parser().parse_args(['today', 'tomorrow'])
51 assert PatchedGCalI().AgendaQuery(start=opts.start, end=opts.end) == 0
52
53
54 def test_updates(PatchedGCalI):
55 since = datetime(2019, 7, 10)
56 assert PatchedGCalI().UpdatesQuery(since) == 0
57
58 opts = get_updates_parser().parse_args(
59 ['2019-07-10', '2019-07-19', '2019-08-01'])
60 assert PatchedGCalI().UpdatesQuery(
61 last_updated_datetime=opts.since,
62 start=opts.start,
63 end=opts.end) == 0
64
65
66 def test_conflicts(PatchedGCalI):
67 assert PatchedGCalI().ConflictsQuery() == 0
68
69 opts = get_conflicts_parser().parse_args(
70 ['search text', '2019-07-19', '2019-08-01'])
71 assert PatchedGCalI().ConflictsQuery(
72 'search text',
73 start=opts.start,
74 end=opts.end) == 0
75
76
77 def test_cal_query(capsys, PatchedGCalI):
78 opts = vars(get_cal_query_parser().parse_args([]))
79 opts.update(vars(get_output_parser().parse_args([])))
80 opts.update(vars(get_color_parser().parse_args([])))
81 gcal = PatchedGCalI(**opts)
82
83 gcal.CalQuery('calw')
84 captured = capsys.readouterr()
85 art = gcal.printer.art
86 expect_top = (
87 gcal.printer.colors[gcal.options['color_border']] + art['ulc'] +
88 art['hrz'] * gcal.options['cal_width'])
89 assert captured.out.startswith(expect_top)
90
91 gcal.CalQuery('calm')
92 captured = capsys.readouterr()
93 assert captured.out.startswith(expect_top)
94
95
96 def test_add_event(PatchedGCalI):
97 cal_names = parse_cal_names(['jcrowgey@uw.edu'])
98 gcal = PatchedGCalI(
99 cal_names=cal_names, allday=False, default_reminders=True)
100 title = 'test event'
101 where = 'anywhere'
102 start = 'now'
103 end = 'tomorrow'
104 descr = 'testing'
105 who = 'anyone'
106 reminders = None
107 color = "banana"
108 assert gcal.AddEvent(
109 title, where, start, end, descr, who, reminders, color)
110
111
112 def test_add_event_override_color(capsys, default_options,
113 PatchedGCalIForEvents):
114 default_options.update({'override_color': True})
115 cal_names = parse_cal_names(['jcrowgey@uw.edu'])
116 gcal = PatchedGCalIForEvents(cal_names=cal_names, **default_options)
117 gcal.AgendaQuery()
118 captured = capsys.readouterr()
119 # this could be parameterized with pytest eventually
120 # assert colorId 10: green
121 assert '\033[0;32m' in captured.out
122
123
124 def test_quick_add(PatchedGCalI):
125 cal_names = parse_cal_names(['jcrowgey@uw.edu'])
126 gcal = PatchedGCalI(cal_names=cal_names)
127 event_text = 'quick test event'
128 reminder = '5m sms'
129 assert gcal.QuickAddEvent(event_text, reminders=[reminder])
130
131
132 def test_text_query(PatchedGCalI):
133 search_parser = get_search_parser()
134 gcal = PatchedGCalI()
135
136 # TODO: mock the api reply for the search
137 # and then assert something greater than zero
138
139 opts = search_parser.parse_args(['test', '1970-01-01', '2038-01-18'])
140 assert gcal.TextQuery(opts.text, opts.start, opts.end) == 0
141
142 opts = search_parser.parse_args(['test', '1970-01-01'])
143 assert gcal.TextQuery(opts.text, opts.start, opts.end) == 0
144
145 opts = search_parser.parse_args(['test'])
146 assert gcal.TextQuery(opts.text, opts.start, opts.end) == 0
147
148
149 def test_declined_event_no_attendees(PatchedGCalI):
150 gcal = PatchedGCalI()
151 event = {
152 'gcalcli_cal': {
153 'id': 'user@email.com',
154 },
155 'attendees': []
156 }
157 assert not gcal._DeclinedEvent(event)
158
159
160 def test_declined_event_non_matching_attendees(PatchedGCalI):
161 gcal = PatchedGCalI()
162 event = {
163 'gcalcli_cal': {
164 'id': 'user@email.com',
165 },
166 'attendees': [{
167 'email': 'user2@otheremail.com',
168 'responseStatus': 'declined',
169 }]
170 }
171 assert not gcal._DeclinedEvent(event)
172
173
174 def test_declined_event_matching_attendee_declined(PatchedGCalI):
175 gcal = PatchedGCalI()
176 event = {
177 'gcalcli_cal': {
178 'id': 'user@email.com',
179 },
180 'attendees': [
181 {
182 'email': 'user@email.com',
183 'responseStatus': 'declined',
184 },
185 {
186 'email': 'user2@otheremail.com',
187 'responseStatus': 'accepted',
188 },
189 ]
190 }
191 assert gcal._DeclinedEvent(event)
192
193
194 def test_declined_event_matching_attendee_accepted(PatchedGCalI):
195 gcal = PatchedGCalI()
196 event = {
197 'gcalcli_cal': {
198 'id': 'user@email.com',
199 },
200 'attendees': [
201 {
202 'email': 'user@email.com',
203 'responseStatus': 'accepted',
204 },
205 {
206 'email': 'user2@otheremail.com',
207 'responseStatus': 'declined',
208 },
209 ]
210 }
211 assert not gcal._DeclinedEvent(event)
212
213
214 def test_modify_event(PatchedGCalI):
215 opts = get_search_parser().parse_args(['test'])
216 gcal = PatchedGCalI(**vars(opts))
217 assert gcal.ModifyEvents(
218 gcal._edit_event, opts.text, opts.start, opts.end) == 0
219
220
221 def test_import(PatchedGCalI):
222 cal_names = parse_cal_names(['jcrowgey@uw.edu'])
223 gcal = PatchedGCalI(cal_names=cal_names, default_reminders=True)
224 vcal_path = TEST_DATA_DIR + '/vv.txt'
225 assert gcal.ImportICS(icsFile=open(vcal_path))
226
227
228 def test_parse_reminder():
229 MINS_PER_DAY = 60 * 24
230 MINS_PER_WEEK = MINS_PER_DAY * 7
231
232 rem = '5m email'
233 tim, method = parse_reminder(rem)
234 assert method == 'email'
235 assert tim == 5
236
237 rem = '2h sms'
238 tim, method = parse_reminder(rem)
239 assert method == 'sms'
240 assert tim == 120
241
242 rem = '1d popup'
243 tim, method = parse_reminder(rem)
244 assert method == 'popup'
245 assert tim == MINS_PER_DAY
246
247 rem = '1w'
248 tim, method = parse_reminder(rem)
249 assert method == 'popup'
250 assert tim == MINS_PER_WEEK
251
252 rem = '10w'
253 tim, method = parse_reminder(rem)
254 assert method == 'popup'
255 assert tim == MINS_PER_WEEK * 10
256
257 rem = 'invalid reminder'
258 assert parse_reminder(rem) is None
259
260
261 def test_parse_cal_names(PatchedGCalI):
262 # TODO we need to mock the event list returned by the search
263 # and then assert the right number of events
264 # for the moment, we assert 0 (which indicates successful completion of
265 # the code path, but no events printed)
266 cal_names = parse_cal_names(['j*#green'])
267 gcal = PatchedGCalI(cal_names=cal_names)
268 assert gcal.AgendaQuery() == 0
269
270 cal_names = parse_cal_names(['j*'])
271 gcal = PatchedGCalI(cal_names=cal_names)
272 assert gcal.AgendaQuery() == 0
273
274 cal_names = parse_cal_names(['jcrowgey@uw.edu'])
275 gcal = PatchedGCalI(cal_names=cal_names)
276 assert gcal.AgendaQuery() == 0
277
278
279 def test_localize_datetime(PatchedGCalI):
280 dt = GoogleCalendarInterface._localize_datetime(datetime.now())
281 assert dt.tzinfo is not None
282
283 dt = datetime.now(tzutc())
284 dt = GoogleCalendarInterface._localize_datetime(dt)
285 assert dt.tzinfo is not None
286
287
288 def test_iterate_events(capsys, PatchedGCalI):
289 gcal = PatchedGCalI()
290 assert gcal._iterate_events(gcal.now, []) == 0
291
292 # TODO: add some events to a list and assert their selection
293
294
295 def test_next_cut(PatchedGCalI):
296 gcal = PatchedGCalI()
297 # default width is 10
298 test_cal_width = 10
299 gcal.options['cal_width'] = test_cal_width
300 event_title = "first looooong"
301 assert gcal._next_cut(event_title) == (5, 5)
302
303 event_title = "tooooooloooong"
304 assert gcal._next_cut(event_title) == (test_cal_width, test_cal_width)
305
306 event_title = "one two three four"
307 assert gcal._next_cut(event_title) == (7, 7)
308
309 # event_title = "& G NSW VIM Project"
310 # assert gcal._next_cut(event_title) == (7, 7)
311
312 event_title = "樹貞 fun fun fun"
313 assert gcal._next_cut(event_title) == (8, 6)
+0
-134
tests/test_input_validation.py less more
0 import pytest
1
2 from gcalcli.validators import validate_input, ValidationError
3 from gcalcli.validators import (STR_NOT_EMPTY,
4 PARSABLE_DATE,
5 PARSABLE_DURATION,
6 STR_TO_INT,
7 STR_ALLOW_EMPTY,
8 REMINDER,
9 VALID_COLORS)
10 # Tests required:
11 #
12 # * Title: any string, not blank
13 # * Location: any string, allow blank
14 # * When: string that can be parsed by dateutil
15 # * Duration: string that can be cast to int
16 # * Description: any string, allow blank
17 # * Color: any string matching: blueberry, lavendar, grape, etc, or blank
18 # * Reminder: a valid reminder
19
20
21 def test_any_string_not_blank_validator(monkeypatch):
22 # Empty string raises ValidationError
23 monkeypatch.setattr("builtins.input", lambda: "")
24 with pytest.raises(ValidationError):
25 validate_input(STR_NOT_EMPTY) == ValidationError(
26 "Input here cannot be empty")
27
28 # None raises ValidationError
29 monkeypatch.setattr("builtins.input", lambda: None)
30 with pytest.raises(ValidationError):
31 validate_input(STR_NOT_EMPTY) == ValidationError(
32 "Input here cannot be empty")
33
34 # Valid string passes
35 monkeypatch.setattr("builtins.input", lambda: "Valid Text")
36 assert validate_input(STR_NOT_EMPTY) == "Valid Text"
37
38
39 def test_any_string_parsable_by_dateutil(monkeypatch):
40 # non-date raises ValidationError
41 monkeypatch.setattr("builtins.input", lambda: "NON-DATE STR")
42 with pytest.raises(ValidationError):
43 validate_input(PARSABLE_DATE) == ValidationError(
44 "Expected format: a date (e.g. 2019-01-01, tomorrow 10am, "
45 "2nd Jan, Jan 4th, etc) or valid time if today. "
46 "(Ctrl-C to exit)\n"
47 )
48
49 # date string passes
50 monkeypatch.setattr("builtins.input", lambda: "2nd January")
51 validate_input(PARSABLE_DATE) == "2nd January"
52
53
54 def test_any_string_parsable_by_parsedatetime(monkeypatch):
55 # non-date raises ValidationError
56 monkeypatch.setattr("builtins.input", lambda: "NON-DATE STR")
57 with pytest.raises(ValidationError) as ve:
58 validate_input(PARSABLE_DURATION)
59 assert ve.value.message == (
60 'Expected format: a duration (e.g. 1m, 1s, 1h3m)'
61 '(Ctrl-C to exit)\n'
62 )
63
64 # duration string passes
65 monkeypatch.setattr("builtins.input", lambda: "1m")
66 assert validate_input(PARSABLE_DURATION) == "1m"
67
68 # duration string passes
69 monkeypatch.setattr("builtins.input", lambda: "1h2m")
70 assert validate_input(PARSABLE_DURATION) == "1h2m"
71
72
73 def test_string_can_be_cast_to_int(monkeypatch):
74 # non int-castable string raises ValidationError
75 monkeypatch.setattr("builtins.input", lambda: "X")
76 with pytest.raises(ValidationError):
77 validate_input(STR_TO_INT) == ValidationError(
78 "Input here must be a number")
79
80 # int string passes
81 monkeypatch.setattr("builtins.input", lambda: "10")
82 validate_input(STR_TO_INT) == "10"
83
84
85 def test_for_valid_colour_name(monkeypatch):
86 # non valid colour raises ValidationError
87 monkeypatch.setattr("builtins.input", lambda: "purple")
88 with pytest.raises(ValidationError):
89 validate_input(VALID_COLORS) == ValidationError(
90 "purple is not a valid color value to use here. Please "
91 "use one of basil, peacock, grape, lavender, blueberry,"
92 "tomato, safe, flamingo or banana."
93 )
94 # valid colour passes
95 monkeypatch.setattr("builtins.input", lambda: "grape")
96 validate_input(VALID_COLORS) == "grape"
97
98 # empty str passes
99 monkeypatch.setattr("builtins.input", lambda: "")
100 validate_input(VALID_COLORS) == ""
101
102
103 def test_any_string_and_blank(monkeypatch):
104 # string passes
105 monkeypatch.setattr("builtins.input", lambda: "TEST")
106 validate_input(STR_ALLOW_EMPTY) == "TEST"
107
108
109 def test_reminder(monkeypatch):
110 # valid reminders pass
111 monkeypatch.setattr("builtins.input", lambda: "10m email")
112 validate_input(REMINDER) == "10m email"
113
114 monkeypatch.setattr("builtins.input", lambda: "10 popup")
115 validate_input(REMINDER) == "10m email"
116
117 monkeypatch.setattr("builtins.input", lambda: "10m sms")
118 validate_input(REMINDER) == "10m email"
119
120 monkeypatch.setattr("builtins.input", lambda: "12323")
121 validate_input(REMINDER) == "10m email"
122
123 # invalid reminder raises ValidationError
124 monkeypatch.setattr("builtins.input", lambda: "meaningless")
125 with pytest.raises(ValidationError):
126 validate_input(REMINDER) == ValidationError(
127 "Format: <number><w|d|h|m> <popup|email|sms>\n")
128
129 # invalid reminder raises ValidationError
130 monkeypatch.setattr("builtins.input", lambda: "")
131 with pytest.raises(ValidationError):
132 validate_input(REMINDER) == ValidationError(
133 "Format: <number><w|d|h|m> <popup|email|sms>\n")
+0
-86
tests/test_printer.py less more
0 import sys
1 from argparse import ArgumentTypeError
2 from io import StringIO
3
4 import pytest
5 from gcalcli.printer import COLOR_NAMES, Printer, valid_color_name
6
7
8 def test_init():
9 cp = Printer()
10 assert cp
11
12
13 def test_valid_color_name():
14 with pytest.raises(ArgumentTypeError):
15 valid_color_name('this_is_not_a_colorname')
16
17
18 def test_all_colors():
19 """Makes sure the COLOR_NAMES is in sync with the colors in the printer"""
20 cp = Printer()
21 for color_name in COLOR_NAMES:
22 out = StringIO()
23 cp.msg('msg', color_name, file=out)
24 out.seek(0)
25 assert out.read() == cp.colors[color_name] + 'msg' + '\033[0m'
26
27
28 def test_red_msg():
29 cp = Printer()
30 out = StringIO()
31 cp.msg('msg', 'red', file=out)
32 out.seek(0)
33 assert out.read() == '\033[0;31mmsg\033[0m'
34
35
36 def test_err_msg(monkeypatch):
37 err = StringIO()
38 monkeypatch.setattr(sys, 'stderr', err)
39 cp = Printer()
40 cp.err_msg('error')
41 err.seek(0)
42 assert err.read() == '\033[31;1merror\033[0m'
43
44
45 def test_debug_msg(monkeypatch):
46 err = StringIO()
47 monkeypatch.setattr(sys, 'stderr', err)
48 cp = Printer()
49 cp.debug_msg('debug')
50 err.seek(0)
51 assert err.read() == '\033[0;33mdebug\033[0m'
52
53
54 def test_conky_red_msg():
55 cp = Printer(conky=True)
56 out = StringIO()
57 cp.msg('msg', 'red', file=out)
58 out.seek(0)
59 assert out.read() == '${color red}msg${color}'
60
61
62 def test_conky_err_msg(monkeypatch):
63 err = StringIO()
64 monkeypatch.setattr(sys, 'stderr', err)
65 cp = Printer(conky=True)
66 cp.err_msg('error')
67 err.seek(0)
68 assert err.read() == '${color red}error${color}'
69
70
71 def test_conky_debug_msg(monkeypatch):
72 err = StringIO()
73 monkeypatch.setattr(sys, 'stderr', err)
74 cp = Printer(conky=True)
75 cp.debug_msg('debug')
76 err.seek(0)
77 assert err.read() == '${color yellow}debug${color}'
78
79
80 def test_no_color():
81 cp = Printer(use_color=False)
82 out = StringIO()
83 cp.msg('msg', 'red', file=out)
84 out.seek(0)
85 assert out.read() == 'msg'
+0
-72
tests/test_utils.py less more
0 import gcalcli.utils as utils
1 from datetime import datetime, timedelta
2 from dateutil.tz import UTC
3 import pytest
4
5
6 def test_get_time_from_str():
7 assert utils.get_time_from_str('7am tomorrow')
8
9
10 def test_get_parsed_timedelta_from_str():
11 assert utils.get_timedelta_from_str('3.5h') == timedelta(
12 hours=3, minutes=30)
13 assert utils.get_timedelta_from_str('1') == timedelta(minutes=1)
14 assert utils.get_timedelta_from_str('1m') == timedelta(minutes=1)
15 assert utils.get_timedelta_from_str('1h') == timedelta(hours=1)
16 assert utils.get_timedelta_from_str('1h1m') == timedelta(
17 hours=1, minutes=1)
18 assert utils.get_timedelta_from_str('1:10') == timedelta(
19 hours=1, minutes=10)
20 assert utils.get_timedelta_from_str('2d:1h:3m') == timedelta(
21 days=2, hours=1, minutes=3)
22 assert utils.get_timedelta_from_str('2d 1h 3m 10s') == timedelta(
23 days=2, hours=1, minutes=3, seconds=10)
24 assert utils.get_timedelta_from_str(
25 '2 days 1 hour 2 minutes 40 seconds') == timedelta(
26 days=2, hours=1, minutes=2, seconds=40)
27 with pytest.raises(ValueError) as ve:
28 utils.get_timedelta_from_str('junk')
29 assert str(ve.value) == "Duration is invalid: junk"
30
31
32 def test_get_times_from_duration():
33 begin_1970 = '1970-01-01'
34 begin_1970_midnight = begin_1970 + 'T00:00:00+00:00'
35 two_hrs_later = begin_1970 + 'T02:00:00+00:00'
36 next_day = '1970-01-02'
37 assert (begin_1970_midnight, two_hrs_later) == \
38 utils.get_times_from_duration(begin_1970_midnight, duration=120)
39
40 assert (begin_1970_midnight, two_hrs_later) == \
41 utils.get_times_from_duration(
42 begin_1970_midnight, duration="2h")
43
44 assert (begin_1970_midnight, two_hrs_later) == \
45 utils.get_times_from_duration(
46 begin_1970_midnight, duration="120m")
47
48 assert (begin_1970, next_day) == \
49 utils.get_times_from_duration(
50 begin_1970_midnight, duration=1, allday=True)
51
52 with pytest.raises(ValueError):
53 utils.get_times_from_duration('this is not a date')
54
55 with pytest.raises(ValueError):
56 utils.get_times_from_duration(
57 begin_1970_midnight, duration='not a duration')
58
59 with pytest.raises(ValueError):
60 utils.get_times_from_duration(
61 begin_1970_midnight, duration='not a duraction', allday=True)
62
63
64 def test_days_since_epoch():
65 assert utils.days_since_epoch(datetime(1970, 1, 1, 0, tzinfo=UTC)) == 0
66 assert utils.days_since_epoch(datetime(1970, 12, 31)) == 364
67
68
69 def test_set_locale():
70 with pytest.raises(ValueError):
71 utils.set_locale('not_a_real_locale')
+0
-17
tox.ini less more
0 [tox]
1 envlist = py35,py36,py37,py38
2
3 [testenv]
4 usedevelop=true
5 deps = pytest
6 pytest-cov
7 coverage
8 flake8
9 vobject
10
11 commands=py.test -vv --cov=./gcalcli --pyargs tests {posargs}
12 coverage html
13 flake8
14
15 [flake8]
16 exclude=.git,__pycache__,venv,.tox,.venv