Update to new YouTube API
This commit is contained in:
		
							parent
							
								
									8bd77e09e5
								
							
						
					
					
						commit
						e7d87e3bbd
					
				
							
								
								
									
										145
									
								
								explore/api.js
								
								
								
								
							
							
						
						
									
										145
									
								
								explore/api.js
								
								
								
								
							|  | @ -5,20 +5,24 @@ const request = require('request'); | ||||||
| const cheerio = require('cheerio'); | const cheerio = require('cheerio'); | ||||||
| 
 | 
 | ||||||
| const YOUTUBE_BASE = 'https://www.youtube.com/%s'; | const YOUTUBE_BASE = 'https://www.youtube.com/%s'; | ||||||
|  | const WATCH_BASE = util.format(YOUTUBE_BASE, 'watch?v=%s'); | ||||||
|  | 
 | ||||||
| const VIDEO_ID_REGEX = /watch\?v=([^&]*)/i; | const VIDEO_ID_REGEX = /watch\?v=([^&]*)/i; | ||||||
| const END_SCREEN_BASE = util.format(YOUTUBE_BASE, 'get_endscreen?v=%s'); | // const END_SCREEN_BASE = util.format(YOUTUBE_BASE, 'get_endscreen?v=%s');
 | ||||||
| const CARD_BASE = util.format(YOUTUBE_BASE, 'annotations_invideo?video_id=%s'); | // const CARD_BASE = util.format(YOUTUBE_BASE, 'annotations_invideo?video_id=%s');
 | ||||||
|  | 
 | ||||||
