-
Notifications
You must be signed in to change notification settings - Fork 54
/
echo360download
317 lines (288 loc) · 11.6 KB
/
echo360download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python3
import argparse
import os
import sys
import re
import logging
import time
from datetime import datetime
try:
import pick
except ImportError as e:
# check if this is windows, if so install windows curse on the fly
if 'win32' not in sys.platform:
raise e
import subprocess
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'windows-curses'])
from .echo_exceptions import EchoLoginError
from .downloader import EchoDownloader
from .course import EchoCourse, EchoCloudCourse
_DEFAULT_BEFORE_DATE = datetime(2900, 1, 1).date()
_DEFAULT_AFTER_DATE = datetime(1100, 1, 1).date()
_LOGGER = logging.getLogger(__name__)
def try_parse_date(date_string, fmt):
try:
return datetime.strptime(date_string, fmt).date()
except ValueError:
print("Error parsing date input:", sys.exc_info())
sys.exit(1)
def handle_args():
parser = argparse.ArgumentParser(
description="Download lectures from portal.")
parser.add_argument(
"url",
help="Full URL of the echo360 course page, \
or only the UUID (which defaults to USYD). \
The URL of the course's video lecture page, \
for example: http://recordings.engineering.illinois.edu/ess/portal/section/115f3def-7371-4e98-b72f-6efe53771b2a)", # noqa
metavar="ECHO360_URL")
parser.add_argument(
"--output",
"-o",
help="Path to the desired output directory. The output \
directory must exist. Otherwise the current \
directory is used.",
metavar="OUTPUT_PATH")
parser.add_argument(
"--after-date",
"-a",
dest="after_date",
help="Only download lectures newer than AFTER_DATE \
(inclusive). Note: this may be combined with \
--before-date.",
metavar="AFTER_DATE(YYYY-MM-DD)")
parser.add_argument(
"--before-date",
"-b",
dest="before_date",
help="Only download lectures older than BEFORE_DATE \
(inclusive). Note: this may be combined with \
--after-date",
metavar="BEFORE_DATE(YYYY-MM-DD)")
parser.add_argument(
"--unikey",
"-u",
dest="unikey",
help="Your unikey for your University of \
Sydney elearning account",
metavar="UNIKEY")
parser.add_argument(
"--password",
"-p",
dest="password",
help="Your password for your University of \
Sydney elearning account",
metavar="PASSWORD")
parser.add_argument(
"--setup-credentials",
action='store_true',
default=False,
dest="setup_credential",
help="Open a chrome instance to expose an ability for user to log into \
any website to obtain credentials needed before proceeding. \
(implies using chrome-driver)"
)
parser.add_argument(
"--download-phantomjs-binary",
action='store_true',
default=False,
dest="download_binary",
help="Force the echo360.py script to download a local \
binary file for phantomjs (will override system bin)"
)
parser.add_argument(
"--chrome",
action='store_true',
default=False,
dest="use_chrome",
help="Use Chrome Driver instead of phantomjs webdriver. You \
must have chromedriver installed in your PATH.")
parser.add_argument(
"--firefox",
action='store_true',
default=False,
dest="use_firefox",
help="Use Firefox Driver instead of phantomjs webdriver. You \
must have geckodriver installed in your PATH.")
parser.add_argument(
"--interactive",
'-i',
action='store_true',
default=False,
help="Interactively pick the lectures you want, instead of download all \
(default) or based on dates .")
parser.add_argument(
"--debug",
action='store_true',
default=False,
dest="enable_degbug",
help="Enable extensive logging.")
args = vars(parser.parse_args())
course_url = args["url"]
output_path = os.path.expanduser(
args["output"]) if args["output"] is not None else "default_out_path"
output_path = output_path if os.path.isdir(
output_path) else "default_out_path"
after_date = try_parse_date(
args["after_date"],
"%Y-%m-%d") if args["after_date"] else _DEFAULT_AFTER_DATE
before_date = try_parse_date(
args["before_date"],
"%Y-%m-%d") if args["before_date"] else _DEFAULT_BEFORE_DATE
username = args["unikey"]
password = args["password"]
# check if the given uuid is actually a full URL
course_hostname = re.search(
'https?:[/]{2}[^/]*',
course_url) # would be none if it does not exists
if course_hostname is not None:
course_hostname = course_hostname.group()
else:
_LOGGER.info(
"Non-URL value is given, defaults to University of Sydney's echo system"
)
_LOGGER.info(
"Use the full URL if you want to use this in other University")
args_without_sensitive_info = dict(args)
args_without_sensitive_info.pop('unikey', None)
args_without_sensitive_info.pop('password', None)
_LOGGER.debug("Input args: %s", args_without_sensitive_info)
_LOGGER.debug("Hostname: %s, UUID: %s", course_hostname, course_url)
webdriver_to_use = "phantomjs"
if args['use_chrome']:
webdriver_to_use = "chrome"
elif args['use_firefox']:
webdriver_to_use = "firefox"
return (course_url, course_hostname, output_path, after_date, before_date,
username, password, args['setup_credential'], args['download_binary'],
webdriver_to_use, args['interactive'], args['enable_degbug'])
def main():
(course_url, course_hostname, output_path, after_date, before_date,
username, password, setup_credential, download_binary, webdriver_to_use,
interactive_mode, enable_degbug) = handle_args()
setup_logging(enable_degbug)
usingEcho360Cloud = False
if "echo360.org" in course_hostname:
print("> Echo360 Cloud platform detected")
print("> This implies setup_credential, and using web_driver")
print(">> Please login with your SSO details and type continue when logged in.")
print('-' * 65)
usingEcho360Cloud = True
setup_credential = True
def cmd_exists(x):
any(
os.access(os.path.join(path, x), os.X_OK)
for path in os.environ["PATH"].split(os.pathsep))
# NOTE: local binary will always override system PATH binary
use_local_binary = True
if setup_credential and webdriver_to_use == 'phantomjs':
# setup credentials must use web driver
webdriver_to_use = 'chrome'
if webdriver_to_use == 'chrome':
from .binary_downloader.chromedriver import ChromedriverDownloader as binary_downloader
binary_type = 'chromedriver'
elif webdriver_to_use == 'firefox':
from .binary_downloader.firefoxdriver import FirefoxDownloader as binary_downloader
binary_type = 'geckodriver'
else:
from .binary_downloader.phantomjs import PhantomjsDownloader as binary_downloader
binary_type = 'phantomjs'
binary_downloader = binary_downloader() # initialise class
_LOGGER.debug("binary_downloader link: %s, bin path: %s",
binary_downloader.get_download_link(),
binary_downloader.get_bin())
# First test for existance of localbinary file
if not os.path.isfile(binary_downloader.get_bin()):
# If failed, then test for existance of global executable in PATH
if cmd_exists(binary_type):
use_local_binary = False
_LOGGER.debug("Using global binary file")
else:
# None exists, download binary file
start_download_binary(binary_downloader, binary_type)
_LOGGER.debug("Downloading binary file")
if download_binary:
start_download_binary(binary_downloader, binary_type, manual=True)
exit(0)
if usingEcho360Cloud:
# echo360 cloud
course_uuid = re.search('[^/]([0-9a-zA-Z]+[-])+[0-9a-zA-Z]+',
course_url).group() # retrieve the last part of the URL
course = EchoCloudCourse(course_uuid, course_hostname)
else:
course_uuid = re.search('[^/]+(?=/$|$)',
course_url).group() # retrieve the last part of the URL
course = EchoCourse(course_uuid, course_hostname)
downloader = EchoDownloader(
course,
output_path,
date_range=(after_date, before_date),
username=username,
password=password,
setup_credential=setup_credential,
use_local_binary=use_local_binary,
webdriver_to_use=webdriver_to_use,
interactive_mode=interactive_mode)
_LOGGER.debug('>>> Download will use "{}" webdriver from {} executable <<<'.format(
binary_type, 'LOCAL'
if use_local_binary else 'GLOBAL'))
if setup_credential:
run_setup_credential(downloader._driver, course_hostname, echo360_cloud=True)
downloader._driver.set_window_size(0, 0)
downloader.download_all()
def start_download_binary(binary_downloader, binary_type, manual=False):
print('=' * 65)
if not manual:
print(
'Binary file of {0} not found, will initiate a download process now...'.
format(binary_type))
binary_downloader.download()
print('Done!')
print('=' * 65)
def run_setup_credential(webdriver, url, echo360_cloud=False):
webdriver.get(url)
try:
# for making it compatiable with Python 2 & 3
input = raw_input
except NameError:
pass
try:
if echo360_cloud:
print(" >> After you finished logging into echo360 cloud, the window "
"should be automatically redirected and continued. If it got stuck, "
"please contact the author :)")
while True:
if echo360_cloud:
# automatically wait for the Auth Token from webdriver
if any('ECHO_JWT' in c['name'] for c in webdriver.get_cookies()):
break
time.sleep(2)
else:
user_inputs = input("> Type 'continue' and press [enter]\n")
if user_inputs.lower() == 'continue':
break
except KeyboardInterrupt:
pass
def setup_logging(enable_degbug=False):
# set up logging to file - see previous section for more details
logging_level = logging.DEBUG if enable_degbug else logging.INFO
root_path = os.path.dirname(
os.path.abspath(sys.modules['__main__'].__file__))
log_path = os.path.join(root_path, 'echo360Downloader.log')
logging.basicConfig(
level=logging_level,
format='[%(levelname)s: %(asctime)s] %(name)-12s %(message)s',
datefmt='%m-%d %H:%M',
filename=log_path,
filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logging.getLogger('').addHandler(console) # add handler to the root logger
if __name__ == '__main__':
try:
main()
except EchoLoginError:
# raise KeyboardInterrupt
pass