From 41f3f8c902f0ceeabb4040efabbf5f9037510b6a Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Fri, 8 Apr 2016 08:33:52 -0400 Subject: [PATCH 01/13] Only convert when the youtube URL is the only thing on the line. --- library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.js b/library.js index 33f8014..ccee1b9 100644 --- a/library.js +++ b/library.js @@ -4,7 +4,7 @@ var controllers = require('./lib/controllers'); var YoutubeLite = {}, embed = '
'; - var regularUrl = //g; + var regularUrl = /

<\/p>/g; YoutubeLite.init = function(params, callback) { var router = params.router, From a4246e7f2e0d3cc480c940311c60f75729925464 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Fri, 8 Apr 2016 09:44:42 -0400 Subject: [PATCH 02/13] * Support URLs that are on consecutive lines. * Check link text to make sure it's exactly the URL or don't convert. --- library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.js b/library.js index ccee1b9..f481fe8 100644 --- a/library.js +++ b/library.js @@ -4,7 +4,7 @@ var controllers = require('./lib/controllers'); var YoutubeLite = {}, embed = '

'; - var regularUrl = /

<\/p>/g; + var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/mg; YoutubeLite.init = function(params, callback) { var router = params.router, From f868ba4327bfdfcfad302c27e4448906746d9541 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Fri, 8 Apr 2016 13:40:23 -0400 Subject: [PATCH 03/13] * detects linked text * regex tests using mocha + chai --- library.js | 3 ++- package.json | 7 +++++- tests/regex.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/regex.js diff --git a/library.js b/library.js index f481fe8..c12131b 100644 --- a/library.js +++ b/library.js @@ -4,7 +4,8 @@ var controllers = require('./lib/controllers'); var YoutubeLite = {}, embed = '

'; - var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/mg; +var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/mg; +YoutubeLite.regularUrl = regularUrl; YoutubeLite.init = function(params, callback) { var router = params.router, diff --git a/package.json b/package.json index 2e2e027..b57a368 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,17 @@ "description": "NodeBB Youtube Lite Plugin", "main": "library.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "mocha ./tests" }, "repository": { "type": "git", "url": "git+https://github.com/a5mith/nodebb-plugin-youtube-lite.git" }, + "devDependencies": { + "chai": "^3.5.0", + "mocha": "^2.4.5", + "winston": "^2.2.0" + }, "keywords": [ "nodebb", "plugin", diff --git a/tests/regex.js b/tests/regex.js new file mode 100644 index 0000000..571395e --- /dev/null +++ b/tests/regex.js @@ -0,0 +1,66 @@ +'use strict'; +var winston = require('winston'); + +process.on('uncaughtException', function (err) { + winston.error('Encountered error while running test suite: ' + err.message); +}); + +var expect = require("chai").expect; + +var youtubeLite = require("../library"); + +var posts = [ + { + description: 'Simple post with only the youtube link', + content: '

https://youtu.be/fXhUgV9qzI0

', + expected: '
' + }, + { + description: 'Markdown link with a youtube URL should be left alone', + content: '

linked

', + expected: '

linked

' + }, + { + description: 'Video URLs on consecutive lines', + content: '

https://youtu.be/fXhUgV9qzI0
\nhttps://youtu.be/fXhUgV9qzI0

', + expected: '
\n
' + }, + { + description: 'Link with text in front should be left alone', + content: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', + expected: '

Look at thishttps://youtu.be/fXhUgV9qzI0

' + }, + { + description: 'Link with text behind should be left alone', + content: '

https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: '

https://youtu.be/fXhUgV9qzI0 uh huh

' + }, + { + description: 'Link with text in front and behind should be left alone', + content: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

' + } +]; + + +describe( 'regex tests', function(){ + var i; + for( i = 0; i < posts.length; ++i ){ + var post = posts[i]; + describe( post.description, function(){ + + var data = new Object(); + var expectedValue = post.expected; + data.postData = new Object(); + data.postData.content = post.content; + it('converts the post correctly', function(){ + youtubeLite.parse( data, function( callback, theData ){ + expect( theData.postData.content ).to.equal( expectedValue ); + }); + + }); + }); + } +}); + + From 361adbd0b0b2215fa6c91a474f8eeb2aec5462c6 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Thu, 28 Apr 2016 15:37:28 -0400 Subject: [PATCH 04/13] Support video start time in youtube URLs. --- library.js | 11 +++++++---- static/lib/lazyYT.js | 3 ++- tests/regex.js | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/library.js b/library.js index c12131b..fe1c860 100644 --- a/library.js +++ b/library.js @@ -2,9 +2,11 @@ var controllers = require('./lib/controllers'); var YoutubeLite = {}, - embed = '
'; + embed = '
', + timeStamp = /t(\=\d+)/g, + startParam = /&start=/g; -var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/mg; + var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/gm; YoutubeLite.regularUrl = regularUrl; YoutubeLite.init = function(params, callback) { @@ -35,11 +37,12 @@ YoutubeLite.parse = function(data, callback) { if (!data || !data.postData || !data.postData.content) { return callback(null, data); } - if (data.postData.content.match(regularUrl)) { + if (data.postData.content.match(regularUrl)) { data.postData.content = data.postData.content.replace(regularUrl, embed); + data.postData.content = data.postData.content.replace(timeStamp, 'start$1'); + data.postData.content = data.postData.content.replace(startParam, '?start='); } callback(null, data); - }; module.exports = YoutubeLite; diff --git a/static/lib/lazyYT.js b/static/lib/lazyYT.js index f9b6cce..c5f2204 100644 --- a/static/lib/lazyYT.js +++ b/static/lib/lazyYT.js @@ -13,6 +13,7 @@ innerHtml = [], $thumb, thumb_img, + start = $el.data('start'), loading_text = $el.text() ? $el.text() : settings.loading_text, youtube_parameters = $el.data('parameters') || ''; @@ -109,7 +110,7 @@ .on('click', function (e) { e.preventDefault(); if (!$el.hasClass('lazyYT-video-loaded') && $thumb.hasClass('lazyYT-image-loaded')) { - $el.html('') + $el.html('') .addClass(settings.video_loaded_class); // execute callback diff --git a/tests/regex.js b/tests/regex.js index 571395e..dd77e30 100644 --- a/tests/regex.js +++ b/tests/regex.js @@ -13,7 +13,7 @@ var posts = [ { description: 'Simple post with only the youtube link', content: '

https://youtu.be/fXhUgV9qzI0

', - expected: '
' + expected: '
' }, { description: 'Markdown link with a youtube URL should be left alone', @@ -23,7 +23,7 @@ var posts = [ { description: 'Video URLs on consecutive lines', content: '

https://youtu.be/fXhUgV9qzI0
\nhttps://youtu.be/fXhUgV9qzI0

', - expected: '
\n
' + expected: '
\n
' }, { description: 'Link with text in front should be left alone', @@ -39,6 +39,16 @@ var posts = [ description: 'Link with text in front and behind should be left alone', content: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', expected: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

' + }, + { + description: 'Simple post with only the youtu.be link with a start time', + content: '

https://youtu.be/fXhUgV9qzI0?t=55s

', + expected: '
' + }, + { + description: 'Simple post with only the youtube.com link with a start time', + content: '

https://youtube.com/watch?v=fXhUgV9qzI0&t=55s

', + expected: '
' } ]; From 83bbafd14f307cdf6e1fcf1072a9764da44d1ac1 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Fri, 27 May 2016 16:02:29 -0400 Subject: [PATCH 05/13] - Moved most of the work to the server, to include API calls (keeps API key from being exposed to users). - LRU cache to reduce quota usage. - Supports `t=`, '`start=`', `end=` query string parameters - With valid API key displays title, channel and duration information --- static/lib/main.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 static/lib/main.js diff --git a/static/lib/main.js b/static/lib/main.js deleted file mode 100644 index f9a97bf..0000000 --- a/static/lib/main.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; - - $(window).on('action:widgets.loaded', function() { - $('.js-lazyYT').lazyYT(); - }); - - - $(window).on('action:posts.loaded', function(){ - $('.js-lazyYT').delay(500).lazyYT(); - }); From 1652ba7b13b51a61c2e72e009a8f569df04aeea5 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Fri, 27 May 2016 16:19:06 -0400 Subject: [PATCH 06/13] Really do all that stuff mentioned in the previous commit. --- .gitignore | 2 + README.md | 7 + library.js | 371 +++++++++++++++++++++++++++++++++++++++---- package.json | 8 +- plugin.json | 10 +- static/lib/lazyYT.js | 224 +------------------------- static/style.less | 1 + tests/regex.js | 217 ++++++++++++++++++------- 8 files changed, 528 insertions(+), 312 deletions(-) diff --git a/.gitignore b/.gitignore index 6dae747..946f815 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ Icon # Files that might appear on external disk .Spotlight-V100 .Trashes +node_modules +coverage diff --git a/README.md b/README.md index 664168b..73bff6c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ Youtube Lite allows you to lazyload youtube videos into your NodeBB Forum with n ##Changes in V3 + 0.6.0 + - Works with NodeBB 1.0 + - Moved most of the work to the server, to include API calls (keeps API key + from being exposed to users). + - LRU cache to reduce quota usage. + - Supports `t=`, '`start=`', `end=` query string parameters + - With valid API key displays title, channel and duration information 0.5.0 - Plugin now supports [Youtube API v3](https://developers.google.com/youtube/v3/?hl=en) diff --git a/library.js b/library.js index fe1c860..f7748d0 100644 --- a/library.js +++ b/library.js @@ -1,48 +1,359 @@ "use strict"; +// https://developers.google.com/youtube/v3/docs/videos +var LRU = require('lru-cache'), + cache = LRU( 500 ); +var https = require('https'); var controllers = require('./lib/controllers'); -var YoutubeLite = {}, - embed = '
', - timeStamp = /t(\=\d+)/g, - startParam = /&start=/g; - var regularUrl = /(?:

|^)]*?>\1\4<\/a>(?:|<\/p>)/gm; -YoutubeLite.regularUrl = regularUrl; +var apiKey; -YoutubeLite.init = function(params, callback) { - var router = params.router, - hostMiddleware = params.middleware, - hostControllers = params.controllers; - - // We create two routes for every view. One API call, and the actual route itself. - // Just add the buildHeader middleware to your route and NodeBB will take care of everything for you. +var YoutubeLite = {}; +YoutubeLite.cache = cache; + +YoutubeLite.youtubeUrl = /(

|^)((]*?>)(\4\7)<\/a>)(|<\/p>)/m; - router.get('/admin/plugins/youtube-lite', hostMiddleware.admin.buildHeader, controllers.renderAdminPage); - router.get('/api/admin/plugins/youtube-lite', controllers.renderAdminPage); +YoutubeLite.init = function(params, callback) { + var router = params.router, + hostMiddleware = params.middleware, + hostControllers = params.controllers, + db = module.parent.require('./database'); + // We create two routes for every view. One API call, and the actual route itself. + // Just add the buildHeader middleware to your route and NodeBB will take care of everything for you. - callback(); + router.get('/admin/plugins/youtube-lite', hostMiddleware.admin.buildHeader, controllers.renderAdminPage); + router.get('/api/admin/plugins/youtube-lite', controllers.renderAdminPage); + + db.getObjectField('settings:youtube-lite', 'id', function(err, result){ + apiKey = result; + callback(); + }); }; YoutubeLite.addAdminNavigation = function(header, callback) { - header.plugins.push({ - route: '/plugins/youtube-lite', - icon: 'fa-youtube', - name: 'Youtube Lite' - }); + header.plugins.push({ + route: '/plugins/youtube-lite', + icon: 'fa-youtube', + name: 'Youtube Lite' + }); - callback(null, header); + callback(null, header); }; -YoutubeLite.parse = function(data, callback) { - if (!data || !data.postData || !data.postData.content) { - return callback(null, data); +function fetchSnippet( videoId, callback ){ + var cachedSnippet = cache.get(videoId); + if( cachedSnippet ){ + return callback(null, cachedSnippet); + } + else{ + if( apiKey ){ + var req = https.request({ + host: 'www.googleapis.com', + path: '/youtube/v3/videos?id=' + videoId + '&key=' + apiKey + '&part=snippet,contentDetails&fields=items(snippet(title,channelTitle,thumbnails),contentDetails(duration))', + port: 443, + agent: false, + json: true, + method: 'GET' + },(res) => { + res.setEncoding('utf8'); + var videos = ''; + res.on('data', (data) => { + videos += data; + }); + res.on('end', function(){ + videos = JSON.parse(videos); + var snippet = videos.items[0].snippet; + snippet.title = replaceAll( snippet.title, '<', '<'); + snippet.channelTitle = replaceAll( snippet.title, '<', '<'); + snippet.duration = timeToString( parseDuration( videos.items[0].contentDetails.duration ) ); + cache.set( videoId, snippet ); + callback( null, snippet ); + }); + }); + req.end(); + + req.on('error', (err) => { + callback( error ); + }); } - if (data.postData.content.match(regularUrl)) { - data.postData.content = data.postData.content.replace(regularUrl, embed); - data.postData.content = data.postData.content.replace(timeStamp, 'start$1'); - data.postData.content = data.postData.content.replace(startParam, '?start='); + else{ + var snippet = { + title: 'Youtube Video', + thumbnails: { + medium: {url: 'https://i.ytimg.com/vi/' + videoId + '/mqdefault.jpg'}, + default: {url: 'https://i.ytimg.com/vi/' + videoId + '/default.jpg'}, + high: {url: 'https://i.ytimg.com/vi/' + videoId + '/hqdefault.jpg'}, + standard: {url: 'https://i.ytimg.com/vi/' + videoId + '/sddefault.jpg'} + } + }; + callback(null, snippet); } + } +} + +function replaceAll(text, search, replace) { + if (replace === undefined) { + return text.toString(); + } + return text.split(search).join(replace); +} + +function spliceSlice(str, index, count, add) { +return str.slice(0, index) + (add || "") + str.slice(index + count); +} + +/* +[ +0 '

https://youtu.be/ggCuyOeDl5M?t=30s&end=50s

', +1 '

', +2 'https://youtu.be/ggCuyOeDl5M?t=30s&end=50s', +3 '', +4 'https://youtu.be/', +5 undefined, +6 'https://', +7 'ggCuyOeDl5M?t=30s&end=50s', +8 'ggCuyOeDl5M', +9 't=30s&end=50s', +10 'https://youtu.be/ggCuyOeDl5M?t=30s&end=50s', + index: 11, + input: '

foo

\n

https://youtu.be/ggCuyOeDl5M?t=30s&end=50s

\n' ] + +0: whole match +1: opening paragraph tag (optional) +2: entire ... tag +3: tag +4: https//url inside tag +5: http://url inside tag +6: https +7: query string +8: video id +9: rest of query string (minus starting character) +10: text between ... +11: ending tag +*/ + +function getParams( params ){ + var result = {}; + var queryString = []; + for( var i = 0; i < params.length; ++i ){ + var param = params[i]; + if( param.indexOf('t=') == 0 ){ + var val = parseTime( param.substring( param.indexOf('=')+1) ); + queryString.push( 'start=' + val ); + result.start = timeToString( parseInt( val, 10 ) ); + } + else if( + param.indexOf('start=') == 0 || + param.indexOf('end=') == 0 + ){ + var ex = param.indexOf('='); + var val = parseTime( param.substring( ex+1 ) ); + queryString.push( param.substring(0, ex+1) + val ); + result[param.substring(0, ex)] = timeToString( parseInt( val, 10 ) ); + } + else if( + param.indexOf('iv_load_policy=') == 0 + ){ + queryString.push( param ); + } + } + result.queryString = queryString.join('&'); + return result; +} + +function getStart( params ){ + +} + +function filter(data, match, preview, callback){ + if(match){ + var videoId = match[8]; + fetchSnippet(videoId, + function(err, snippet){ + if( err ){ + callback(err); + } + + var params = getParams( (match[9] || '').split('&') ); + var queryString = params.queryString; + var content = match[1] + match[3] + ' ' + snippet.title; + + if( snippet.duration ){ + content += ' – '; + + if( params.start ){ + if( params.end ){ + content += '[' + params.start + '..' + params.end + ']'; + } + else{ + content += '[' + params.start + '..' + snippet.duration + ']'; + } + } + else if( params.end ){ + content += '[00:00..' + params.end + ']'; + } + + content += ' ' + snippet.duration + '
— ' + snippet.channelTitle +'
'; + } + else if( params.start || params.end ){ + // No API key, but we still have a start / end we can display + content += ' – '; + + if( params.start ){ + if( params.end ){ + content += '[' + params.start + '..' + params.end + ']'; + } + else{ + content += '[' + params.start + '..]'; + } + } + else if( params.end ){ + content += '[00:00..' + params.end + ']'; + } + content += ''; + } + content += ''; + var thumbnails = snippet.thumbnails; + if( preview ){ + var img = thumbnails.medium || thumbnails.default || thumbnails.high || thumbnails.standard; + content += '
'; + } + else{ + + var img = thumbnails.high || thumbnails.standard || thumbnails.default || thumbnails.medium; + content += '
' + + '\n
' + + '\n ' + + '\n
' + + '\n
'; + } + + content += match[11]; + data = data.substring(0, match.index) + content + data.substring( match.index + match[0].length ) ; + + // Check for more... + filter(data, data.match(YoutubeLite.youtubeUrl), preview, callback); + }); + } + else{ callback(null, data); - }; + } +} +function parseTime(PT) { + var output = []; + var seconds = 0; + var matches = PT.match(/(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?(\d*)?/i); + var parts = [ + { // hours + pos: 1, + multiplier: 3600 + }, + { // minutes + pos: 2, + multiplier: 60 + }, + { // seconds + pos: 3, + multiplier: 1 + }, + { // seconds (raw) + pos: 4, + multiplier: 1 + } + ]; + + for (var i = 0; i < parts.length; i++) { + if (typeof matches[parts[i].pos] != 'undefined') { + seconds += parseInt(matches[parts[i].pos]) * parts[i].multiplier; + } + } + + return seconds; +}; + +YoutubeLite.parseRaw = function(data, callback){ + if (!data ) { + return callback(null, data); + } + filter(data, data.match(YoutubeLite.youtubeUrl), true, callback); +} + +YoutubeLite.parsePost = function(data, callback) { + if (!data || !data.postData || !data.postData.content) { + return callback(null, data); + } + var content = data.postData.content; + filter(content, content.match(YoutubeLite.youtubeUrl), false, function(err, content){ + data.postData.content = content; + callback(null, data); + }); +}; + +function parseDuration(PT, settings) { + var durationInSec = 0; + var matches = PT.match(/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)W)?(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/i); + var parts = [ + { // years + pos: 1, + multiplier: 86400 * 365 + }, + { // months + pos: 2, + multiplier: 86400 * 30 + }, + { // weeks + pos: 3, + multiplier: 604800 + }, + { // days + pos: 4, + multiplier: 86400 + }, + { // hours + pos: 5, + multiplier: 3600 + }, + { // minutes + pos: 6, + multiplier: 60 + }, + { // seconds + pos: 7, + multiplier: 1 + } + ]; + + for (var i = 0; i < parts.length; i++) { + if (typeof matches[parts[i].pos] != 'undefined') { + durationInSec += parseInt(matches[parts[i].pos]) * parts[i].multiplier; + } + } + return durationInSec; + +}; + +function timeToString(seconds){ + var output = []; + // Hours extraction + if (seconds > 3599) { + output.push(parseInt(seconds / 3600)); + seconds %= 3600; + } + // Minutes extraction with leading zero + output.push(('0' + parseInt(seconds / 60)).slice(-2)); + // Seconds extraction with leading zero + output.push(('0' + seconds % 60).slice(-2)); + + return output.join(':'); +} module.exports = YoutubeLite; diff --git a/package.json b/package.json index b57a368..ee9aeba 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,23 @@ "name": "nodebb-plugin-youtube-lite", "version": "0.5.0", "nbbpm": { - "compatibility": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0" + "compatibility": "^1.0.3 " }, "description": "NodeBB Youtube Lite Plugin", "main": "library.js", "scripts": { - "test": "mocha ./tests" + "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 100000" }, "repository": { "type": "git", "url": "git+https://github.com/a5mith/nodebb-plugin-youtube-lite.git" }, + "dependencies":{ + "lru-cache": "^4.0.1" + }, "devDependencies": { "chai": "^3.5.0", + "istanbul": "^0.4.2", "mocha": "^2.4.5", "winston": "^2.2.0" }, diff --git a/plugin.json b/plugin.json index 94ecbdf..33ef55d 100644 --- a/plugin.json +++ b/plugin.json @@ -5,14 +5,14 @@ "url": "https://github.com/a5mith/nodebb-plugin-youtube-lite", "library": "./library.js", "hooks": [ - { "hook": "filter:parse.post", "method": "parse" }, - { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, - { "hook": "static:app.load", "method": "init" } + { "hook": "filter:parse.raw", "method": "parseRaw", "priority": 10 }, + { "hook": "filter:parse.post", "method": "parsePost", "priority": 10 }, + { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, + { "hook": "static:app.load", "method": "init" } ], "templates": "templates", "scripts": [ - "static/lib/lazyYT.js", - "static/lib/main.js" + "static/lib/lazyYT.js" ], "staticDirs": { "static": "./static" diff --git a/static/lib/lazyYT.js b/static/lib/lazyYT.js index c5f2204..173f8f0 100644 --- a/static/lib/lazyYT.js +++ b/static/lib/lazyYT.js @@ -1,221 +1,13 @@ ;(function ($) { 'use strict'; - - function setUp($el, settings) { - var width = $el.data('width'), - height = $el.data('height'), - title = $el.attr('title') || $el.data('title'), - display_title = $el.data('display-title'), - ratio = ($el.data('ratio')) ? $el.data('ratio') : settings.default_ratio, - display_duration = $el.data('display-duration'), + $.fn.lazyYT = function (e) { + + var $el = $(e.parentElement.parentElement), id = $el.data('youtube-id'), - padding_bottom, - innerHtml = [], - $thumb, - thumb_img, - start = $el.data('start'), - loading_text = $el.text() ? $el.text() : settings.loading_text, youtube_parameters = $el.data('parameters') || ''; - - ratio = ratio.split(":"); - - youtube_parameters += '&' + settings.youtube_parameters; - - if (typeof display_title != "boolean") { - display_title = settings.display_title; - } - - if (typeof display_duration != "boolean") { - display_duration = settings.display_duration; - } - - // width and height might override default_ratio value - if (typeof width === 'number' && typeof height === 'number') { - $el.width(width); - padding_bottom = height + 'px'; - } else if (typeof width === 'number') { - $el.width(width); - padding_bottom = (width * ratio[1] / ratio[0]) + 'px'; - } else { - width = $el.width(); - - // no width means that container is fluid and will be the size of its parent - if (width == 0) { - width = $el.parent().width(); - } - - padding_bottom = (ratio[1] / ratio[0] * 100) + '%'; - } - - // - // This HTML will be placed inside 'lazyYT' container - - innerHtml.push('
'); - - // Play button from YouTube (exactly as it is in YouTube) - innerHtml.push(''); // end of .ytp-large-play-button - - // video time from YouTube (exactly as it is in YouTube) - if (display_duration) { - innerHtml.push(''); - } - innerHtml.push('
'); // end of .ytp-thumbnail - - // Video title (info bar) - if (display_title) { - innerHtml.push('
'); - innerHtml.push('
'); - innerHtml.push('
'); - innerHtml.push(''); // /.ytp-title-text - innerHtml.push('
'); // /.ytp-title - innerHtml.push('
'); // /.ytp-chrome-top - } - - $el.css({ - 'padding-bottom': padding_bottom - }) - .html(innerHtml.join('')); - - if (width > 320) { - thumb_img = 'hqdefault.jpg'; - } else if (width > 120) { - thumb_img = 'mqdefault.jpg'; - } else if (width == 0) { // sometimes it fails on fluid layout - thumb_img = 'hqdefault.jpg'; - } else { - thumb_img = 'default.jpg'; - } - - $thumb = $el.find('.ytp-thumbnail').css({ - 'background-image': ['url(http://img.youtube.com/vi/', id, '/', thumb_img, ')'].join('') - }) - .addClass('lazyYT-image-loaded') - .on('click', function (e) { - e.preventDefault(); - if (!$el.hasClass('lazyYT-video-loaded') && $thumb.hasClass('lazyYT-image-loaded')) { - $el.html('') - .addClass(settings.video_loaded_class); - - // execute callback - if (typeof settings.callback == 'function') { // make sure the callback is a function - settings.callback.call($el); // brings the scope to the callback - } - } - }); - - if ((!title && display_title) || display_duration) { - var youtube_data_url = ['https://www.googleapis.com/youtube/v3/videos?id=', id, '&key=', settings.yt_api_key, '&part=snippet']; - if (display_duration) youtube_data_url.push(',contentDetails'); // this extra info now costs some quota points, so we retrieve it only when necessary. More on quota: https://developers.google.com/youtube/v3/getting-started#quota - - $.getJSON(youtube_data_url.join(''), function (data) { - var item = data.items[0]; - // console.log(item.snippet.title); - - $el.find('#lazyYT-title-' + id).text(item.snippet.title); - - if (display_duration) { - $el.find('.video-time') - .text(parseDuration(item.contentDetails.duration, settings)) - .show(); - } - - }); - } - - }; - - function parseDuration(PT, settings) { - var output = []; - var durationInSec = 0; - var matches = PT.match(/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)W)?(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/i); - var parts = [ - { // years - pos: 1, - multiplier: 86400 * 365 - }, - { // months - pos: 2, - multiplier: 86400 * 30 - }, - { // weeks - pos: 3, - multiplier: 604800 - }, - { // days - pos: 4, - multiplier: 86400 - }, - { // hours - pos: 5, - multiplier: 3600 - }, - { // minutes - pos: 6, - multiplier: 60 - }, - { // seconds - pos: 7, - multiplier: 1 - } - ]; - - for (var i = 0; i < parts.length; i++) { - if (typeof matches[parts[i].pos] != 'undefined') { - durationInSec += parseInt(matches[parts[i].pos]) * parts[i].multiplier; - } - } - - // Hours extraction - if (durationInSec > 3599) { - output.push(parseInt(durationInSec / 3600)); - durationInSec %= 3600; - } - // Minutes extraction with leading zero - output.push(('0' + parseInt(durationInSec / 60)).slice(-2)); - // Seconds extraction with leading zero - output.push(('0' + durationInSec % 60).slice(-2)); - - return output.join(':'); - }; - - $.fn.lazyYT = function (yt_api_key, newSettings) { - var defaultSettings = { - yt_api_key: yt_api_key, - - youtube_parameters: 'rel=0', - loading_text: 'Loading...', - display_title: true, - default_ratio: '16:9', - display_duration: false, - callback: null, - - // Advanced settings - video_loaded_class: 'lazyYT-video-loaded', - container_class: 'lazyYT-container' - }; - var settings = $.extend(defaultSettings, newSettings); - - return this.each(function () { - var $el = $(this).addClass(settings.container_class); - setUp($el, settings); - }); - }; - + $el.html( + '') + .addClass('lazyYT-video-loaded'); + }; }(jQuery)); + diff --git a/static/style.less b/static/style.less index 1b04d59..fc34cf0 100644 --- a/static/style.less +++ b/static/style.less @@ -17,6 +17,7 @@ iframe.lazytube { padding: 0 0 56.25% 0; overflow: hidden; background-color: @body-bg; + margin-bottom: 10px; } .lazyYT-container iframe { diff --git a/tests/regex.js b/tests/regex.js index dd77e30..0be8689 100644 --- a/tests/regex.js +++ b/tests/regex.js @@ -2,75 +2,174 @@ var winston = require('winston'); process.on('uncaughtException', function (err) { - winston.error('Encountered error while running test suite: ' + err.message); + winston.error('Encountered error while running test suite: ' + err.message); }); +function replaceAll(text, searchReplace) { + if (searchReplace === undefined) { + return text.toString(); + } + if( !Array.isArray(searchReplace) ){ + searchReplace = [searchReplace]; + } + for( var i = 0; i < searchReplace.length; ++i ){ + var sr = searchReplace[i]; + text = text.split( sr.search ).join( sr.replace ); + } + return text; +} + var expect = require("chai").expect; var youtubeLite = require("../library"); +var rawTitlePrefix = ' Youtube Video'; +var rawTimes = ' – {time}'; +var rawTitleSuffix = '
' +var rawSimpleTemplate = rawTitlePrefix + rawTitleSuffix; +var rawTimesTemplate = rawTitlePrefix + rawTimes + rawTitleSuffix; +var postQueryStringPrefixTemplate = ' Youtube Video – {time}'; +var postSimplePrefixTemplate = ' Youtube Video'; +var postSuffix = +'
' + +'\n
' + +'\n ' + +'\n
' + +'\n
'; +var postSimpleTemplate = postSimplePrefixTemplate + postSuffix; +var postQueryStringTemplate = postQueryStringPrefixTemplate + postSuffix; + +var videoIdSearchReplace = {search:'{videoId}', replace:'fXhUgV9qzI0'}; +var blankQueryStringSearchReplace = {search:'{queryString}', replace:''}; +var blankDataParameterstringSearchReplace = {search:'{dataParameters}', replace:''}; var posts = [ - { - description: 'Simple post with only the youtube link', - content: '

https://youtu.be/fXhUgV9qzI0

', - expected: '
' - }, - { - description: 'Markdown link with a youtube URL should be left alone', - content: '

linked

', - expected: '

linked

' - }, - { - description: 'Video URLs on consecutive lines', - content: '

https://youtu.be/fXhUgV9qzI0
\nhttps://youtu.be/fXhUgV9qzI0

', - expected: '
\n
' - }, - { - description: 'Link with text in front should be left alone', - content: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', - expected: '

Look at thishttps://youtu.be/fXhUgV9qzI0

' - }, - { - description: 'Link with text behind should be left alone', - content: '

https://youtu.be/fXhUgV9qzI0 uh huh

', - expected: '

https://youtu.be/fXhUgV9qzI0 uh huh

' - }, - { - description: 'Link with text in front and behind should be left alone', - content: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', - expected: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

' - }, - { - description: 'Simple post with only the youtu.be link with a start time', - content: '

https://youtu.be/fXhUgV9qzI0?t=55s

', - expected: '
' - }, - { - description: 'Simple post with only the youtube.com link with a start time', - content: '

https://youtube.com/watch?v=fXhUgV9qzI0&t=55s

', - expected: '
' - } + { + description: 'Simple post with only the youtube link', + content: '

https://youtu.be/fXhUgV9qzI0

', + expected:{ + raw: '

' + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '

', + post: '

' + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '

' + } + }, + { + description: 'Markdown link with a youtube URL should be left alone', + content: '

linked

', + expected: { + raw: '

linked

', + post: '

linked

' + } + }, + { + description: 'Video URLs on consecutive lines', + videoId: 'fXhUgV9qzI0', + content: '

https://youtu.be/fXhUgV9qzI0
\nhttps://youtu.be/fXhUgV9qzI0

', + expected: { + raw: '

' + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '
\n' + + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '

', + post: '

' + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '
\n' + + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '

' + } + }, + { + description: 'Link with text in front should be left alone', + content: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', + expected: { + raw: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', + post: '

Look at thishttps://youtu.be/fXhUgV9qzI0

' + } + }, + { + description: 'Link with text behind should be left alone', + content: '

https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: { + raw: '

https://youtu.be/fXhUgV9qzI0 uh huh

', + post: '

https://youtu.be/fXhUgV9qzI0 uh huh

' + } + }, + { + description: 'Link with text in front and behind should be left alone', + content: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: { + raw: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', + post: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

' + } + }, + { + description: 'Simple post with only the youtu.be link with a start time', + videoId: 'fXhUgV9qzI0', + content: '

https://youtu.be/fXhUgV9qzI0?t=55s

', + expected: { + raw: '

' + replaceAll( rawTimesTemplate, [videoIdSearchReplace,{search:'{time}',replace:'[00:55..]'}, {search:'{queryString}', replace:'?t=55s'}]) + '

', + post: '

' + replaceAll( postQueryStringTemplate, [ + videoIdSearchReplace, + {search:'{queryString}', replace:'t=55s'}, + {search:'{time}',replace:'[00:55..]'}, + {search:'{dataParameters}', replace:'start=55'} + ]) + '

' + } + }, + { + description: 'Simple post with only the youtube.com link with a start time', + videoId: 'fXhUgV9qzI0', + content: '

https://youtube.com/watch?v=fXhUgV9qzI0&t=55s

', + expected: { + raw: '

' + replaceAll( rawTimesTemplate, [ + {search:'youtu.be/',replace:'youtube.com/watch?v='}, + videoIdSearchReplace, + {search:'{time}',replace:'[00:55..]'}, + {search:'{queryString}', replace:'&t=55s'}]) + '

', + post: '

' + replaceAll( postQueryStringTemplate, [ + videoIdSearchReplace, + {search:'youtu.be/',replace:'youtube.com/watch?v='}, + {search:'?{queryString}',replace:'&{queryString}'}, + {search:'{queryString}', replace:'t=55s'}, + {search:'{time}',replace:'[00:55..]'}, + {search:'{dataParameters}', replace:'start=55'} + ]) + '

' + } + } ]; -describe( 'regex tests', function(){ - var i; - for( i = 0; i < posts.length; ++i ){ - var post = posts[i]; - describe( post.description, function(){ - - var data = new Object(); - var expectedValue = post.expected; - data.postData = new Object(); - data.postData.content = post.content; - it('converts the post correctly', function(){ - youtubeLite.parse( data, function( callback, theData ){ - expect( theData.postData.content ).to.equal( expectedValue ); - }); - - }); - }); - } +describe( 'Parsing a ', function(){ + var i; + for( i = 0; i < posts.length; ++i ){ + var post = posts[i]; + describe( post.description + ' in preview', function(){ + + var data = new Object(); + var expectedValue = post.expected.raw; + data = post.content; + it('converts the post correctly', function(){ + youtubeLite.parseRaw( data, function( callback, theData ){ + expect( theData ).to.equal( expectedValue ); + }); + + }); + }); + describe( post.description + ' in the final post', function(){ + + var data = new Object(); + var expectedValue = post.expected.post; + data.postData = new Object(); + data.postData.content = post.content; + it('converts the post correctly', function(){ + youtubeLite.parsePost( data, function( callback, theData ){ + expect( theData.postData.content ).to.equal( expectedValue ); + }); + + }); + }); + } }); From bed7d07354fba13825849df26ed46f4d6d34c192 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Tue, 31 May 2016 07:42:43 -0400 Subject: [PATCH 07/13] Show the channel title instead of the video title twice. --- library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.js b/library.js index f7748d0..2c0ff0f 100644 --- a/library.js +++ b/library.js @@ -64,7 +64,7 @@ function fetchSnippet( videoId, callback ){ videos = JSON.parse(videos); var snippet = videos.items[0].snippet; snippet.title = replaceAll( snippet.title, '<', '<'); - snippet.channelTitle = replaceAll( snippet.title, '<', '<'); + snippet.channelTitle = replaceAll( snippet.channelTitle, '<', '<'); snippet.duration = timeToString( parseDuration( videos.items[0].contentDetails.duration ) ); cache.set( videoId, snippet ); callback( null, snippet ); From 09a6e7b31d82b312db05ac195dd76ff24e2aa120 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Wed, 1 Jun 2016 13:10:31 -0400 Subject: [PATCH 08/13] Don't convert invalid video URLs. Also don't crash when trying to process what the youtube API returns. --- library.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library.js b/library.js index 2c0ff0f..82b74ef 100644 --- a/library.js +++ b/library.js @@ -62,6 +62,9 @@ function fetchSnippet( videoId, callback ){ }); res.on('end', function(){ videos = JSON.parse(videos); + if( !videos.items || videos.items.length == 0 ){ + return callback(null, null); + } var snippet = videos.items[0].snippet; snippet.title = replaceAll( snippet.title, '<', '<'); snippet.channelTitle = replaceAll( snippet.channelTitle, '<', '<'); @@ -173,6 +176,10 @@ function filter(data, match, preview, callback){ if( err ){ callback(err); } + if( !snippet ){ + // not a valid video, skip it + return callback(null, data); + } var params = getParams( (match[9] || '').split('&') ); var queryString = params.queryString; From 202a339b9cc006ac90186ecc7888248a17513a6d Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Thu, 2 Jun 2016 16:14:53 -0400 Subject: [PATCH 09/13] Added tests for handling result of API call with a bad videoId. --- library.js | 81 ++++++++++++++++++++++++---------------------- package.json | 3 +- tests/googleapi.js | 64 ++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 40 deletions(-) create mode 100644 tests/googleapi.js diff --git a/library.js b/library.js index 82b74ef..d31b126 100644 --- a/library.js +++ b/library.js @@ -6,9 +6,8 @@ var LRU = require('lru-cache'), var https = require('https'); var controllers = require('./lib/controllers'); -var apiKey; - var YoutubeLite = {}; +YoutubeLite.apiKey = null; YoutubeLite.cache = cache; YoutubeLite.youtubeUrl = /(

|^)((]*?>)(\4\7)<\/a>)(|<\/p>)/m; @@ -25,7 +24,7 @@ YoutubeLite.init = function(params, callback) { router.get('/api/admin/plugins/youtube-lite', controllers.renderAdminPage); db.getObjectField('settings:youtube-lite', 'id', function(err, result){ - apiKey = result; + YoutubeLite.apiKey = result; callback(); }); }; @@ -40,43 +39,50 @@ YoutubeLite.addAdminNavigation = function(header, callback) { callback(null, header); }; -function fetchSnippet( videoId, callback ){ +YoutubeLite.apiRequest = function( videoId, callback ){ + var req = https.request({ + host: 'www.googleapis.com', + path: '/youtube/v3/videos?id=' + videoId + '&key=' + YoutubeLite.apiKey + '&part=snippet,contentDetails&fields=items(snippet(title,channelTitle,thumbnails),contentDetails(duration))', + port: 443, + agent: false, + json: true, + method: 'GET' + },(res) => { + res.setEncoding('utf8'); + var videos = ''; + res.on('data', (data) => { + videos += data; + }); + res.on('end', function(){ + callback(null, videos); + }); + }); + req.end(); + + req.on('error', (err) => { + callback( error ); + }); +} + +YoutubeLite.fetchSnippet = function( videoId, callback ){ var cachedSnippet = cache.get(videoId); if( cachedSnippet ){ return callback(null, cachedSnippet); } else{ - if( apiKey ){ - var req = https.request({ - host: 'www.googleapis.com', - path: '/youtube/v3/videos?id=' + videoId + '&key=' + apiKey + '&part=snippet,contentDetails&fields=items(snippet(title,channelTitle,thumbnails),contentDetails(duration))', - port: 443, - agent: false, - json: true, - method: 'GET' - },(res) => { - res.setEncoding('utf8'); - var videos = ''; - res.on('data', (data) => { - videos += data; - }); - res.on('end', function(){ - videos = JSON.parse(videos); - if( !videos.items || videos.items.length == 0 ){ - return callback(null, null); - } - var snippet = videos.items[0].snippet; - snippet.title = replaceAll( snippet.title, '<', '<'); - snippet.channelTitle = replaceAll( snippet.channelTitle, '<', '<'); - snippet.duration = timeToString( parseDuration( videos.items[0].contentDetails.duration ) ); - cache.set( videoId, snippet ); - callback( null, snippet ); - }); - }); - req.end(); - - req.on('error', (err) => { - callback( error ); + if( YoutubeLite.apiKey ){ + return YoutubeLite.apiRequest( videoId, function(err, videos){ + videos = JSON.parse(videos); + if( !videos.items || videos.items.length == 0 ){ + cache.set( videoId, null ); + return callback(null, null); + } + var snippet = videos.items[0].snippet; + snippet.title = replaceAll( snippet.title, '<', '<'); + snippet.channelTitle = replaceAll( snippet.channelTitle, '<', '<'); + snippet.duration = timeToString( parseDuration( videos.items[0].contentDetails.duration ) ); + cache.set( videoId, snippet ); + callback( null, snippet ); }); } else{ @@ -164,14 +170,11 @@ function getParams( params ){ return result; } -function getStart( params ){ - -} function filter(data, match, preview, callback){ if(match){ var videoId = match[8]; - fetchSnippet(videoId, + YoutubeLite.fetchSnippet(videoId, function(err, snippet){ if( err ){ callback(err); diff --git a/package.json b/package.json index ee9aeba..76b46db 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "chai": "^3.5.0", "istanbul": "^0.4.2", "mocha": "^2.4.5", - "winston": "^2.2.0" + "winston": "^2.2.0", + "sinon": "^1.17" }, "keywords": [ "nodebb", diff --git a/tests/googleapi.js b/tests/googleapi.js new file mode 100644 index 0000000..fb4c178 --- /dev/null +++ b/tests/googleapi.js @@ -0,0 +1,64 @@ +'use strict'; +var winston = require('winston'); +var sinon = require('sinon'); +var expect = require("chai").expect; +var youtubeLite = require("../library"); + +process.on('uncaughtException', function (err) { + winston.error('Encountered error while running test suite: ' + err.message); +}); + + +describe('Youtube API call',function(){ + var sandbox = null; + beforeEach(function(){ + sandbox = sinon.sandbox.create(); + youtubeLite.apiKey = 'fakekey'; + }); + afterEach(function(){ + sandbox.restore(); + }); + + it("doesn't crash on a bad video id",function(){ + sandbox.stub( youtubeLite, 'apiRequest').yields( null, "{}" ); + youtubeLite.fetchSnippet( "badVideo", function( err, snippet ){ + expect( snippet ).to.be.null; + }); + }); + + it("returns {title, channelTitle, thumbnails, duration} on a good video id",function(){ + sandbox.stub( youtubeLite, 'apiRequest').yields( null, JSON.stringify( + { + items: [ + { + snippet: { + title: 'Video title!', + channelTitle: 'Channel Title!', + thumbnails:{ + default:{ url:'https://i.ytimg.com/vi/goodvideo/default.jpg'}, + high:{ url:'https://i.ytimg.com/vi/goodvideo/hqdefault.jpg'}, + medium:{ url:'https://i.ytimg.com/vi/goodvideo/mqdefault.jpg'}, + standard:{ url:'https://i.ytimg.com/vi/goodvideo/sddefault.jpg'}, + } + }, + contentDetails: {duration: 'PT3H2M31S'} + } + ] + }) ); + youtubeLite.fetchSnippet( "goodvideo", function( err, snippet ){ + expect( snippet ).to.deep.equal( + { + title: 'Video title!', + channelTitle: 'Channel Title!', + duration: '3:02:31', + thumbnails:{ + default:{ url:'https://i.ytimg.com/vi/goodvideo/default.jpg'}, + high:{ url:'https://i.ytimg.com/vi/goodvideo/hqdefault.jpg'}, + medium:{ url:'https://i.ytimg.com/vi/goodvideo/mqdefault.jpg'}, + standard:{ url:'https://i.ytimg.com/vi/goodvideo/sddefault.jpg'}, + } + } ); + + }); + }); +}); From a335e9274653aa9a5bb3920589652643f054b60f Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Tue, 14 Jun 2016 06:07:13 -0400 Subject: [PATCH 10/13] Support using `#` as a query string separator --- README.md | 1 + library.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 73bff6c..26d52c3 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Youtube Lite allows you to lazyload youtube videos into your NodeBB Forum with n - LRU cache to reduce quota usage. - Supports `t=`, '`start=`', `end=` query string parameters - With valid API key displays title, channel and duration information + - Support `#` in query strings 0.5.0 - Plugin now supports [Youtube API v3](https://developers.google.com/youtube/v3/?hl=en) diff --git a/library.js b/library.js index d31b126..f7720e3 100644 --- a/library.js +++ b/library.js @@ -10,7 +10,7 @@ var YoutubeLite = {}; YoutubeLite.apiKey = null; YoutubeLite.cache = cache; -YoutubeLite.youtubeUrl = /(

|^)((]*?>)(\4\7)<\/a>)(|<\/p>)/m; +YoutubeLite.youtubeUrl = /(

|^)((]*?>)(\4\7)<\/a>)(|<\/p>)/m; YoutubeLite.init = function(params, callback) { var router = params.router, From 9c1ed9778c61ba4ce1042800b36c3b7f604af767 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Thu, 16 Jun 2016 11:35:45 -0400 Subject: [PATCH 11/13] Fix error handler in googleapi call. --- library.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.js b/library.js index f7720e3..64447d5 100644 --- a/library.js +++ b/library.js @@ -60,7 +60,7 @@ YoutubeLite.apiRequest = function( videoId, callback ){ req.end(); req.on('error', (err) => { - callback( error ); + callback( err ); }); } From dfa539f8a4aa9a673808fdd16768dd1e90c90173 Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Thu, 16 Jun 2016 14:01:04 -0400 Subject: [PATCH 12/13] Fix nex bit of error handling after calling google api. --- library.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library.js b/library.js index 64447d5..6dfa14c 100644 --- a/library.js +++ b/library.js @@ -59,7 +59,7 @@ YoutubeLite.apiRequest = function( videoId, callback ){ }); req.end(); - req.on('error', (err) => { + req.on('error', (err) => { callback( err ); }); } @@ -72,6 +72,9 @@ YoutubeLite.fetchSnippet = function( videoId, callback ){ else{ if( YoutubeLite.apiKey ){ return YoutubeLite.apiRequest( videoId, function(err, videos){ + if( err ){ + callback(err); + } videos = JSON.parse(videos); if( !videos.items || videos.items.length == 0 ){ cache.set( videoId, null ); From 93cf13bc0ce2a6264fee559d252a97232ce57f8f Mon Sep 17 00:00:00 2001 From: boomzillawtf Date: Tue, 21 Jun 2016 17:00:21 -0400 Subject: [PATCH 13/13] More error handling fixes. Some logging. --- library.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/library.js b/library.js index 6dfa14c..57687f2 100644 --- a/library.js +++ b/library.js @@ -5,6 +5,7 @@ var LRU = require('lru-cache'), cache = LRU( 500 ); var https = require('https'); var controllers = require('./lib/controllers'); +var winston = require('winston'); var YoutubeLite = {}; YoutubeLite.apiKey = null; @@ -60,6 +61,8 @@ YoutubeLite.apiRequest = function( videoId, callback ){ req.end(); req.on('error', (err) => { + winston.error('[youtube-lite] error looking up video id: [' + videoId + ']' ); + winston.error( err ); callback( err ); }); } @@ -73,7 +76,7 @@ YoutubeLite.fetchSnippet = function( videoId, callback ){ if( YoutubeLite.apiKey ){ return YoutubeLite.apiRequest( videoId, function(err, videos){ if( err ){ - callback(err); + return callback(err); } videos = JSON.parse(videos); if( !videos.items || videos.items.length == 0 ){ @@ -180,7 +183,7 @@ function filter(data, match, preview, callback){ YoutubeLite.fetchSnippet(videoId, function(err, snippet){ if( err ){ - callback(err); + return callback(err); } if( !snippet ){ // not a valid video, skip it @@ -298,7 +301,7 @@ YoutubeLite.parseRaw = function(data, callback){ return callback(null, data); } filter(data, data.match(YoutubeLite.youtubeUrl), true, callback); -} +}; YoutubeLite.parsePost = function(data, callback) { if (!data || !data.postData || !data.postData.content) { @@ -306,6 +309,10 @@ YoutubeLite.parsePost = function(data, callback) { } var content = data.postData.content; filter(content, content.match(YoutubeLite.youtubeUrl), false, function(err, content){ + if(err){ + winston.error('[youtube-lite] error parsing pid ' + data.postData.pid ); + return callback(null, data); + } data.postData.content = content; callback(null, data); });