├── README.image
└── 1.png
├── README.md
├── app.js
├── download.js
├── package.json
├── renderer.js
├── router.js
└── views
├── download.html
├── foot.html
├── form.html
└── head.html
/README.image/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MakhouT/treehouseDownloader/a7229413e8e0da73bc99708aae3d7a79d1c8f758/README.image/1.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Not active any more
2 | Since the upgrade of treehouse it is not working any more - since I don't have a treehouse subscription any more - I am not able to work on this. However I am happy to pick this up in the future when I do get myself a treehouse subscription again or if someone lends me theirs for a week or so.
3 |
4 | #Treehouse downloader
5 | This tool will help you download videos from the website www.teamtreehouse.com if you have an active subscription and don't want to use the build in downloader with iTunes.
6 | The application will also create all the folders for you numbering them so you can see all the video's in the correct order.
7 |
8 | #How to start
9 | 1. Pull the project
10 | ``` git clone https://github.com/MakhouT/treehouseDownloader.git ```
11 | 2. With Node.js installed ([Node.js website](https://nodejs.org/en/))
12 | 3. Open the command prompt/terminal and navigate to the folder where the application is
13 | ```cd treehouseDownloader```
14 | 4. Type ``` npm install ``` to install all the required packages
15 | 5. Run ```node app.js```
16 | 6. In a browser browse to http://127.0.0.1:8080/
17 |
18 |
19 | 7.1 Copy the course download link to download from iTunes and insert it in the input of the application, this link should start with 'itpc' and should end with you token corresponding to your account. This will download 1 course.
20 |
21 | ```
22 | itpc://teamtreehouse.com/library/build-an-interactive-story-app.rss?feed_token=xxxxxxxx-b326-4ada-8ce4-ca456d6axxxx
23 | ```
24 |
25 | 7.2 To download a full track paste in the url of the track and manually add "?tokencode", so it would look something like this
26 | ```
27 | https://teamtreehouse.com/library/adobe-illustrator-for-web-design?xxxxxxxx-b326-4ada-8ce4-ca456d6axxxx
28 | ```
29 | Note that downloading a full track is still instable.
30 |
31 | 8. Press download and have some patience until the video's are downloaded.
32 |
33 |
34 | It should create a folder for each chapter of the course and each video inside of it should be numbered so you can watch them in the correct order.
35 | If you find any bugs please report them so I can fix them.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var router = require("./router.js");
3 | var http = require("http");
4 |
5 | //Create a basic server
6 | http.createServer(function(request, response){
7 | router.home(request, response);
8 | }).listen(8080, "127.0.0.1");
9 | console.log("Server up and running on 127.0.0.1:8080");
--------------------------------------------------------------------------------
/download.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var download = require("download");
3 | var downloadStatus = require("download-status");
4 | var https = require("https");
5 | var xmlParser = require("xml2js").parseString;
6 | var mkdirp = require('mkdirp');
7 |
8 | function downloadVideo(source, hd){
9 | // Retrieve xml from treehouse
10 | https.get(source, function(response){
11 | var xml = "";
12 |
13 | // Put all the xml data in a variable
14 | response.on("data", function(chunk){
15 | xml += chunk;
16 | });
17 |
18 | // When that's done, filter the xml
19 | // And add all the links in an array
20 | response.on("end", function(){
21 | xmlParser(xml, function (err, xmlObject) {
22 | var courseItem = xmlObject.rss.channel[0].item;
23 | var courseLinks = [];
24 |
25 | //I'm adding all the link
26 | courseItem.forEach(function(course){
27 | courseLinks.push(course.enclosure[0].$.url + hd);
28 | });
29 |
30 | var moduleNumbering = 0;
31 | var previousModuleFolder = "";
32 | var courseFileNameNumbering;
33 | var firstVideoOfModule = true;
34 |
35 | courseLinks.forEach(function(course){
36 | var courseDetails = course.replace("https:\/\/teamtreehouse.com\/library\/", "").split("\/");
37 | var courseName = courseDetails[0];
38 | var courseModule = courseDetails[1];
39 | var courseVideo = courseDetails[2];
40 | var courseFileNameNoNumbering = (courseDetails[3].substring(0, courseDetails[3].indexOf("?"))).replace("download", courseVideo); //The filename missing the filenumbering
41 |
42 | if(courseModule !== previousModuleFolder){
43 | //Starting a new module
44 | moduleNumbering++;
45 | previousModuleFolder = courseModule;
46 | courseFileNameNumbering = 1;
47 | }
48 |
49 | //Add leading zero if numbers are from 1-9
50 | if(courseFileNameNumbering < 10){
51 | courseFileNameNumbering = "0" + courseFileNameNumbering;
52 | }
53 |
54 | //Form the name of the file with numbering so you can see the video's in the correct order
55 | //Also removing the token and options from the originel filename
56 | var courseFileName = courseFileNameNumbering + ". " + courseFileNameNoNumbering;
57 | courseFileNameNumbering++;
58 | var directory = courseName + "\/" + moduleNumbering + ". " + courseModule;
59 |
60 | //Make all the directories and download the video's in the correct directory
61 | mkdirp.sync(directory);
62 | new download({mode: "755"}).get(course).rename(courseFileName).dest(directory).use(downloadStatus()).run();
63 | });
64 | });
65 | });
66 | });
67 | }
68 |
69 | module.exports.downloadVideo = downloadVideo;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "treehouse",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node app.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "download": "^4.4.3",
14 | "download-status": "^2.2.1",
15 | "mkdirp": "^0.5.1",
16 | "xml2js": "^0.4.15"
17 | },
18 | "devDependencies": {
19 | "x-ray": "^2.0.3"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/renderer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var fs = require("fs");
3 |
4 | //Renders the files that are passed as arguments
5 | function view(templateName, response){
6 | var fileContents = fs.readFileSync("./views/" + templateName + ".html");
7 | response.write(fileContents);
8 | }
9 |
10 | module.exports.view = view;
--------------------------------------------------------------------------------
/router.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var xray = require("x-ray");
3 | var renderer = require("./renderer.js");
4 | var downloadVideo = require("./download");
5 | var queryString = require("querystring");
6 | var url;
7 | var hd;
8 |
9 | function home(request, response){
10 | if(request.url === "/"){
11 | //Home page, this will show the actuall form to start a download
12 | if(request.method.toUpperCase() === "GET"){
13 | response.writeHead(200, {"Content-Type": "text/html"});
14 | renderer.view("head", response);
15 | renderer.view("form", response);
16 | renderer.view("foot", response);
17 | response.end();
18 | }
19 | else{
20 | //When you submit the form, we should start downloading the video
21 | // We take the data from the form parse it then send it to the download module
22 | request.on("data", function(postBody){
23 | var query = queryString.parse(postBody.toString());
24 | url = query.url.replace("itpc", "https");
25 | downloadVideo.downloadVideo(url);
26 | });
27 | }
28 | }
29 | else if(request.url === "/download" && request.method.toUpperCase() === "POST"){
30 | //Received the url from the form, start the download
31 | request.on("data", function(postBody){
32 | var query = queryString.parse(postBody.toString());
33 | var protocol = query.url.substring(0,7);
34 | var hdOption = query.hd;
35 |
36 | //If HD option is enabled we add that option to the final url
37 | if(hdOption === "hd"){
38 | hd = "&hd=yes";
39 | }
40 | else{
41 | hd = "";
42 | }
43 |
44 | //if the protocol is itpc then we're downloading a single track
45 | //else we're downloading a whole track
46 | if(protocol === "itpc://"){
47 | url = query.url.replace("itpc", "https");
48 | downloadVideo.downloadVideo(url, hd);
49 | }
50 | else{
51 | var token = query.url.substring(query.url.indexOf("?")+1);
52 | var trackUrl = query.url.substring(0, query.url.indexOf("?"));
53 |
54 | var x = xray();
55 | x(trackUrl, "#track-steps li.card.course a.card-title", ["@href"])(function(err, data){
56 | data.forEach(function(course){
57 | var courseFromTrack = course + ".rss?feed_token=" + token;
58 | downloadVideo.downloadVideo(courseFromTrack, hd);
59 | });
60 | });
61 | }
62 | });
63 |
64 | response.writeHead(200, {"Content-Type": "text/html"});
65 | renderer.view("head", response);
66 | renderer.view("download", response);
67 | renderer.view("foot", response);
68 | }
69 | else{
70 | //Any url that doesn't exist or shouldn't be accessed, redirect to the homepage
71 | response.writeHead(301, {Location: 'http://127.0.0.1:8080/'});
72 | response.end();
73 | }
74 | }
75 |
76 | module.exports.home = home;
--------------------------------------------------------------------------------
/views/download.html:
--------------------------------------------------------------------------------
1 |