Skip to content
This repository has been archived by the owner on Dec 30, 2021. It is now read-only.

Commit

Permalink
lynda-dl v0.3, added support for cookie based login, updated code qua…
Browse files Browse the repository at this point in the history
…lity, fixed #45, fixed #44
  • Loading branch information
r0oth3x49 committed Nov 4, 2018
1 parent 7e6467e commit c59cbb8
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 63 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ lynda.json
course.json
format.json
course.txt
cookies.txt
pack.sublime-project
pack.sublime-workspace
# Byte-compiled / optimized / DLL files
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ experience the problem? All these details will help to fix any potential bugs as
>
> Any other information you want to share that is relevant to the issue being reported.
## ***Extracting Cookies***

- Login to your lynda account via browser.
- Once you are logged in right click on page the search for option called **Inspect Element** and click on that.
- Under that look for **Network Tab** and click on that. Under that **Network Tab** click on Requests type **XHR** .
- Now Visit the **Course URL** you want to download, You will see some requests under **Network Tab XHR**.
- Right click on any of the Requests which links to **lynda.com**. Simply copy **Request Headers** and save to text file.
- Done run the lynda-dl against that text file it will start downloading the course.

## ***Requirements***

- Python (2 or 3)
Expand Down Expand Up @@ -126,8 +135,8 @@ You can download the latest version of lynda-dl by cloning the GitHub repository
<pre><code>
Author: Nasir khan (<a href="http://r0oth3x49.herokuapp.com/">r0ot h3x49</a>)

usage: lynda-dl.py [-h] [-v] [-u] [-p] [-o] [-d] [-q] [--info] [--sub-only]
[--skip-sub]
usage: lynda-dl.py [-h] [-v] [-k] [-u] [-p] [-o] [-d] [-q] [--info]
[--sub-only] [--skip-sub]
course

A cross-platform python based utility to download courses from lynda for
Expand All @@ -141,6 +150,7 @@ General:
-v, --version Shows the version.

Authentication:
-k , --cookies Cookies to authenticate with.
-u , --username Username or Library Card Number.
-p , --password Password or Library Card Pin.
-o , --organization Organization, registered at Lynda.
Expand All @@ -157,5 +167,6 @@ Others:
Example:
python lynda-dl.py COURSE_URL
python lynda-dl.py -o organization COURSE_URL
python lynda-dl.py -k cookies.txt COURSE_URL

</code></pre>
133 changes: 88 additions & 45 deletions lynda-dl.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@

class Lynda(ProgressBar):

def __init__(self, url, username='', password='', organization=''):
def __init__(self, url, username='', password='', organization='', cookies=''):
self.url = url
self.username = username
self.password = password
self.cookies = cookies
self.organization = organization
super(Lynda, self).__init__()

Expand Down Expand Up @@ -148,11 +149,14 @@ def download_lectures_and_captions(self, lecture_best='', lecture_title='', inne
self.download_subtitles(subtitle=subtitle, filepath=filepath)

def course_download(self, path='', quality='', caption_only=False, skip_captions=False):
if not self.organization:
sys.stdout.write(fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sb + "Trying to login as " + fm + sb +"(%s)" % (self.username) + fg + sb +"...\n")
if self.organization:
sys.stdout.write(fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sb + "Trying to login as organization " + fm + sb +"(%s)" % (self.organization) + fg + sb +"...\n")
course = lynda.course(url=self.url, username=self.username, password=self.password, organization=self.organization)
if self.cookies:
sys.stdout.write(fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sb + "Trying to login using cookies ...\n")
if not self.cookies:
if not self.organization:
sys.stdout.write(fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sb + "Trying to login as " + fm + sb +"(%s)" % (self.username) + fg + sb +"...\n")
if self.organization:
sys.stdout.write(fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sb + "Trying to login as organization " + fm + sb +"(%s)" % (self.organization) + fg + sb +"...\n")
course = lynda.course(url=self.url, username=self.username, password=self.password, organization=self.organization, cookies=self.cookies)
course_id = course.id
course_name = course.title
chapters = course.get_chapters()
Expand Down Expand Up @@ -218,6 +222,11 @@ def main():
help="Shows the version.")

