From df85877e45e5f5a78626819216f598fe8ad3b53e Mon Sep 17 00:00:00 2001 From: Mohamed Amara Date: Wed, 30 Mar 2022 10:24:01 +0100 Subject: [PATCH] Update code & README --- automating.py => ClassroomFilesDownloader.py | 88 ++++++++++++-------- README.md | 69 +++++++++------ 2 files changed, 97 insertions(+), 60 deletions(-) rename automating.py => ClassroomFilesDownloader.py (73%) diff --git a/automating.py b/ClassroomFilesDownloader.py similarity index 73% rename from automating.py rename to ClassroomFilesDownloader.py index 47b1d8a..256cc1d 100644 --- a/automating.py +++ b/ClassroomFilesDownloader.py @@ -4,7 +4,10 @@ from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials from googleapiclient.http import MediaIoBaseDownload +from googleapiclient.errors import HttpError + import io import os import os.path @@ -15,6 +18,8 @@ def main(): service = get_classroom_service() courses = service.courses().list(pageSize=20).execute() + for course in courses['courses']: + print(course['name']) downd_files=list() @@ -22,7 +27,7 @@ def main(): course_name = course['name'] course_id = course['id'] - + print("Downloading files for course : ", course_name) if not (path.exists(course_name)): os.mkdir('./' + course_name) os.mkdir('./' +course_name+ "/cours") @@ -50,12 +55,13 @@ def get_classroom_service(): Prints the names of the first 10 courses the user has access to. """ creds = None - # The file token.pickle stores the user's access and refresh tokens, and is + # The file classroom-token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. - if os.path.exists('token.pickle'): - with open('token.pickle', 'rb') as token: - creds = pickle.load(token) + #Deleting the token files will force the user to re-authenticate + if os.path.exists('classroom-token.json'): + creds = Credentials.from_authorized_user_file('classroom-token.json', SCOPES) + # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: @@ -65,11 +71,15 @@ def get_classroom_service(): 'credentials.json', SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run - with open('token.pickle', 'wb') as token: - pickle.dump(creds, token) + with open('classroom-token.json', 'w') as token: + token.write(creds.to_json()) - service = build('classroom', 'v1', credentials=creds) - return service + try: + service = build('classroom', 'v1', credentials=creds) + return service + + except HttpError as error: + print('An error occurred: %s' % error) def download_file(file_id, file_name, course_name): @@ -79,43 +89,46 @@ def download_file(file_id, file_name, course_name): Prints the names and ids of the first 10 files the user has access to. """ creds = None - # The file token.pickle stores the user's access and refresh tokens, and is + # The file drive-token.json stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. - if os.path.exists('token.pickledrive'): - with open('token.pickledrive', 'rb') as token: - creds = pickle.load(token) + if os.path.exists('drive-token.json'): + creds = Credentials.from_authorized_user_file('drive-token.json', SCOPES) # If there are no (valid) credentials available, let the user 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( - 'credential-drive.json', SCOPES) + 'credentials.json', SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run - with open('token.pickledrive', 'wb') as token: - pickle.dump(creds, token) + with open('drive-token.json', 'w') as token: + token.write(creds.to_json()) - service = build('drive', 'v3', credentials=creds) - request = service.files().get_media(fileId=file_id) + try: + service = build('drive', 'v3', credentials=creds) - fh = io.BytesIO() - downloader = MediaIoBaseDownload(fh, request) - done = False - + request = service.files().get_media(fileId=file_id) - while done is False: - status, done = downloader.next_chunk() - print("Download %d%%." % int(status.progress() * 100)) + fh = io.BytesIO() + downloader = MediaIoBaseDownload(fh, request) + done = False + - fh.seek(0) + while done is False: + status, done = downloader.next_chunk() + print("Download %d%%." % int(status.progress() * 100)) - with open(os.path.join('./', course_name, file_name), 'wb') as f: - f.write(fh.read()) - f.close() + fh.seek(0) + with open(os.path.join('./', course_name, file_name), 'wb') as f: + f.write(fh.read()) + f.close() + except HttpError as error: + # TODO(developer) - Handle errors from drive API. + print(f'An error occurred: {error}') def download_annonc_files(announcements, course_name): @@ -138,8 +151,12 @@ def download_annonc_files(announcements, course_name): print("DOWNLOADING " ,file_name) download_file(file_id, file_name, course_name) downloaded.append("Annonoucemet : "+course_name +' : ' + file_name) - else: + elif (file_name in present_files): print(file_name, "already exists") + elif not valid(extension[1:]): + print('Unsupported file type: ', extension[1:] ) + else: + print("Something went wrong") except KeyError as e: continue return downloaded @@ -164,12 +181,17 @@ def download_works_files(works, course_name): os.path.splitext(file_name) )[1] #the extension exists in second elemnts of returned tuple path_str = os.path.join('./', course_name, file_name) - if ((valid(extension[1:])) and (file_name not in present_files)) : + if valid(extension[1:]) and (file_name not in present_files) : print("DOWNLOADING " ,file_name) download_file(file_id, file_name, course_name) downloaded.append("Devoir : "+course_name +' : ' + file_name) - else: + elif (file_name in present_files): print(file_name, "already exists") + elif not valid(extension[1:]): + print('Unsupported file type: ', extension[1:] ) + else: + print("Something went wrong") + except KeyError as e: continue return downloaded @@ -178,7 +200,7 @@ def download_works_files(works, course_name): def valid(ch): return ch in [ 'pdf', 'docx', 'pptx', 'png', 'jpg', 'html', 'css', 'js', 'java', - 'class', 'txt', 'r', 'm', ' sql', 'doc', 'mp3', 'rar', 'zip' + 'class', 'txt', 'r', 'm', ' sql', 'doc', 'mp3', 'rar', 'zip', 'py', 'c' ] diff --git a/README.md b/README.md index b0aee9c..5fe1644 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,53 @@ - A script that automates downloading Google Classroom files and organizes them each in their appropriate folder. +A script that automates downloading Google Classroom files and organizes them each in their appropriate folder. - Prerequisities: +Prerequisities: - Python - - Pip +Python + +Pip How to setup the necessary files for the script? -1) you will need to download the credentials.json file which is generated by activating the API, you will need to enable 2 APIs: +1- First we need to create a project in Google Cloud Platform : https://developers.google.com/workspace/guides/create-project + +2- Next we need to enable Google Classroom API and Google Drive API in our project : https://developers.google.com/workspace/guides/enable-apis + +3- Now we need to configure the OAuth consent screen so that we can be able to create credentials that we will later download and allows our app to access our data. + +This can be achieved by following this guide : https://developers.google.com/workspace/guides/configure-oauth-consent. + +There are 2 important steps in this part : + +1-Add our email address to test users, just add the email address (addresses) you're gonna use to access the app. +2-Add the required scopes, the ones that our app will need to function properly , + +For Google Classroom API we will need these scopes +SCOPES = [ +'https://www.googleapis.com/auth/classroom.courses.readonly', +'https://www.googleapis.com/auth/classroom.announcements.readonly', +'https://www.googleapis.com/auth/classroom.student-submissions.me.readonly' +] +For Google Drive API we will need this one : +SCOPES = ['https://www.googleapis.com/auth/drive'] + +3- After configuring the OAuth consent screen, we can now create credentials in our GCP project, we need to create OAuth Client ID for a desktop app : + +https://developers.google.com/workspace/guides/create-credentials#desktop-app + +If you still didn't configure the OAuth consent screen , you will get a warning and won't be able to create credentials. + +4- Finally, after creating the credentials that our app needs to function, we can now download the json file that contains our credentials. The download button for that file should be easily accessible. After downloading that file we need to rename it to credentials.json and place it next to the python script ClassroomFilesDownloader.py. + +ALMOST DONE!!!!!!! + +Now we need to execute a command in our terminal : pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib - Google Drive API: https://developers.google.com/drive/api/v3/quickstart/python - - Google Classroom API: https://developers.google.com/classroom/quickstart/python - -- after this step you should have 2 files downloaded +This is mentioned here in this guide : https://developers.google.com/drive/api/quickstart/python -2) you should rename the file associated with Google Drive API to : credential-drive.json +\*\* Now we are ready to execute our script but before that we need to tell our script the number of courses to download by editing a variable value in the script: -we're almost done + - open the ClassroomFilesDownloader.py in any text editor you want -3) you need to launch a simple PIP command: + - CTRL +F to look for the word "pageSize" - pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib - - we're done! - - - Last part is about choosing the number of courses to download , which you will set by editing a variable value in the script: - - - open the automating.py in any text editor you want - - - CTRL +F to look for the word "pageSize" - - change the value of that variable to how many courses you want to download, it works by the newest to oldest order. - -