Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 43 additions & 28 deletions olsync/olclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,53 +16,61 @@
from socketIO_client import SocketIO
import time

# Where to get the CSRF Token and where to send the login request to
LOGIN_URL = "https://www.overleaf.com/login"
PROJECT_URL = "https://www.overleaf.com/project" # The dashboard URL
# The URL to download all the files in zip format
DOWNLOAD_URL = "https://www.overleaf.com/project/{}/download/zip"
UPLOAD_URL = "https://www.overleaf.com/project/{}/upload" # The URL to upload files
FOLDER_URL = "https://www.overleaf.com/project/{}/folder" # The URL to create folders
BASE_URL = "https://www.overleaf.com" # The Overleaf Base URL


class OverleafClient(object):
"""
Overleaf API Wrapper
Supports login, querying all projects, querying a specific project, downloading a project and
uploading a file to a project.
"""
_ce = False

def __init__(self, cookie=None, csrf=None):
def __init__(self, cookie=None, csrf=None, ce_url=None):
self._cookie = cookie # Store the cookie for authenticated requests
self._csrf = csrf # Store the CSRF token since it is needed for some requests

# Where to get the CSRF Token and where to send the login request to
if ce_url != None:
self._ce = True
self._BASE_URL = ce_url
else:
self._BASE_URL = "https://www.overleaf.com" # The Overleaf Base URL

self._LOGIN_URL = self._BASE_URL + "/login"
self._PROJECT_URL = self._BASE_URL + "/project" # The dashboard URL
# The URL to download all the files in zip format
self._DOWNLOAD_URL = self._BASE_URL + "/project/{}/download/zip"
self._UPLOAD_URL = self._BASE_URL + "/project/{}/upload" # The URL to upload files
self._FOLDER_URL = self._BASE_URL + "/project/{}/folder" # The URL to create folders

def login(self, username, password):
"""
Login to the Overleaf Service with a username and a password
Params: username, password
Returns: Dict of cookie and CSRF
"""