authentication = parser.add_argument_group("Authentication")
authentication.add_argument(
'-k', '--cookies',\
dest='cookies',\
type=str,\
help="Cookies to authenticate with.",metavar='')
authentication.add_argument(
'-u', '--username',\
dest='username',\
Expand Down Expand Up @@ -272,13 +281,84 @@ def main():
sys.stdout.write (fc + sd + "[" + fw + sb + "+" + fc + sd + "] : " + fw + sd + "Found (%s) courses ..\n" % (len(courses)))
for course in courses:

if options.cookies:
f_in = open(options.cookies)
cookies = '\n'.join([line for line in (l.strip() for l in f_in) if line])
f_in.close()
lynda = Lynda(url=course, cookies=cookies)
if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

if not options.cookies:
if not options.username and not options.password:
username = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Username : " + fg + sb
password = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Password : " + fc + sb
email = getpass.getuser(prompt=username)
passwd = getpass.getpass(prompt=password)
if email and passwd:
lynda = Lynda(url=course, username=email, password=passwd, organization=options.org)
else:
sys.stdout.write('\n' + fc + sd + "[" + fr + sb + "-" + fc + sd + "] : " + fr + sb + "Username and password is required.\n")
sys.exit(0)

if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

elif options.username and options.password:
lynda = Lynda(url=course, username=options.username, password=options.password, organization=options.org)
if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

if not os.path.isfile(options.course):

if options.cookies:
f_in = open(options.cookies)
cookies = '\n'.join([line for line in (l.strip() for l in f_in) if line])
f_in.close()
lynda = Lynda(url=options.course, cookies=cookies)
if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

if not options.cookies:
if not options.username and not options.password:
username = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Username : " + fg + sb
password = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Password : " + fc + sb
email = getpass.getuser(prompt=username)
passwd = getpass.getpass(prompt=password)
if email and passwd:
lynda = Lynda(url=course, username=email, password=passwd, organization=options.org)
lynda = Lynda(url=options.course, username=email, password=passwd, organization=options.org)
else:
sys.stdout.write('\n' + fc + sd + "[" + fr + sb + "-" + fc + sd + "] : " + fr + sb + "Username and password is required.\n")
sys.exit(0)
Expand All @@ -295,7 +375,7 @@ def main():
lynda.course_download(path=options.output, quality=options.quality)

elif options.username and options.password:
lynda = Lynda(url=course, username=options.username, password=options.password, organization=options.org)
lynda = Lynda(url=options.course, username=options.username, password=options.password, organization=options.org)
if options.info:
lynda.course_list_down()

Expand All @@ -307,43 +387,6 @@ def main():
else:
lynda.course_download(path=options.output, quality=options.quality)

if not os.path.isfile(options.course):

if not options.username and not options.password:
username = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Username : " + fg + sb
password = fc + sd + "[" + fm + sb + "*" + fc + sd + "] : " + fg + sd + "Password : " + fc + sb
email = getpass.getuser(prompt=username)
passwd = getpass.getpass(prompt=password)
if email and passwd:
lynda = Lynda(url=options.course, username=email, password=passwd, organization=options.org)
else:
sys.stdout.write('\n' + fc + sd + "[" + fr + sb + "-" + fc + sd + "] : " + fr + sb + "Username and password is required.\n")
sys.exit(0)

if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

elif options.username and options.password:
lynda = Lynda(url=options.course, username=options.username, password=options.password, organization=options.org)
if options.info:
lynda.course_list_down()

if not options.info:
if options.caption_only and not options.skip_captions:
lynda.course_download(caption_only=options.caption_only, path=options.output)
elif not options.caption_only and options.skip_captions:
lynda.course_download(skip_captions=options.skip_captions, path=options.output, quality=options.quality)
else:
lynda.course_download(path=options.output, quality=options.quality)

if __name__ == '__main__':
try:
main()
Expand Down
2 changes: 1 addition & 1 deletion lynda/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

__version__ = "0.2"
__version__ = "0.3"
__author__ = "Nasir Khan (r0ot h3x49)"
__license__ = 'MIT'
__copyright__ = 'Copyright (c) 2018 Nasir Khan (r0ot h3x49)'
Expand Down
30 changes: 28 additions & 2 deletions lynda/_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
conn_error,
HEADERS,
LOGOUT_URL,
ParseCookie,
USER_LOGIN_URL,
AJAX_USERNAME,
AJAX_PASSWORD,
Expand Down Expand Up @@ -158,8 +159,33 @@ def _organization_session(self):
sys.stdout.write(fc + sd + "[" + fr + sb + "-" + fc + sd + "] : " + fr + sb + "Failed to extract login-form..\n")
sys.exit(0)

def authenticate(self):
def _cookie_session_step(self, raw_cookies):
cookies = {}
cookie_parser = ParseCookie()
try:
cookie_string = re.search(r'Cookie:\s*(.+)\n', raw_cookies, flags=re.I).group(1)
except:
sys.stdout.write(fc + sd + "[" + fr + sb + "-" + fc + sd + "] : " + fr + sb + "Cookies error, Request Headers is required.\n")
sys.stdout.write(fc + sd + "[" + fm + sb + "i" + fc + sd + "] : " + fg + sb + "Copy Request Headers for single request to a file, while you are logged in.\n")
sys.exit(0)
cookie_parser.load(cookie_string)
for key, cookie in cookie_parser.items():
cookies[key] = cookie.value
return cookies

def _cookies_session(self, cookies):
auth_cookies = self._cookie_session_step(raw_cookies=cookies)
if auth_cookies:
self._session.cookies.update(auth_cookies)
return self._session
else:
return None

def authenticate(self, cookies=''):
if cookies:
return self._cookies_session(cookies=cookies)
if self.organization:
return self._organization_session()
else:
return self._user_session()
return self._user_session()

5 changes: 3 additions & 2 deletions lynda/_colorized/banner.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
'''

from .colors import *
from .. import __version__

def banner():
banner = """%s%s
Expand All @@ -35,8 +36,8 @@ def banner():
888 `888' 888 888 888 888 d8( 888 888 888 888
o888o .8' o888o o888o `Y8bod88P" `Y888""8o `Y8bod88P" o888o
.o..P'
`Y8P'\t\t\t\t%s%sVersion : %s%s0.2\n\t\t\t\t\t%s%sAuthor : %s%sNasir Khan (r0ot h3x49)\n\t\t\t\t\t%s%sGithub : %s%shttps://github.com/r0oth3x49
`Y8P'\t\t\t\t%s%sVersion : %s%s%s\n\t\t\t\t\t%s%sAuthor : %s%sNasir Khan (r0ot h3x49)\n\t\t\t\t\t%s%sGithub : %s%shttps://github.com/r0oth3x49
""" % (fc, sb, fm, sb, fc, sb, fm, sb, fc, sb, fy,sb, fg, sd, fy,sb, fg, sd, fy,sb, fg, sd)
""" % (fc, sb, fm, sb, fc, sb, fm, sb, fc, sb, fy,sb, fg, sd, __version__, fy,sb, fg, sd, fy,sb, fg, sd)
return banner
3 changes: 3 additions & 0 deletions lynda/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from urllib.request import urlopen as compat_urlopen
from urllib.request import build_opener as compat_opener
from html.parser import HTMLParser as compat_HTMLParser
from http.cookies import SimpleCookie as ParseCookie
from requests.exceptions import ConnectionError as conn_error

encoding, pyver = str, 3
Expand All @@ -57,6 +58,7 @@
from urllib2 import build_opener as compat_opener
from urlparse import urlparse as compat_urlparse
from HTMLParser import HTMLParser as compat_HTMLParser
from Cookie import SimpleCookie as ParseCookie
from requests.exceptions import ConnectionError as conn_error

encoding, pyver = unicode, 2
Expand Down Expand Up @@ -110,6 +112,7 @@
'compat_HTMLParser',
'HEADERS',
'NO_DEFAULT',
'ParseCookie',

'USER_LOGIN_URL',
'AJAX_USERNAME',
Expand Down
Loading

0 comments on commit c59cbb8

Please sign in to comment.