Skip to content

Commit

Permalink
Merge pull request #7 from mohamedamara1/code_update
Browse files Browse the repository at this point in the history
Update code & README
  • Loading branch information
mohamedamara1 authored Mar 30, 2022
2 parents d9a4703 + df85877 commit 1489a1f
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 60 deletions.
88 changes: 55 additions & 33 deletions automating.py → ClassroomFilesDownloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,14 +18,16 @@ def main():

service = get_classroom_service()
courses = service.courses().list(pageSize=20).execute()
for course in courses['courses']:
print(course['name'])

downd_files=list()

for course in courses['courses']:

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")
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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'
]


Expand Down
69 changes: 42 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.


0 comments on commit 1489a1f

Please sign in to comment.