get_login = reqs.get(LOGIN_URL)
get_login = reqs.get(self._LOGIN_URL)
self._csrf = BeautifulSoup(get_login.content, 'html.parser').find(
'input', {'name': '_csrf'}).get('value')
login_json = {
"_csrf": self._csrf,
"email": username,
"password": password
}
post_login = reqs.post(LOGIN_URL, json=login_json,
post_login = reqs.post(self._LOGIN_URL, json=login_json,
cookies=get_login.cookies)

# On a successful authentication the Overleaf API returns a new authenticated cookie.
# If the cookie is different than the cookie of the GET request the authentication was successful
if post_login.status_code == 200 and get_login.cookies["overleaf_session2"] != post_login.cookies[
"overleaf_session2"]:
if post_login.status_code == 200 and ((self._ce and get_login.cookies["sharelatex.sid"] != post_login.cookies[
"sharelatex.sid"]) or get_login.cookies["overleaf_session2"] != post_login.cookies[
"overleaf_session2"]):

self._cookie = post_login.cookies

# Enrich cookie with gke-route cookie from GET request above
self._cookie['gke-route'] = get_login.cookies['gke-route']
if not self._ce:
# Enrich cookie with gke-route cookie from GET request above
self._cookie['gke-route'] = get_login.cookies['gke-route']

return {"cookie": self._cookie, "csrf": self._csrf}

Expand All @@ -71,7 +79,7 @@ def all_projects(self):
Get all of a user's active projects (= not archived)
Returns: List of project objects
"""
projects_page = reqs.get(PROJECT_URL, cookies=self._cookie)
projects_page = reqs.get(self._PROJECT_URL, cookies=self._cookie)
json_content = json.loads(
BeautifulSoup(projects_page.content, 'html.parser').find('script', {'id': 'data'}).contents[0])
return list(filter(lambda x: not x.get("archived"), json_content.get("projects")))
Expand All @@ -83,7 +91,7 @@ def get_project(self, project_name):
Returns: project object
"""

projects_page = reqs.get(PROJECT_URL, cookies=self._cookie)
projects_page = reqs.get(self._PROJECT_URL, cookies=self._cookie)
json_content = json.loads(
BeautifulSoup(projects_page.content, 'html.parser').find('script', {'id': 'data'}).contents[0])
return next(
Expand All @@ -96,7 +104,7 @@ def download_project(self, project_id):
Params: project_id, the id of the project
Returns: bytes string (zip file)
"""
r = reqs.get(DOWNLOAD_URL.format(project_id),
r = reqs.get(self._DOWNLOAD_URL.format(project_id),
stream=True, cookies=self._cookie)
return r.content

Expand All @@ -117,7 +125,7 @@ def create_folder(self, project_id, parent_folder_id, folder_name):
"_csrf": self._csrf,
"name": folder_name
}
r = reqs.post(FOLDER_URL.format(project_id),
r = reqs.post(self._FOLDER_URL.format(project_id),
cookies=self._cookie, json=params)

if r.ok:
Expand All @@ -144,15 +152,22 @@ def set_project_infos(a, project_infos_dict, c, d):
project_infos = project_infos_dict

# Convert cookie from CookieJar to string
cookie = "gke-route={}; overleaf_session2={}" \
.format(
reqs.utils.dict_from_cookiejar(self._cookie)["gke-route"],
reqs.utils.dict_from_cookiejar(self._cookie)["overleaf_session2"]
)
cookie = None
if self._ce:
cookie = "sharelatex.sid={}" \
.format(
reqs.utils.dict_from_cookiejar(self._cookie)["sharelatex.sid"]
)
else:
cookie = "gke-route={}; overleaf_session2={}" \
.format(
reqs.utils.dict_from_cookiejar(self._cookie)["gke-route"],
reqs.utils.dict_from_cookiejar(self._cookie)["overleaf_session2"]
)

# Connect to Overleaf Socket.IO, send a time parameter and the cookies
socket_io = SocketIO(
BASE_URL,
self._BASE_URL,
params={'t': int(time.time())},
headers={'Cookie': cookie}
)
Expand Down Expand Up @@ -217,6 +232,6 @@ def upload_file(self, project_id, project_infos, file_name, file_size, file):
}

# Upload the file to the predefined folder
r = reqs.post(UPLOAD_URL.format(project_id), cookies=self._cookie, params=params, files=files)
r = reqs.post(self._UPLOAD_URL.format(project_id), cookies=self._cookie, params=params, files=files)

return r.status_code == str(200) and json.loads(r.content)["success"]
24 changes: 22 additions & 2 deletions olsync/olsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ def main(ctx, local, remote, cookie_path, sync_path, olignore_path):
with open(cookie_path, 'rb') as f:
store = pickle.load(f)

overleaf_client = OverleafClient(store["cookie"], store["csrf"])
ce_url = olceChecker(sync_path)

overleaf_client = OverleafClient(store["cookie"], store["csrf"], ce_url)

project = execute_action(
lambda: overleaf_client.get_project(
Expand Down Expand Up @@ -119,7 +121,7 @@ def login(username, password, cookie_path):


def login_handler(username, password, path):
overleaf_client = OverleafClient()
overleaf_client = OverleafClient(None, None, olceChecker())
store = overleaf_client.login(username, password)
if store is None:
return False
Expand Down Expand Up @@ -233,6 +235,24 @@ def olignore_keep_list(sync_path, olignore_path):
keep_list = [item for item in keep_list if not os.path.isdir(item)]
return keep_list

def olceChecker(sync_path = ""):

# Read .olce file for URL to local installation of Overleaf/Sharelatex
olce_file = os.path.join(sync_path, ".olce")

click.echo("=" * 40)
ce_url = None
if not os.path.isfile(olce_file):
click.echo("\nNotice: .olce file does not exist, will sync with overleaf.com.")
else:

with open(olce_file, 'r') as f:
ce_url = f.readline()
f.close()

click.echo("\n.olce: using %s as CE server" % ce_url)

return ce_url

if __name__ == "__main__":
main()