From bd59f3ac894794fbeb9135f5f48f0c55bdfd2fa0 Mon Sep 17 00:00:00 2001 From: pransh62390 Date: Sat, 4 Jan 2025 16:09:26 +0530 Subject: [PATCH 1/5] modified code for google calendar integration according to the new google calendar api manual --- .../google/get-google-credentials | 51 ++++++++++++------ zulip/integrations/google/google-calendar | 52 +++++++++---------- zulip/integrations/google/requirements.txt | 5 +- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index bb97e5f69..a844d90bc 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -2,24 +2,29 @@ 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() +flags = argparse.ArgumentParser(description="Google Calendar Bot") +flags.add_argument('--noauth_local_webserver', action='store_true', + help='Run OAuth flow in console instead of opening a web browser.') +args = flags.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.readonly"] # 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("~") +CREDENTIALS_PATH = os.path.join(HOME_DIR, "google-credentials.json") -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, @@ -29,18 +34,30 @@ def get_credentials() -> client.Credentials: Credentials, the obtained credential. """ - 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 + + # Check if the credentials file exists + if os.path.exists(CREDENTIALS_PATH): + creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH, SCOPES) + + # If there are no valid credentials, initiate the OAuth flow + 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 + ) + if args.noauth_local_webserver: + creds = flow.run_console() + else: + creds = flow.run_local_server(port=0) + + # Save the credentials for future use + with open(CREDENTIALS_PATH, 'w') as token_file: + token_file.write(creds.to_json()) + + print("Storing credentials to " + CREDENTIALS_PATH) get_credentials() diff --git a/zulip/integrations/google/google-calendar b/zulip/integrations/google/google-calendar index 85906bd46..07498912f 100755 --- a/zulip/integrations/google/google-calendar +++ b/zulip/integrations/google/google-calendar @@ -12,21 +12,16 @@ 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 -except ImportError: - logging.exception("Install google-api-python-client") - sys.exit(1) +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build 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.readonly"] CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 APPLICATION_NAME = "Zulip" HOME_DIR = os.path.expanduser("~") @@ -87,34 +82,39 @@ 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, - an exception is thrown and the user is informed to run the script in this directory to get - credentials. + the user will be prompted to authenticate. Returns: Credentials, the obtained credential. """ - try: - credential_path = os.path.join(HOME_DIR, "google-credentials.json") + credential_path = os.path.join(HOME_DIR, "google-credentials.json") + creds = None + + # Load credentials from file if they exist + if os.path.exists(credential_path): + creds = Credentials.from_authorized_user_file(credential_path, SCOPES) + + # If there are no (valid) credentials available, prompt the user to log in. + 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(CLIENT_SECRET_FILE, SCOPES) + creds = flow.run_local_server(port=0) - store = Storage(credential_path) - return store.get() - except client.Error: - logging.exception("Error while trying to open the `google-credentials.json` file.") - sys.exit(1) - except OSError: - logging.error("Run the get-google-credentials script from this directory first.") - sys.exit(1) + # Save the credentials for the next run + with open(credential_path, "w") as token: + token.write(creds.to_json()) + return creds 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 = ( diff --git a/zulip/integrations/google/requirements.txt b/zulip/integrations/google/requirements.txt index 139c0705b..018523c01 100644 --- a/zulip/integrations/google/requirements.txt +++ b/zulip/integrations/google/requirements.txt @@ -1,2 +1,3 @@ -httplib2>=0.22.0 -oauth2client>=4.1.3 +google-api-python-client>=2.157.0 +google-auth-httplib2>=0.2.0 +google-auth-oauthlib>=1.2.1 From f5f913c892c94117d826fe4a86002c1836f714a9 Mon Sep 17 00:00:00 2001 From: pransh62390 Date: Sat, 4 Jan 2025 19:48:33 +0530 Subject: [PATCH 2/5] solved lint issues --- zulip/integrations/google/get-google-credentials | 7 +++---- zulip/integrations/google/google-calendar | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index a844d90bc..1b6ed4e6a 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -7,8 +7,7 @@ from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow flags = argparse.ArgumentParser(description="Google Calendar Bot") -flags.add_argument('--noauth_local_webserver', action='store_true', - help='Run OAuth flow in console instead of opening a web browser.') +flags.add_argument("--noauth_local_webserver", action="store_true", help="Run OAuth flow in console instead of opening a web browser.") args = flags.parse_args() # If modifying these scopes, delete your previously saved credentials @@ -54,9 +53,9 @@ def get_credentials() -> Credentials: creds = flow.run_local_server(port=0) # Save the credentials for future use - with open(CREDENTIALS_PATH, 'w') as token_file: + with open(CREDENTIALS_PATH, "w") as token_file: token_file.write(creds.to_json()) - + print("Storing credentials to " + CREDENTIALS_PATH) diff --git a/zulip/integrations/google/google-calendar b/zulip/integrations/google/google-calendar index 07498912f..d82988c45 100755 --- a/zulip/integrations/google/google-calendar +++ b/zulip/integrations/google/google-calendar @@ -55,7 +55,6 @@ google-calendar --calendar calendarID@example.calendar.google.com ) ) - parser.add_argument( "--interval", dest="interval", From f4dc4cca2d3f9ba052c69fe6913630f391d68646 Mon Sep 17 00:00:00 2001 From: pransh62390 Date: Thu, 9 Jan 2025 22:15:59 +0530 Subject: [PATCH 3/5] added module dependency in pyproject.toml --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 159c85e23..5261b00f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,9 @@ module = [ "feedparser.*", "gitlint.*", "googleapiclient.*", + "google_api_python_client.*", + "google_auth_httplib2.*", + "google_auth_oauthlib.*", "irc.*", "mercurial.*", "nio.*", From 29ac543c67462fbb0ea4a3a66ce63bc769d153d6 Mon Sep 17 00:00:00 2001 From: needJobCoder <97464694+needJobCoder@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:56:12 +0530 Subject: [PATCH 4/5] fixed linting issues and import issues --- requirements.txt | 1 + zulip/integrations/google/get-google-credentials | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c5d735436..e0e805d3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ types-pytz types-requests gitlint>=0.13.0 -r ./zulip/integrations/bridge_with_matrix/requirements.txt +-r ./zulip/integrations/google/requirements.txt diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index 1b6ed4e6a..393658371 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -22,7 +22,6 @@ APPLICATION_NAME = "Zulip Calendar Bot" HOME_DIR = os.path.expanduser("~") CREDENTIALS_PATH = os.path.join(HOME_DIR, "google-credentials.json") - def get_credentials() -> Credentials: """Gets valid user credentials from storage. @@ -58,5 +57,7 @@ def get_credentials() -> Credentials: print("Storing credentials to " + CREDENTIALS_PATH) + return creds # Return the obtained credentials + get_credentials() From 0e671baaf4bc1b3d501a182882e29652286b4689 Mon Sep 17 00:00:00 2001 From: mpagler <167506943+mpagler@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:03:18 +0100 Subject: [PATCH 5/5] api: Exclude None values from request payload in Endpoint class. Description: Refactored the Endpoint class to exclude parameters with None values from the request payload. This ensures cleaner API interactions and avoids unintended behavior, improving code reliability and alignment with best practices. --- pyproject.toml | 3 + requirements.txt | 1 + .../google/get-google-credentials | 57 +++++++++++++------ zulip/integrations/google/google-calendar | 53 ++++++++--------- zulip/integrations/google/requirements.txt | 5 +- zulip_botserver/README.md | 1 + 6 files changed, 74 insertions(+), 46 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 159c85e23..5261b00f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,9 @@ module = [ "feedparser.*", "gitlint.*", "googleapiclient.*", + "google_api_python_client.*", + "google_auth_httplib2.*", + "google_auth_oauthlib.*", "irc.*", "mercurial.*", "nio.*", diff --git a/requirements.txt b/requirements.txt index c5d735436..e0e805d3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ types-pytz types-requests gitlint>=0.13.0 -r ./zulip/integrations/bridge_with_matrix/requirements.txt +-r ./zulip/integrations/google/requirements.txt diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index bb97e5f69..c23ab789c 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -2,24 +2,31 @@ 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() +flags = argparse.ArgumentParser(description="Google Calendar Bot") +flags.add_argument( + "--noauth_local_webserver", + action="store_true", + help="Run OAuth flow in console instead of opening a web browser.", +) +args = flags.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.readonly"] # 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("~") +CREDENTIALS_PATH = os.path.join(HOME_DIR, "google-credentials.json") - -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, @@ -29,18 +36,32 @@ def get_credentials() -> client.Credentials: Credentials, the obtained credential. """ - 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 + + # Check if the credentials file exists + if os.path.exists(CREDENTIALS_PATH): + creds = Credentials.from_authorized_user_file(CREDENTIALS_PATH, SCOPES) + + # If there are no valid credentials, initiate the OAuth flow + 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 + ) + if args.noauth_local_webserver: + creds = flow.run_console() + else: + creds = flow.run_local_server(port=0) + + # Save the credentials for future use + with open(CREDENTIALS_PATH, "w") as token_file: + token_file.write(creds.to_json()) + + print("Storing credentials to " + CREDENTIALS_PATH) + + return creds # Return the obtained credentials get_credentials() diff --git a/zulip/integrations/google/google-calendar b/zulip/integrations/google/google-calendar index 85906bd46..a37fd4618 100755 --- a/zulip/integrations/google/google-calendar +++ b/zulip/integrations/google/google-calendar @@ -12,21 +12,16 @@ 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 -except ImportError: - logging.exception("Install google-api-python-client") - sys.exit(1) +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build 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.readonly"] CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 APPLICATION_NAME = "Zulip" HOME_DIR = os.path.expanduser("~") @@ -60,7 +55,6 @@ google-calendar --calendar calendarID@example.calendar.google.com ) ) - parser.add_argument( "--interval", dest="interval", @@ -88,33 +82,40 @@ 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, - an exception is thrown and the user is informed to run the script in this directory to get - credentials. + the user will be prompted to authenticate. Returns: Credentials, the obtained credential. """ - try: - credential_path = os.path.join(HOME_DIR, "google-credentials.json") + credential_path = os.path.join(HOME_DIR, "google-credentials.json") + creds = None + + # Load credentials from file if they exist + if os.path.exists(credential_path): + creds = Credentials.from_authorized_user_file(credential_path, SCOPES) + + # If there are no (valid) credentials available, prompt the user to log in. + 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(CLIENT_SECRET_FILE, SCOPES) + creds = flow.run_local_server(port=0) + + # Save the credentials for the next run + with open(credential_path, "w") as token: + token.write(creds.to_json()) - store = Storage(credential_path) - return store.get() - except client.Error: - logging.exception("Error while trying to open the `google-credentials.json` file.") - sys.exit(1) - except OSError: - logging.error("Run the get-google-credentials script from this directory first.") - sys.exit(1) + return creds 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 = ( diff --git a/zulip/integrations/google/requirements.txt b/zulip/integrations/google/requirements.txt index 139c0705b..018523c01 100644 --- a/zulip/integrations/google/requirements.txt +++ b/zulip/integrations/google/requirements.txt @@ -1,2 +1,3 @@ -httplib2>=0.22.0 -oauth2client>=4.1.3 +google-api-python-client>=2.157.0 +google-auth-httplib2>=0.2.0 +google-auth-oauthlib>=1.2.1 diff --git a/zulip_botserver/README.md b/zulip_botserver/README.md index c16e0b1ee..cb5750f55 100644 --- a/zulip_botserver/README.md +++ b/zulip_botserver/README.md @@ -19,6 +19,7 @@ The format for a configuration file is: email=helloworld-bot@zulip.com site=http://localhost token=abcd1234 + bot-config-file=helloworld.conf Is passed `--use-env-vars` instead of `--config-file`, the configuration can instead be provided via the `ZULIP_BOTSERVER_CONFIG`