-
-
Notifications
You must be signed in to change notification settings - Fork 393
Update Google Calendar integration #856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
352a475
google-calendar: Update requirements.txt dependencies.
Niloth-p 81387d1
google-calendar: Replace deprecated oauth2client library.
Niloth-p e722e90
google-calendar: Update outdated send_message parameters.
Niloth-p dfe38f1
google-calendar: Narrow the permission scope to calendar events.
Niloth-p 4b9dbb3
google-calendar: Use a constant for the tokens filename.
Niloth-p 5b8b440
google-calendar: Fix usage of term "credentials", replace with "tokens".
Niloth-p 2bb1b8a
google-calendar: Update command usage help.
Niloth-p f30a571
google-calendar: Clean up `add_argument` parameters.
Niloth-p f18399c
google-calendar: Add --provision argument to install dependencies.
Niloth-p 07810c5
google-calendar: Add error handling for missing client secret file.
Niloth-p abf6415
google-calendar: Stop printing events unless the verbose option is set.
Niloth-p 8b2b9dd
google-calendar: Add error handling for send_message.
Niloth-p 06db61b
google-calendar: Improve CLIENT_SECRET_FILE occurrences.
Niloth-p 445ee9b
google-calendar: Call get-google-credentials script internally.
Niloth-p 8b7c536
google-calendar: Use current user's email id for send_message.
Niloth-p 6436918
google-calendar: Use a bot to send direct messages to the bot owner.
Niloth-p 5d3ff39
google-calendar: Support sending reminders to channels.
Niloth-p 4c56d9b
google-calendar: Support manual authorization using auth code.
Niloth-p 441dce9
google-calendar: Log writing to the tokens file.
Niloth-p c004199
google-calendar: Add options --client-secret-file and --tokens-file.
Niloth-p 9ced7b8
google-calendar: Support loading options from the zuliprc.
Niloth-p e38a8e6
google-calendar: Fix type of event id, switch from int to str.
Niloth-p da6d896
google-calendar: Send each reminder as its own message.
Niloth-p 8794800
google-calendar: Add TypedDict and string conversion function for event.
Niloth-p b468931
google-calendar: Generalize the datetime parsing for event fields.
Niloth-p 078d307
google-calendar: Display more Event info in reminder messages.
Niloth-p 974863e
google-calendar: Support user customization of the message template.
Niloth-p File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,52 @@ | ||
#!/usr/bin/env python3 | ||
import argparse | ||
import os | ||
|
||
from oauth2client import client, tools | ||
from oauth2client.file import Storage | ||
from google.auth.transport.requests import Request | ||
from google.oauth2.credentials import Credentials | ||
from google_auth_oauthlib.flow import InstalledAppFlow | ||
|
||
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() | ||
|
||
# If modifying these scopes, delete your previously saved credentials | ||
# at zulip/bots/gcal/ | ||
# NOTE: When adding more scopes, add them after the previous one in the same field, with a space | ||
# seperating them. | ||
SCOPES = "https://www.googleapis.com/auth/calendar.readonly" | ||
SCOPES = ["https://www.googleapis.com/auth/calendar.events.readonly"] | ||
# File containing user's access and refresh tokens for Google application requests. | ||
# If it does not exist, e.g., first run, it is generated on user authorization. | ||
TOKENS_FILE = "google-tokens.json" | ||
# This file contains the information that google uses to figure out which application is requesting | ||
# this client's data. | ||
CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 | ||
APPLICATION_NAME = "Zulip Calendar Bot" | ||
HOME_DIR = os.path.expanduser("~") | ||
|
||
|
||
def get_credentials() -> client.Credentials: | ||
"""Gets valid user credentials from storage. | ||
def get_credentials() -> Credentials: | ||
""" | ||
Writes google tokens to a json file, using the client secret file (for the OAuth flow), | ||
and the refresh token. | ||
|
||
If the tokens file exists and is valid, nothing needs to be done. | ||
If the tokens file exists, but the auth token is expired (expiry duration of auth token | ||
is 1 hour), the refresh token is used to get a new token. | ||
If the tokens file does not exist, or is invalid, the OAuth2 flow is triggered. | ||
|
||
If nothing has been stored, or if the stored credentials are invalid, | ||
the OAuth2 flow is completed to obtain the new credentials. | ||
The OAuth2 flow needs the client secret file, and requires the user to grant access to | ||
the application via a browser authorization page, for the first run. | ||
|
||
Returns: | ||
Credentials, the obtained credential. | ||
The fetched tokens are written to storage in a json file, for reference by other scripts. | ||
""" | ||
|
||
credential_path = os.path.join(HOME_DIR, "google-credentials.json") | ||
|
||
store = Storage(credential_path) | ||
credentials = store.get() | ||
if not credentials or credentials.invalid: | ||
flow = client.flow_from_clientsecrets(os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES) | ||
flow.user_agent = APPLICATION_NAME | ||
# This attempts to open an authorization page in the default web browser, and asks the user | ||
# to grant the bot access to their data. If the user grants permission, the run_flow() | ||
# function returns new credentials. | ||
credentials = tools.run_flow(flow, store, flags) | ||
print("Storing credentials to " + credential_path) | ||
creds = None | ||
tokens_path = os.path.join(HOME_DIR, TOKENS_FILE) | ||
|
||
if os.path.exists(tokens_path): | ||
creds = Credentials.from_authorized_user_file(tokens_path, SCOPES) | ||
if not creds or not creds.valid: | ||
if creds and creds.expired and creds.refresh_token: | ||
creds.refresh(Request()) | ||
else: | ||
flow = InstalledAppFlow.from_client_secrets_file( | ||
os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES | ||
) | ||
creds = flow.run_local_server(port=0) | ||
with open(tokens_path, "w") as token: | ||
token.write(creds.to_json()) | ||
return creds | ||
|
||
|
||
get_credentials() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,4 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# This script depends on python-dateutil and python-pytz for properly handling | ||
# times and time zones of calendar events. | ||
import argparse | ||
import datetime | ||
import itertools | ||
|
@@ -12,23 +9,23 @@ import time | |
from typing import List, Optional, Set, Tuple | ||
|
||
import dateutil.parser | ||
import httplib2 | ||
import pytz | ||
from oauth2client import client | ||
from oauth2client.file import Storage | ||
|
||
try: | ||
from googleapiclient import discovery | ||
from google.oauth2.credentials import Credentials | ||
from googleapiclient.discovery import build | ||
except ImportError: | ||
logging.exception("Install google-api-python-client") | ||
logging.exception("Install the required python packages from requirements.txt first.") | ||
sys.exit(1) | ||
|
||
sys.path.append(os.path.join(os.path.dirname(__file__), "../../")) | ||
import zulip | ||
|
||
SCOPES = "https://www.googleapis.com/auth/calendar.readonly" | ||
SCOPES = ["https://www.googleapis.com/auth/calendar.events.readonly"] | ||
# File containing user's access and refresh tokens for Google application requests. | ||
# If it does not exist, e.g., first run, it is generated on user authorization. | ||
TOKENS_FILE = "google-tokens.json" | ||
CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 | ||
APPLICATION_NAME = "Zulip" | ||
HOME_DIR = os.path.expanduser("~") | ||
|
||
# Our cached view of the calendar, updated periodically. | ||
|
@@ -39,26 +36,20 @@ sent: Set[Tuple[int, datetime.datetime]] = set() | |
|
||
sys.path.append(os.path.dirname(__file__)) | ||
|
||
parser = zulip.add_default_arguments( | ||
argparse.ArgumentParser( | ||
r""" | ||
usage = r"""google-calendar --user EMAIL [--interval MINUTES] [--calendar CALENDAR_ID] | ||
|
||
google-calendar --calendar [email protected] | ||
This integration can be used to send Zulip messages as reminders for upcoming events from your Google Calendar. | ||
|
||
This integration can be used to send yourself reminders, on Zulip, of Google Calendar Events. | ||
Specify your Zulip API credentials and server in a ~/.zuliprc file, or using the options. | ||
|
||
Specify your Zulip API credentials and server in a ~/.zuliprc file or using the options. | ||
Before running this integration, make sure you download the client secret file from Google, and run the get-google-credentials script to give Zulip read access to your Google Calendar. | ||
|
||
Before running this integration make sure you run the get-google-credentials file to give Zulip | ||
access to certain aspects of your Google Account. | ||
This integration should be run on your local machine, as your API key is accessible to local users through the command line. | ||
|
||
This integration should be run on your local machine. Your API key and other information are | ||
revealed to local users through the command line. | ||
|
||
Depends on: google-api-python-client | ||
For more information, see https://zulip.com/integrations/doc/google-calendar. | ||
""" | ||
) | ||
) | ||
|
||
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage=usage)) | ||
|
||
|
||
parser.add_argument( | ||
|
@@ -88,7 +79,7 @@ if not options.zulip_email: | |
zulip_client = zulip.init_from_options(options) | ||
|
||
|
||
def get_credentials() -> client.Credentials: | ||
def get_credentials() -> Credentials: | ||
"""Gets valid user credentials from storage. | ||
|
||
If nothing has been stored, or if the stored credentials are invalid, | ||
|
@@ -99,12 +90,10 @@ def get_credentials() -> client.Credentials: | |
Credentials, the obtained credential. | ||
""" | ||
try: | ||
credential_path = os.path.join(HOME_DIR, "google-credentials.json") | ||
|
||
store = Storage(credential_path) | ||
return store.get() | ||
except client.Error: | ||
logging.exception("Error while trying to open the `google-credentials.json` file.") | ||
tokens_path = os.path.join(HOME_DIR, TOKENS_FILE) | ||
return Credentials.from_authorized_user_file(tokens_path, SCOPES) | ||
except ValueError: | ||
logging.exception("Error while trying to open the %s file.", TOKENS_FILE) | ||
sys.exit(1) | ||
except OSError: | ||
logging.error("Run the get-google-credentials script from this directory first.") | ||
|
@@ -113,8 +102,7 @@ def get_credentials() -> client.Credentials: | |
|
||
def populate_events() -> Optional[None]: | ||
credentials = get_credentials() | ||
creds = credentials.authorize(httplib2.Http()) | ||
service = discovery.build("calendar", "v3", http=creds) | ||
service = build("calendar", "v3", credentials=credentials) | ||
|
||
now = datetime.datetime.now(pytz.utc).isoformat() | ||
feed = ( | ||
|
@@ -186,9 +174,7 @@ def send_reminders() -> Optional[None]: | |
else: | ||
message = "Reminder:\n\n" + "\n".join("* " + m for m in messages) | ||
|
||
zulip_client.send_message( | ||
dict(type="private", to=options.zulip_email, sender=options.zulip_email, content=message) | ||
) | ||
zulip_client.send_message({"type": "direct", "to": [options.zulip_email], "content": message}) | ||
|
||
sent.update(keys) | ||
|
||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
httplib2>=0.22.0 | ||
oauth2client>=4.1.3 | ||
google-api-python-client>=1.7.9 | ||
google-auth-httplib2>=0.0.3 | ||
google-auth-oauthlib>=0.4.0 | ||
python-dateutil | ||
pytz |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.