-
-
Save Romern/225ceeca7a7825c0d2be7554c03b2bea to your computer and use it in GitHub Desktop.
| 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 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
Thank you very much. I had the plan decompiling the app today but now that is not necessary.
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 😂)
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"}% Thanks for your fast answer - for testing purposes I used curl instead of python and forgot the -X POST 🙈
is there an official documentation for the API? How do you know the functions of the API?