|  | const playerRegex = /ytplayer\.config = (\{.*?\});/; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Récupère une liste des vidéos liées à la fin d’une autre vidéo. |  * Fetch the `ytplayer.config` object for a YouTube video. | ||||||
|  * |  * | ||||||
|  * @param videoId Identifiant de la vidéo source. |  * @async | ||||||
|  * @return Liste des vidéos liées. Chaque vidéo est un objet contenant |  * @param videoId Identifier of the video to fetch. | ||||||
|  * son identifiant (videoId) et son titre (title). |  * @return The player configuration object. | ||||||
|  */ |  */ | ||||||
| const getEndScreenVideos = videoId => | const getPlayerConfig = videoId => | ||||||
| { | { | ||||||
|     const url = util.format(END_SCREEN_BASE, videoId); |     const url = util.format(WATCH_BASE, videoId); | ||||||
| 
 | 
 | ||||||
|     return new Promise((resolve, reject) => |     return new Promise((resolve, reject) => | ||||||
|     { |     { | ||||||
|  | @ -30,87 +34,84 @@ const getEndScreenVideos = videoId => | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Suppression des caractères initiaux inutiles si applicable
 |             // Look for the definition of ytplayer.config and unserialize it
 | ||||||
|             if (body.substr(0, 3) === ')]}') |             // and the player_response subobject
 | ||||||
|  |             try | ||||||
|             { |             { | ||||||
|                 body = body.substr(3); |                 const playerConfig = JSON.parse(body.match(playerRegex)[1]); | ||||||
|  |                 playerConfig.args.player_response | ||||||
|  |                     = JSON.parse(playerConfig.args.player_response); | ||||||
|  |                 resolve(playerConfig); | ||||||
|             } |             } | ||||||
| 
 |             catch (err) | ||||||
|             // Interprétation du JSON
 |  | ||||||
|             const data = JSON.parse(body); |  | ||||||
| 
 |  | ||||||
|             // Aucun écran de fin
 |  | ||||||
|             if ( |  | ||||||
|                 typeof data !== 'object' || data === null || |  | ||||||
|                 data.elements === undefined |  | ||||||
|             ) |  | ||||||
|             { |             { | ||||||
|                 resolve([]); |                 reject(err); | ||||||
|                 return; |  | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             // Filtrage des données pour extraire le titre et l’ID des vidéos
 |  | ||||||
|             resolve(data.elements.map(elt => |  | ||||||
|             { |  | ||||||
|                 const data = elt.endscreenElementRenderer; |  | ||||||
|                 const videoIdResults = VIDEO_ID_REGEX.exec( |  | ||||||
|                     data.endpoint.urlEndpoint.url |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 return { |  | ||||||
|                     videoId: videoIdResults ? videoIdResults[1] : null, |  | ||||||
|                     title: data.title.simpleText, |  | ||||||
|                 }; |  | ||||||
|             })); |  | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | exports.getPlayerConfig = getPlayerConfig; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Get metadata about a YouTube video. | ||||||
|  |  * | ||||||
|  |  * @param playerConfig The `ytplayer.config` object corresponding to the source | ||||||
|  |  * YouTube video, as obtained from `getPlayerConfig`. | ||||||
|  |  * @return Object containing the video metadata. | ||||||
|  |  */ | ||||||
|  | const getVideoMeta = playerConfig => ({ | ||||||
|  |     videoId: playerConfig.args.player_response.videoDetails.videoId, | ||||||
|  |     title: playerConfig.args.player_response.videoDetails.title, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | exports.getVideoMeta = getVideoMeta; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Find videos linked from the endscreen of a YouTube video. | ||||||
|  |  * | ||||||
|  |  * @param playerConfig The `ytplayer.config` object corresponding to the source | ||||||
|  |  * YouTube video, as obtained from `getPlayerConfig`. | ||||||
|  |  * @return List of identifiers of linked videos. | ||||||
|  |  */ | ||||||
|  | const getEndScreenVideos = playerConfig => | ||||||
|  | { | ||||||
|  |     const response = playerConfig.args.player_response; | ||||||
|  | 
 | ||||||
|  |     if (!('endscreen' in response)) | ||||||
|  |     { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return response.endscreen.endscreenRenderer.elements | ||||||
|  |         .map(elt => elt.endscreenElementRenderer) | ||||||
|  |         .filter(rdr => 'watchEndpoint' in rdr.endpoint) | ||||||
|  |         .map(rdr => rdr.endpoint.watchEndpoint.videoId); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| exports.getEndScreenVideos = getEndScreenVideos; | exports.getEndScreenVideos = getEndScreenVideos; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Récupère une liste des vidéos liées en tant que carte d’une autre vidéo. |  * Find videos linked from as cards from a YouTube video. | ||||||
|  * |  * | ||||||
|  * @param videoId Identifiant de la vidéo source. |  * @param playerConfig The `ytplayer.config` object corresponding to the source | ||||||
|  * @return Liste des vidéos liées. Chaque vidéo est un objet contenant |  * YouTube video, as obtained from `getPlayerConfig`. | ||||||
|  * son identifiant (videoId) et son titre (title). |  * @return List of identifiers of linked videos. | ||||||
|  */ |  */ | ||||||
| const getCardVideos = videoId => | const getCardVideos = playerConfig => | ||||||
| { | { | ||||||
|     const url = util.format(CARD_BASE, videoId); |     const response = playerConfig.args.player_response; | ||||||
| 
 | 
 | ||||||
|     return new Promise((resolve, reject) => |     if (!('cards' in response)) | ||||||
|     { |     { | ||||||
|         request(url, (err, res, body) => |         return []; | ||||||
|         { |     } | ||||||
|             if (err) |  | ||||||
|             { |  | ||||||
|                 reject(err); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             // Interprétation du XML externe et recherche des annotations
 |     return response.cards.cardCollectionRenderer.cards | ||||||
|             // de type carte
 |         .map(card => card.cardRenderer.content) | ||||||
|             const nav = cheerio.load(body); |         .filter(content => 'videoInfoCardContentRenderer' in content) | ||||||
|             const cards = nav('annotation[type="card"][style="video"]'); |         .map(content => content.videoInfoCardContentRenderer) | ||||||
|             const list = []; |         .map(rdr => rdr.action.watchEndpoint.videoId); | ||||||
| 
 |  | ||||||
|             cards.each((i, el) => |  | ||||||
|             { |  | ||||||
|                 // Interprétation du JSON de chaque carte, extraction
 |  | ||||||
|                 // du titre et de l’identifiant de la vidéo
 |  | ||||||
|                 const data = JSON.parse(nav(el).children('data').text()); |  | ||||||
|                 const videoIdResults = VIDEO_ID_REGEX.exec(data.url); |  | ||||||
| 
 |  | ||||||
|                 list.push({ |  | ||||||
|                     title: data.title, |  | ||||||
|                     videoId: videoIdResults ? videoIdResults[1] : null |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             resolve(list); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| exports.getCardVideos = getCardVideos; | exports.getCardVideos = getCardVideos; | ||||||
|  |  | ||||||
|  | @ -7,19 +7,19 @@ const GRAPH_NODE_URL = '    "%s" [label="%s", URL="%s", fontcolor=blue]'; | ||||||
| const GRAPH_LINK = '    "%s" -> "%s"'; | const GRAPH_LINK = '    "%s" -> "%s"'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Convertit un graphe en format DOT. |  * Convert a graph to the DOT format. | ||||||
|  * |  * | ||||||
|  * @param graph Graphe à convertir. |  * @param graph Graph to convert. | ||||||
|  * @param [title=identity] Fonction donnant le titre de chaque nœud du graphe. |  * @param [title=identity] Function giving the name of each node. | ||||||
|  * @param [url=none] Fonction donnant l’URL de chaque nœud du graphe, ou la |  * @param [url=none] Function given the URL of each node, or an empty string | ||||||
|  * chaîne vide si un nœud n’a pas d’URL. |  * if a node has no URL. | ||||||
|  * @return Représentation DOT du graphe. |  * @return DOT representation of the graph. | ||||||
|  */ |  */ | ||||||
| const graphToDOT = (graph, title = id => id, url = () => '') => | const graphToDOT = (graph, title = id => id, url = () => '') => | ||||||
| { | { | ||||||
|     const ser = graph.serialize(); |     const ser = graph.serialize(); | ||||||
| 
 | 
 | ||||||
|     // Conversion des nœuds de vidéo
 |     // Convert nodes
 | ||||||
|     const nodes = ser.nodes.map(({id}) => |     const nodes = ser.nodes.map(({id}) => | ||||||
|     { |     { | ||||||
|         const nodeTitle = title(id); |         const nodeTitle = title(id); | ||||||
|  | @ -36,7 +36,7 @@ const graphToDOT = (graph, title = id => id, url = () => '') => | ||||||
|     } |     } | ||||||
|     ).join('\n'); |     ).join('\n'); | ||||||
| 
 | 
 | ||||||
|     // Conversion des liens de vidéo
 |     // Convert edges
 | ||||||
|     const links = ser.links.map(({source, target}) => |     const links = ser.links.map(({source, target}) => | ||||||
|         util.format(GRAPH_LINK, source, target) |         util.format(GRAPH_LINK, source, target) | ||||||
|     ).join('\n'); |     ).join('\n'); | ||||||
|  |  | ||||||
|  | @ -5,74 +5,59 @@ const fs = require('fs'); | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const util = require('util'); | const util = require('util'); | ||||||
| 
 | 
 | ||||||
| const {getEndScreenVideos, getCardVideos} = require('./api'); | const api = require('./api'); | ||||||
| const {graphToDOT} = require('./graph'); | const {graphToDOT} = require('./graph'); | ||||||
| 
 | 
 | ||||||
| const YOUTUBE_WATCH = 'https://youtu.be/%s'; | const YOUTUBE_WATCH = 'https://youtu.be/%s'; | ||||||
| 
 | 
 | ||||||
| // Récupère le chemin vers le fichier de sortie depuis la ligne de commande
 | // Fetch the output path from command line
 | ||||||
| if (process.argv.length !== 3) | if (process.argv.length !== 3) | ||||||
| { | { | ||||||
|     console.error(`Utilisation : node src [sortie]`); |     console.error(`Usage: node explore [output]`); | ||||||
|     process.exit(1); |     process.exit(1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const dest = process.argv[2]; | const dest = process.argv[2]; | ||||||
| 
 | 
 | ||||||
| // Graphe des vidéos. Chaque nœud est une vidéo, et est liée à toutes
 | // Graph of visited videos. Each node is a video which is linked to all the
 | ||||||
| // les vidéos vers lesquelles elle a un lien (par carte ou écran de fin)
 | // videos to which there is a link, either through a card or an endscreen item
 | ||||||
| const videosGraph = Graph(); | const videosGraph = Graph(); | ||||||
| 
 | 
 | ||||||
| // Stocke les métadonnées de chaque vidéo
 | // Store metadata about each visited video
 | ||||||
| const videosMeta = Object.create(null); | const videosMeta = Object.create(null); | ||||||
| 
 | 
 | ||||||
| // Se souvient des vidéos déja visitées
 |  | ||||||
| const visitedVideos = Object.create(null); |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Explore récursivement les liens d’une vidéo pour remplir le graphe |  * Recursively explore a video and the video linked from it to fill | ||||||
|  * des vidéos. |  * the video graph. | ||||||
|  * |  * | ||||||
|  * @param videoId Identifiant de la vidéo source. |  * @param videoId Source video identifier. | ||||||
|  */ |  */ | ||||||
| const exploreVideo = videoId => | const exploreVideo = async videoId => | ||||||
| { | { | ||||||
|     // S’assure de ne pas visiter deux fois la même vidéo
 |     // Make sure we don’t explore the same video twice
 | ||||||
|     if (visitedVideos[videoId] === true) |     if (videoId in videosMeta) | ||||||
|     { |     { | ||||||
|         return Promise.resolve(); |         return Promise.resolve(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     visitedVideos[videoId] = true; |     const playerConfig = await api.getPlayerConfig(videoId); | ||||||
|  |     videosMeta[videoId] = api.getVideoMeta(playerConfig); | ||||||
| 
 | 
 | ||||||
|     return Promise.all([ |     const linkedVideos = [ | ||||||
|         getEndScreenVideos(videoId), |         ...api.getEndScreenVideos(playerConfig), | ||||||
|         getCardVideos(videoId) |         ...api.getCardVideos(playerConfig), | ||||||
|     ]).then(results => |     ]; | ||||||
|     { |  | ||||||
|         const videos = [].concat(...results); |  | ||||||
|         const ids = videos.map(video => video.videoId); |  | ||||||
| 
 | 
 | ||||||
|         // Ajout des vidéos liées dans les métadonnées
 |     // Add links between this video and the linked ones
 | ||||||
|         videos.forEach(video => videosMeta[video.videoId] = video); |     linkedVideos.forEach(id => videosGraph.addEdge(videoId, id)); | ||||||
| 
 | 
 | ||||||
|         // Ajout des liens entre la vidéo et ses vidéos liées
 |     // Recurse on linked videos
 | ||||||
|         ids.forEach(id => videosGraph.addEdge(videoId, id)); |     return Promise.all(linkedVideos.map(id => exploreVideo(id))); | ||||||
| 
 |  | ||||||
|         // Récursion sur les vidéos non-explorées
 |  | ||||||
|         return Promise.all(ids.map(id => exploreVideo(id))); |  | ||||||
|     }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Métadonnées de la vidéo source
 | // Metadata of the source video
 | ||||||
| const rootVideoId = 'EZGra6O8ClQ'; | const rootVideoId = 'EZGra6O8ClQ'; | ||||||
| videosMeta[rootVideoId] = { | console.log('Starting to explore!'); | ||||||
|     title: '1 avril 2017 : présentation des règles', |  | ||||||
|     videoId: rootVideoId, |  | ||||||
|     links: [] |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| console.log('Démarrage de l’exploration !'); |  | ||||||
| 
 | 
 | ||||||
| exploreVideo(rootVideoId).then(() => | exploreVideo(rootVideoId).then(() => | ||||||
| { | { | ||||||
|  | @ -85,5 +70,5 @@ exploreVideo(rootVideoId).then(() => | ||||||
|         ) |         ) | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     console.log('Terminé. Résultat dans ' + dest); |     console.log('Finished. Result in ' + dest); | ||||||
| }).catch(console.error); | }).catch(console.error); | ||||||
|  |  | ||||||
|  | @ -10,11 +10,11 @@ | ||||||
|       "integrity": "sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ==" |       "integrity": "sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ==" | ||||||
|     }, |     }, | ||||||
|     "ajv": { |     "ajv": { | ||||||
|       "version": "6.10.1", |       "version": "6.12.3", | ||||||
|       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", |       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", | ||||||
|       "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", |       "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "fast-deep-equal": "^2.0.1", |         "fast-deep-equal": "^3.1.1", | ||||||
|         "fast-json-stable-stringify": "^2.0.0", |         "fast-json-stable-stringify": "^2.0.0", | ||||||
|         "json-schema-traverse": "^0.4.1", |         "json-schema-traverse": "^0.4.1", | ||||||
|         "uri-js": "^4.2.2" |         "uri-js": "^4.2.2" | ||||||
|  | @ -44,9 +44,9 @@ | ||||||
|       "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" |       "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" | ||||||
|     }, |     }, | ||||||
|     "aws4": { |     "aws4": { | ||||||
|       "version": "1.8.0", |       "version": "1.10.0", | ||||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", |       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", | ||||||
|       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" |       "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" | ||||||
|     }, |     }, | ||||||
|     "bcrypt-pbkdf": { |     "bcrypt-pbkdf": { | ||||||
|       "version": "1.0.2", |       "version": "1.0.2", | ||||||
|  | @ -177,14 +177,14 @@ | ||||||
|       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" |       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" | ||||||
|     }, |     }, | ||||||
|     "fast-deep-equal": { |     "fast-deep-equal": { | ||||||
|       "version": "2.0.1", |       "version": "3.1.3", | ||||||
|       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", |       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||||
|       "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" |       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" | ||||||
|     }, |     }, | ||||||
|     "fast-json-stable-stringify": { |     "fast-json-stable-stringify": { | ||||||
|       "version": "2.0.0", |       "version": "2.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", |       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", | ||||||
|       "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" |       "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" | ||||||
|     }, |     }, | ||||||
|     "forever-agent": { |     "forever-agent": { | ||||||
|       "version": "0.6.1", |       "version": "0.6.1", | ||||||
|  | @ -210,9 +210,9 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "graph-data-structure": { |     "graph-data-structure": { | ||||||
|       "version": "1.8.0", |       "version": "1.12.1", | ||||||
|       "resolved": "https://registry.npmjs.org/graph-data-structure/-/graph-data-structure-1.8.0.tgz", |       "resolved": "https://registry.npmjs.org/graph-data-structure/-/graph-data-structure-1.12.1.tgz", | ||||||
|       "integrity": "sha512-G2Jl7JLGsq0FGBvFMfKkQEwjnk+1//ssIirR5GTQxXBbKqYZkmjDFEVcJ8H87dVpw4D8lZDJj9v9ggnHWW8M+g==" |       "integrity": "sha512-0DHxFEUk2EHO19PQrcOckz91WZPk7Itl2mmNpGdpSIZUtBUHRVJPtuuZCJAXB69YRL4fKDCRv2cX3ly8aZZ0QQ==" | ||||||
|     }, |     }, | ||||||
|     "har-schema": { |     "har-schema": { | ||||||
|       "version": "2.0.0", |       "version": "2.0.0", | ||||||
|  | @ -298,21 +298,21 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "lodash": { |     "lodash": { | ||||||
|       "version": "4.17.11", |       "version": "4.17.19", | ||||||
|       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", |       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", | ||||||
|       "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" |       "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" | ||||||
|     }, |     }, | ||||||
|     "mime-db": { |     "mime-db": { | ||||||
|       "version": "1.40.0", |       "version": "1.44.0", | ||||||
|       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", |       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", | ||||||
|       "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" |       "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" | ||||||
|     }, |     }, | ||||||
|     "mime-types": { |     "mime-types": { | ||||||
|       "version": "2.1.24", |       "version": "2.1.27", | ||||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", |       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", | ||||||
|       "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", |       "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "mime-db": "1.40.0" |         "mime-db": "1.44.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "nth-check": { |     "nth-check": { | ||||||
|  | @ -342,9 +342,9 @@ | ||||||
|       "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" |       "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" | ||||||
|     }, |     }, | ||||||
|     "psl": { |     "psl": { | ||||||
|       "version": "1.2.0", |       "version": "1.8.0", | ||||||
|       "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", |       "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", | ||||||
|       "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" |       "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" | ||||||
|     }, |     }, | ||||||
|     "punycode": { |     "punycode": { | ||||||
|       "version": "2.1.1", |       "version": "2.1.1", | ||||||
|  | @ -367,9 +367,9 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "request": { |     "request": { | ||||||
|       "version": "2.88.0", |       "version": "2.88.2", | ||||||
|       "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", |       "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", | ||||||
|       "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", |       "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "aws-sign2": "~0.7.0", |         "aws-sign2": "~0.7.0", | ||||||
|         "aws4": "^1.8.0", |         "aws4": "^1.8.0", | ||||||
|  | @ -378,7 +378,7 @@ | ||||||
|         "extend": "~3.0.2", |         "extend": "~3.0.2", | ||||||
|         "forever-agent": "~0.6.1", |         "forever-agent": "~0.6.1", | ||||||
|         "form-data": "~2.3.2", |         "form-data": "~2.3.2", | ||||||
|         "har-validator": "~5.1.0", |         "har-validator": "~5.1.3", | ||||||
|         "http-signature": "~1.2.0", |         "http-signature": "~1.2.0", | ||||||
|         "is-typedarray": "~1.0.0", |         "is-typedarray": "~1.0.0", | ||||||
|         "isstream": "~0.1.2", |         "isstream": "~0.1.2", | ||||||
|  | @ -388,7 +388,7 @@ | ||||||
|         "performance-now": "^2.1.0", |         "performance-now": "^2.1.0", | ||||||
|         "qs": "~6.5.2", |         "qs": "~6.5.2", | ||||||
|         "safe-buffer": "^5.1.2", |         "safe-buffer": "^5.1.2", | ||||||
|         "tough-cookie": "~2.4.3", |         "tough-cookie": "~2.5.0", | ||||||
|         "tunnel-agent": "^0.6.0", |         "tunnel-agent": "^0.6.0", | ||||||
|         "uuid": "^3.3.2" |         "uuid": "^3.3.2" | ||||||
|       } |       } | ||||||
|  | @ -428,19 +428,12 @@ | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "tough-cookie": { |     "tough-cookie": { | ||||||
|       "version": "2.4.3", |       "version": "2.5.0", | ||||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", |       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", | ||||||
|       "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", |       "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "psl": "^1.1.24", |         "psl": "^1.1.28", | ||||||
|         "punycode": "^1.4.1" |         "punycode": "^2.1.1" | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "punycode": { |  | ||||||
|           "version": "1.4.1", |  | ||||||
|           "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", |  | ||||||
|           "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "tunnel-agent": { |     "tunnel-agent": { | ||||||
|  | @ -470,9 +463,9 @@ | ||||||
|       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" |       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" | ||||||
|     }, |     }, | ||||||
|     "uuid": { |     "uuid": { | ||||||
|       "version": "3.3.2", |       "version": "3.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", |       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||||
|       "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" |       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" | ||||||
|     }, |     }, | ||||||
|     "verror": { |     "verror": { | ||||||
|       "version": "1.10.0", |       "version": "1.10.0", | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "cheerio": "1.0.0-rc.3", |     "cheerio": "1.0.0-rc.3", | ||||||
|     "graph-data-structure": "^1.8.0", |     "graph-data-structure": "^1.12.1", | ||||||
|     "request": "^2.88.0" |     "request": "^2.88.2" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue