youtube: update scraper for youtube.com change
[conkeror.git] / modules / page-modes / youtube.js
blob5b0a9d242f00fa9d0f5a1522c3a6843ec725831c
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2009-2010 John J. Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 in_module(null);
11 require("content-buffer.js");
12 require("media.js");
14 let media_youtube_content_key_regexp = /&t=([^&]+)/;
15 let media_youtube_content_title_regexp = /<meta name="title" content="([^"]+)">/;
19  * Youtube Format Scrapers
20  *
21  *    Given a push function, the id of the video, the `t' key, and the
22  * text of the document (to scrape further data if necessary), the format
23  * scrapers' job is to call the push function with four args: url, file
24  * extension, mime type, and description.
25  *
26  *    Scrapers should return true on success and false on failure.
27  */
28 function youtube_scrape_standard_flv (push, id, t, text) {
29     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&asv=3',
30          'flv', 'video/x-flv', 'standard flv');
31     return true;
34 function youtube_scrape_hq_mp4 (push, id, t, text) {
35     if (/"fmt_map": ""/.test(text))
36         return false;
37     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=18'+'&asv=3',
38          'mp4', 'video/mp4', 'hq mp4');
39     return true;
42 function youtube_scrape_720p_mp4 (push, id, t, text) {
43     if (!(/'IS_HD_AVAILABLE': true/.test(text)))
44         return false;
45     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=22'+'&asv=3',
46          'mp4', 'video/mp4', '720p mp4');
47     return true;
50 function youtube_scrape_1080p_mp4 (push, id, t, text) {
51     if (!(/"fmt_map": "37/.test(text)))
52         return false;
53     push('http://youtube.com/get_video?video_id='+id+'&t='+t+'&fmt=37'+'&asv=3',
54          'mp4', 'video/mp4', '1080p mp4');
55     return true;
60  * Scraper Composition
61  */
63 /**
64  * make_call_each takes any number of functions as arguments and returns a
65  * closure which, when called, calls each of those functions in order, and
66  * finally returns true if any of the functions returned true.
67  */
68 function make_call_each () {
69     var fns = Array.prototype.slice.call(arguments, 0);
70     return function () {
71         var args = Array.prototype.slice.call(arguments, 0);
72         var found = false;
73         fns.map(function (fn) {
74             if (fn.apply(this, args))
75                 found = true;
76         });
77         return found;
78     };
81 /**
82  * make_call_each_until_success takes any number of functions as arguments
83  * and returns a closure which, when called, calls each of those functions
84  * in order until one returns true.
85  */
86 function make_call_each_until_success () {
87     var fns = Array.prototype.slice.call(arguments, 0);
88     return function () {
89         var args = Array.prototype.slice.call(arguments, 0);
90         for each (var fn in fns) {
91             if (fn.apply(this, args))
92                 return true;
93         }
94         return false;
95     };
98 define_variable('youtube_scrape_function',
99                 make_call_each(youtube_scrape_1080p_mp4,
100                                youtube_scrape_720p_mp4,
101                                youtube_scrape_hq_mp4,
102                                youtube_scrape_standard_flv),
103     "This function is called as the last step of scraping a youtube page, "+
104     "after the basic information needed to build the media url has been "+
105     "extracted. Youtube_scape_function is called with four arguments: a "+
106     "`push' function, the id, the t key, and the text of the page.  Its "+
107     "job is to call the push function for each media url desired with "+
108     "the arguments url, file extension, mime type, and description.  It "+
109     "should return true if it called the push function at least once, and "+
110     "otherwise false.");
113 function youtube_scrape_text (results, frame, id, text) {
114     var title_match = media_youtube_content_title_regexp.exec(text);
115     if (!title_match)
116         return null;
117     var title = decodeURIComponent(title_match[1]);
118     var res = media_youtube_content_key_regexp.exec(text);
119     if (!res)
120         return null;
121     function push (url, extension, mime_type, description) {
122         results.push(load_spec({
123             uri: url,
124             suggest_filename_from_uri: false,
125             title: title,
126             filename_extension: extension,
127             source_frame: frame,
128             mime_type: mime_type,
129             description: description
130         }));
131     }
132     youtube_scrape_function(push, id, res[1], text);
135 function media_scrape_youtube (buffer, results) {
136     try {
137         var uri = buffer.current_uri.spec;
138         var result = media_youtube_uri_test_regexp.exec(uri);
139         if (!result)
140             return;
141         let text = buffer.document.documentElement.innerHTML;
142         let id = result[1];
143         youtube_scrape_text(results, buffer.top_frame, id, text);
144     } catch (e if !(e instanceof interactive_error)) {}
147 define_page_mode("youtube_mode",
148     $display_name = "YouTube",
149     $enable = function (buffer) {
150         media_setup_local_object_classes(buffer);
151     });
153 function media_scrape_embedded_youtube (buffer, results) {
154     const embedded_youtube_regexp = /^http:\/\/[a-zA-Z0-9\-.]+\.youtube\.com\/v\/(.*)$/;
155     for (let frame in frame_iterator(buffer.top_frame, buffer.focused_frame)) {
156         // Look for embedded YouTube videos
157         let obj_els = frame.document.getElementsByTagName("object");
158         for (let i = 0; i < obj_els.length; ++i) {
159             let obj_el = obj_els[i];
160             let param_els = obj_el.getElementsByTagName("param");
161             inner:
162             for (let j = 0; j < param_els.length; ++j) {
163                 let param_el = param_els[j];
164                 let match;
165                 if (param_el.getAttribute("name").toLowerCase() == "movie" &&
166                     (match = embedded_youtube_regexp.exec(param_el.getAttribute("value"))) != null) {
167                     try {
168                         let id = match[1];
169                         let lspec = load_spec({uri: "http://youtube.com/watch?v=" + id});
170                         var result =
171                             yield buffer.window.minibuffer.wait_for(
172                                 "Requesting " + lspec.uri + "...",
173                                 send_http_request(lspec));
174                         let text = result.responseText;
175                         if (text != null && text.length > 0)
176                             youtube_scrape_text(results, frame, id, text);
177                     } catch (e if (e instanceof abort)) {
178                         // Still allow other media scrapers to try even if the user aborted an http request,
179                         // but don't continue looking for embedded youtube videos.
180                         return;
181                     } catch (e) {
182                         // Some other error here means there was some problem with the request.
183                         // We'll just ignore it.
184                     }
185                     break inner;
186                 }
187             }
188         }
189     }
193 let media_youtube_uri_test_regexp = build_url_regex($domain = /(?:[a-z]+\.)?youtube/,
194                                                     $path = /watch\?v=([A-Za-z0-9\-_]+)/);
195 media_scrapers.unshift([/.*/, media_scrape_embedded_youtube]);
196 media_scrapers.unshift([media_youtube_uri_test_regexp, media_scrape_youtube]);
197 auto_mode_list.push([media_youtube_uri_test_regexp, youtube_mode]);
199 provide("youtube");