Skip to content

Instantly share code, notes, and snippets.

@Romern
Last active March 3, 2020 15:58
Show Gist options
  • Save Romern/225ceeca7a7825c0d2be7554c03b2bea to your computer and use it in GitHub Desktop.
Save Romern/225ceeca7a7825c0d2be7554c03b2bea to your computer and use it in GitHub Desktop.
StudyDriveDownloader: Python implementation of the StudyDrive API
import requests
import json
import os
from datetime import datetime
baseurl = "https://api.studydrive.net/"
def login(user, passwd):
param = {"client_id": 4,
"client_secret": "nmGaT4rJ3VVGQXu75ymi5Cu5bdqb3tFnkWw9f1IX",
"grant_type":"password",
"username": user,
"password": passwd}
req = requests.post('{}oauth/token'.format(baseurl), data=param)
req.raise_for_status()
return json.loads(req.text)['access_token']
def getTime():
return str(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
def getUniversityData(universityid, token): #returns all courses of the university
headers={"authorization": "Bearer "+token}
req = requests.get('{}api/app/v1/universities/{}/courses'.format(baseurl,universityid), headers=headers)
req.raise_for_status()
return json.loads(req.text)
def getCourseData(courseid, token, page=0, reference_time=None):
#reference_time="2019-09-24 14:52:08"
param = {"sort": "time",
"page": page,
"semester_from":0,
"semester_until":0,
"type_ids":0}
if page>0:
if reference_time is None:
reference_time = getTime()
param["reference_time"] = reference_time
headers={"authorization": "Bearer "+token}
req = requests.get('{}api/app/v1/feed/courses/{}/documents'.format(baseurl,courseid), params=param, headers=headers)
req.raise_for_status()
return json.loads(req.text)
def getFullCourseData(courseid, token, until=None): # until is of type date, e.g. datetime.now()
reference_time = getTime()
init_data = getCourseData(courseid, token, page=0, reference_time=reference_time)
last_page = int(init_data["last_page"])
files = init_data["files"]
for i in range(1,last_page+1):
if (until != None) and (len([f for f in files if datetime.strptime(f["uploaded"], '%Y-%m-%d %H:%M:%S')>until])==0):
break
files.extend(getCourseData(courseid, token, page=i, reference_time=reference_time)["files"])
init_data["files"] = files
return init_data
def getDocument(docid, token):
headers={"authorization": "Bearer "+token}
#uploadDate = datetime.strptime(data["files"][0]["uploaded"], '%Y-%m-%d %H:%M:%S')
req = requests.get('{}api/app/v1/documents/{}/download'.format(baseurl,docid), headers=headers)
req.raise_for_status()
return req.content
def downloadAllFilesInCourse(filelist, token, folder="."):
for f in filelist:
docid = f['file_id']
docname = f['file_name'] + docid + f["file_name"].split(".")[-1]
print("Downloading {}...".format(docname))
doc = getDocument(docid,login_token)
if os.path.isfile(docname):
print("Found duplicate: {} already exists".format(docname))
file = open(folder + "/" + docname, "wb")
file.write(doc)
file.close()
def crawlAllCourses(lastcrawled, university_id, token):
#lastcrawled = {'50936': "2019-09-27 11:38:57", ...}
courses = getUniversityData(universityid, token)
for c in courses:
if c["course_id"] in lastcrawled.keys():
until = lastcrawled[c["course_id"]]
else:
until = None
data = getFullCourseData(c["course_id"], token, until=until)
if not os.path.exists(c["course_name"]):
os.mkdir(c["course_name"])
downloadAllFilesInCourse(data["files"], token, folder=c["course_name"])
lastcrawled[c["course_id"]] = data["files"][0]["uploaded"]
return lastcrawled #return updated lastcrawled
@henrydatei
Copy link

is there an official documentation for the API? How do you know the functions of the API?

@Romern
Copy link
Author

Romern commented Feb 25, 2020

@henrydatei I have just sniffed the traffic of the app. You could probably also just decompile the apk using apktool for example, a list of the apis is in base.apk/smali_classes3/de/veedapp/veed/network/StudydriveApiInterface.smali :

/api/app/v1/{answer_type}/answers/{answer_id}/create
/api/app/v1/{answer_type}/answers/{answer_id}/delete
/api/app/v1/{answer_type}/answers/{answer_id}/downvote
/api/app/v1/{answer_type}/answers/{answer_id}/edit
/api/app/v1/{answer_type}/answers/{answer_id}/report
/api/app/v1/{answer_type}/answers/{answer_id}/upload
/api/app/v1/{answer_type}/answers/{answer_id}/upload/{file_id}/delete
/api/app/v1/{answer_type}/answers/{answer_id}/upvote
/api/app/v1/community/order
/api/app/v1/courses/{course_id}/join
/api/app/v1/courses/{course_id}/leave
/api/app/v1/deeplink/{type}/{related_id}/data
/api/app/v1/documents/{file_id}/delete
/api/app/v1/documents/{file_id}/details
/api/app/v1/documents/{file_id}/details
/api/app/v1/documents/{file_id}/downvote
/api/app/v1/documents/{file_id}/edit
/api/app/v1/documents/{file_id}/follow
/api/app/v1/documents/{file_id}/increment
/api/app/v1/documents/{file_id}/mute
/api/app/v1/documents/{file_id}/report
/api/app/v1/documents/{file_id}/storage-url
/api/app/v1/documents/{file_id}/upvote
/api/app/v1/documents/upload
/api/app/v1/documents/upload/init
/api/app/v1/documents/upload/{upload_hash}/cancel
/api/app/v1/documents/upload/{upload_hash}/finalize
/api/app/v1/feed/courses/{course_id}/discussion
/api/app/v1/feed/courses/{course_id}/documents
/api/app/v1/feed/courses/{course_id}/documents/filter
/api/app/v1/feed/courses/{course_id}/files
/api/app/v1/feed/flashcards/sets
/api/app/v1/feed/flashcards/{user_id}/sets
/api/app/v1/feed/groups/{group_id}/discussion
/api/app/v1/feed/header
/api/app/v1/feed/header/read
/api/app/v1/feed/header/unread
/api/app/v1/feed/my_answers_feed
/api/app/v1/feed/my_documents
/api/app/v1/feed/my_favored_questions_feed
/api/app/v1/feed/my_flashcard_sets
/api/app/v1/feed/my_followed_documents
/api/app/v1/feed/my_followed_flashcard_sets
/api/app/v1/feed/my_followed_users
/api/app/v1/feed/my_questions_feed
/api/app/v1/feed/newsfeed
/api/app/v1/flashcards/{id}/bookmark
/api/app/v1/flashcards/{id}/rate
/api/app/v1/flashcards/image/add
/api/app/v1/flashcards/sets/create
/api/app/v1/flashcards/sets/{id}
/api/app/v1/flashcards/sets/{id}/delete
/api/app/v1/flashcards/sets/{id}/follow
/api/app/v1/flashcards/sets/{id}/rate/reset
/api/app/v1/flashcards/sets/{id}/report
/api/app/v1/flashcards/sets/{id}/study/finish
/api/app/v1/flashcards/sets/{id}/study/start
/api/app/v1/flashcards/sets/{id}/update
/api/app/v1/flashcards/sets/{id}/vote/down
/api/app/v1/flashcards/sets/{id}/vote/up
/api/app/v1/forgot-password
/api/app/v1/groups
/api/app/v1/groups/{group_id}/faq
/api/app/v1/groups/{group_id}/join
/api/app/v1/groups/{group_id}/leave
/api/app/v1/groups/{group_id}/mute
/api/app/v1/groups/{group_id}/unmute
/api/app/v1/groups/suggest
/api/app/v1/groups/target
/api/app/v1/html/{content}
/api/app/v1/ke/accept
/api/app/v1/ke/dashboard
/api/app/v1/ke/decline
/api/app/v1/ke/earnings
/api/app/v1/ke/goal/{id}
/api/app/v1/ke/small
/api/app/v1/login
/api/app/v1/logout
/api/app/v1/majors
/api/app/v1/majors/subscribe
/api/app/v1/mixpanel/enabled
/api/app/v1/myself
/api/app/v1/myself
/api/app/v1/myself/courses
/api/app/v1/myself/groups
/api/app/v1/onboarding/feed
/api/app/v1/{poll_type}/questions/poll/{poll_id}/vote
/api/app/v1/profiles/picture/delete
/api/app/v1/profiles/studies/reset
/api/app/v1/profiles/{user_id}
/api/app/v1/profiles/{user_id}/documents
/api/app/v1/profiles/{user_id}/follow
/api/app/v1/push-notification/subscribe
/api/app/v1/push-notification/unsubscribe
/api/app/v1/questions/downvote
/api/app/v1/questions/upvote
/api/app/v1/{question_type}/questions/init
/api/app/v1/{question_type}/questions/{question_id}
/api/app/v1/{question_type}/questions/{question_id}/answers/init
/api/app/v1/{question_type}/questions/{question_id}/best_answer
/api/app/v1/{question_type}/questions/{question_id}/create
/api/app/v1/{question_type}/questions/{question_id}/create
/api/app/v1/{question_type}/questions/{question_id}/delete
/api/app/v1/{question_type}/questions/{question_id}/edit
/api/app/v1/{question_type}/questions/{question_id}/favor
/api/app/v1/{question_type}/questions/{question_id}/mute
/api/app/v1/{question_type}/questions/{question_id}/report
/api/app/v1/{question_type}/questions/{question_id}/upload
/api/app/v1/{question_type}/questions/{question_id}/upload/{file_id}/delete
/api/app/v1/rewards
/api/app/v1/rewards/order
/api/app/v1/search/{context}
/api/app/v1/semester
/api/app/v1/semester
/api/app/v1/universities
/api/app/v1/universities/create
/api/app/v1/universities/subscribe
/api/app/v1/universities/{university_id}/courses
/api/app/v1/universities/{university_id}/courses/create
/api/app/v1/universities/{university_id}/degree_program
/api/app/v1/universities/{university_id}/degree_program/create
/api/app/v1/universities/{university_id}/degree_program/degree_types
/api/app/v1/universities/{university_id}/degree_program/subscribe
/api/app/v1/users/credits/detail
/api/app/v1/users/delete
/api/app/v1/users/email
/api/app/v1/users/email/check
/api/app/v1/users/feedback
/api/app/v1/users/feedback
/api/app/v1/users/gender
/api/app/v1/users/give-karma
/api/app/v1/users/left_sidebar
/api/app/v1/users/majors
/api/app/v1/users/nickname
/api/app/v1/users/password
/api/app/v1/users/register/email
/api/app/v1/users/right_sidebar_stats
/api/app/v1/users/settings
/api/app/v1/users/settings
/api/app/v1/users/upload_profile_picture
/api/app/v1/users/verify/email
/api/app/v1/verification/resend_email

@henrydatei
Copy link

Thank you very much. I had the plan decompiling the app today but now that is not necessary.

@henrydatei
Copy link

Do you know how
‘‘‘
/api/app/v1/{answer_type}/answers/{answer_id}/upvote
‘‘‘
works? The answer_type is the same as the question_type (document, course) and the answer_id should be clear. But if I try a POST request with the parameter token given when logging in, I get an error („Something gone wrong“, very helpful 😂)

@Romern
Copy link
Author

Romern commented Mar 1, 2020

no idea, works for me. Maybe you didnt specify it is a POST request and it tries GET?

curl -X POST -H "authorization: Bearer ********" https://api.studydrive.net/api/app/v1/document/answers/*****/upvote  
{"success":true,"rating":1,"hasvotes":true,"upvotes":1,"downvotes":0,"uservote":"up"}% 

@henrydatei
Copy link

Thanks for your fast answer - for testing purposes I used curl instead of python and forgot the -X POST 🙈

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment