glowingbear-mainbox/js/plugins.js
Jordan Callicoat 14879811a5 Make videoPlugin work for all imgur gifv videos
Some gifv links on imgur.com do not have an associated webm video
and only provide an mp4 video. Add two source elements with proper
mimetypes for both types of video. The video player will fall back
to the secondary source if the first cannot be loaded (due to 404)

Example: Trying to load the webm version of this video

  http://i.imgur.com/i7D4GRb.webm

will result in a 302 redirect to the gifv url, which tries to load
an HTML document. This will cause our video player to reject the
document as an invalid video format (text/html); on some videos it
will simply 404. The mp4 version is available and our player will
fall back to that.
2017-04-18 16:55:44 +00:00

576 lines
23 KiB
JavaScript

/*
* This file contains the plugin definitions
*/
(function() {
'use strict';
var plugins = angular.module('plugins', []);
/*
* Definition of a user provided plugin with sensible default values
*
* User plugins are created by providing a name and a contentForMessage
* function that parses a string and returns any additional content.
*/
var Plugin = function(name, contentForMessage) {
return {
contentForMessage: contentForMessage,
exclusive: false,
name: name
};
};
// Regular expression that detects URLs for UrlPlugin
var urlRegexp = /(?:(?:https?|ftp):\/\/|www\.|ftp\.)\S*[^\s.;,(){}<>]/g;
/*
* Definition of a user provided plugin that consumes URLs
*
* URL plugins are created by providing a name and a function that
* that parses a URL and returns any additional content.
*/
var UrlPlugin = function(name, urlCallback) {
return {
contentForMessage: function(message) {
var urls = _.uniq(message.match(urlRegexp));
var content = [];
for (var i = 0; urls && i < urls.length; i++) {
var result = urlCallback(urls[i]);
if (result) {
content.push(result);
}
}
return content;
},
exclusive: false,
name: name
};
};
/*
* This service provides access to the plugin manager
*
* The plugin manager is where the various user provided plugins
* are registered. It is responsible for finding additional content
* to display when messages are received.
*
*/
plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
/*
* Defines the plugin manager object
*/
var PluginManagerObject = function() {
var plugins = [];
/*
* Register the user provides plugins
*
* @param userPlugins user provided plugins
*/
var registerPlugins = function(userPlugins) {
for (var i = 0; i < userPlugins.length; i++) {
plugins.push(userPlugins[i]);
}
};
var nsfwRegexp = new RegExp('nsfw', 'i');
/*
* Iterates through all the registered plugins
* and run their contentForMessage function.
*/
var contentForMessage = function(message) {
message.metadata = [];
var addPluginContent = function(content, pluginName, num) {
if (num) {
pluginName += " " + num;
}
// If content isn't a callback, it's HTML
if (!(content instanceof Function)) {
content = $sce.trustAsHtml(content);
}
message.metadata.push({
'content': content,
'nsfw': nsfw,
'name': pluginName
});
};
for (var i = 0; i < plugins.length; i++) {
var nsfw = false;
if (message.text.match(nsfwRegexp)) {
nsfw = true;
}
var pluginContent = plugins[i].contentForMessage(
message.text
);
if (pluginContent && pluginContent !== []) {
if (pluginContent instanceof Array) {
for (var j = pluginContent.length - 1; j >= 0; j--) {
// only give a number if there are multiple embeds
var num = (pluginContent.length == 1) ? undefined : (j + 1);
addPluginContent(pluginContent[j], plugins[i].name, num);
}
} else {
addPluginContent(pluginContent, plugins[i].name);
}
if (plugins[i].exclusive) {
break;
}
}
}
return message;
};
return {
registerPlugins: registerPlugins,
contentForMessage: contentForMessage
};
};
// Instanciates and registers the plugin manager.
this.PluginManager = new PluginManagerObject();
this.PluginManager.registerPlugins(userPlugins.plugins);
}]);
/*
* This factory exposes the collection of user provided plugins.
*
* To create your own plugin, you need to:
*
* 1. Define its contentForMessage function. The contentForMessage
* function takes a string as a parameter and returns a HTML string.
*
* 2. Instantiate a Plugin object with contentForMessage function as its
* argument.
*
* 3. Add it to the plugins array.
*
*/
plugins.factory('userPlugins', function() {
// standard JSONp origin policy trick
var jsonp = function (url, callback) {
var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
window[callbackName] = function(data) {
delete window[callbackName];
document.body.removeChild(script);
callback(data);
};
var script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
document.body.appendChild(script);
};
/*
* Spotify Embedded Player
*
* See: https://developer.spotify.com/technologies/widgets/spotify-play-button/
*
*/
var spotifyPlugin = new Plugin('Spotify music', function(message) {
var content = [];
var addMatch = function(match) {
for (var i = 0; match && i < match.length; i++) {
var element = angular.element('<iframe></iframe>')
.attr('src', '//embed.spotify.com/?uri=' + match[i])
.attr('width', '350')
.attr('height', '80')
.attr('frameborder', '0')
.attr('allowtransparency', 'true');
content.push(element.prop('outerHTML'));
}
};
addMatch(message.match(/spotify:track:[a-zA-Z-0-9]{22}/g));
addMatch(message.match(/spotify:artist:[a-zA-Z-0-9]{22}/g));
addMatch(message.match(/spotify:user:\w+:playlist:[a-zA-Z-0-9]{22}/g));
addMatch(message.match(/(open|play)\.spotify\.com\/track\/[a-zA-Z-0-9]{22}/g));
addMatch(message.match(/(open|play)\.spotify\.com\/artist\/[a-zA-Z-0-9]{22}/g));
addMatch(message.match(/(open|play)\.spotify\.com\/user\/\w+\/playlist\/[a-zA-Z-0-9]{22}/g));
return content;
});
/*
* YouTube Embedded Player
*
* See: https://developers.google.com/youtube/player_parameters
*/
var youtubePlugin = new UrlPlugin('YouTube video', function(url) {
var regex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i,
match = url.match(regex);
if (match){
var token = match[1],
embedurl = "https://www.youtube.com/embed/" + token + "?html5=1&iv_load_policy=3&modestbranding=1&rel=0",
element = angular.element('<iframe></iframe>')
.attr('src', embedurl)
.attr('width', '560')
.attr('height', '315')
.attr('frameborder', '0')
.attr('allowfullscreen', 'true');
return element.prop('outerHTML');
}
});
/*
* Dailymotion Embedded Player
*
* See: http://www.dailymotion.com/doc/api/player.html
*/
var dailymotionPlugin = new Plugin('Dailymotion video', function(message) {
var rPath = /dailymotion\.com\/.*video\/([^_?# ]+)/;
var rAnchor = /dailymotion\.com\/.*#video=([^_& ]+)/;
var rShorten = /dai\.ly\/([^_?# ]+)/;
var match = message.match(rPath) || message.match(rAnchor) || message.match(rShorten);
if (match) {
var id = match[1];
var embedurl = 'https://www.dailymotion.com/embed/video/' + id + '?html&controls=html&startscreen=html&info=0&logo=0&related=0';
var element = angular.element('<iframe></iframe>')
.attr('src', embedurl)
.attr('width', '480')
.attr('height', '270')
.attr('frameborder', '0');
return element.prop('outerHTML');
}
return null;
});
/*
* AlloCine Embedded Player
*/
var allocinePlugin = new Plugin('AlloCine video', function(message) {
var rVideokast = /allocine\.fr\/videokast\/video-(\d+)/;
var rCmedia = /allocine\.fr\/.*cmedia=(\d+)/;
var match = message.match(rVideokast) || message.match(rCmedia);
if (match) {
var id = match[1];
var embedurl = 'http://www.allocine.fr/_video/iblogvision.aspx?cmedia=' + id;
var element = angular.element('<iframe></iframe>')
.attr('src', embedurl)
.attr('width', '480')
.attr('height', '270')
.attr('frameborder', '0');
return element.prop('outerHTML');
}
return null;
});
/*
* Image Preview
*/
var imagePlugin = new UrlPlugin('image', function(url) {
if (url.match(/\.(bmp|gif|ico|jpeg|jpg|png|svg|svgz|tif|tiff|webp)(:(small|medium|large))?\b/i)) {
/* A fukung.net URL may end by an image extension but is not a direct link. */
if (url.indexOf("^https?://fukung.net/v/") != -1) {
url = url.replace(/.*\//, "http://media.fukung.net/imgs/");
} else if (url.match(/^http:\/\/(i\.)?imgur\.com\//i)) {
// remove protocol specification to load over https if used by g-b
url = url.replace(/http:/, "https:");
} else if (url.match(/^https:\/\/www\.dropbox\.com\/s\/[a-z0-9]+\//i)) {
// Dropbox requires a get parameter, dl=1
var dbox_url = document.createElement("a");
dbox_url.href = url;
var base_url = dbox_url.protocol + '//' + dbox_url.host + dbox_url.pathname + '?';
var dbox_params = dbox_url.search.substring(1).split('&');
var dl_added = false;
for (var i = 0; i < dbox_params.length; i++) {
if (dbox_params[i].split('=')[0] === "dl") {
dbox_params[i] = "dl=1";
dl_added = true;
// we continue looking at the other parameters in case
// it's specified twice or something
}
}
if (!dl_added) {
dbox_params.push("dl=1");
}
url = base_url + dbox_params.join('&');
}
return function() {
var element = this.getElement();
var imgElem = angular.element('<a></a>')
.attr('target', '_blank')
.attr('href', url)
.append(angular.element('<img>')
.addClass('embed')
.attr('src', url));
element.innerHTML = imgElem.prop('outerHTML');
};
}
});
/*
* Audio Preview
*/
var audioPlugin = new UrlPlugin('audio', function(url) {
if (url.match(/\.(flac|m4a|mid|midi|mp3|oga|ogg|ogx|opus|pls|spx|wav|wave|wma)\b/i)) {
return function() {
var element = this.getElement();
var aelement = angular.element('<audio controls></audio>')
.addClass('embed')
.attr('width', '560')
.append(angular.element('<source></source>')
.attr('src', url));
element.innerHTML = aelement.prop('outerHTML');
};
}
});
/*
* Video Preview
*/
var videoPlugin = new UrlPlugin('video', function(url) {
if (url.match(/\.(3gp|avi|flv|gifv|mkv|mp4|ogv|webm|wmv)\b/i)) {
return function() {
var element = this.getElement(), src;
var velement = angular.element('<video autoplay loop muted></video>')
.addClass('embed')
.attr('width', '560');
// imgur doesn't always have webm for gifv so add sources for webm and mp4
if (url.match(/^http:\/\/(i\.)?imgur\.com\//i) &&
url.indexOf('.gifv') > -1) {
src = angular.element('<source></source>')
.attr('src', url.replace(/\.gifv\b/i, ".webm"))
.attr('type', 'video/webm');
velement.append(src);
src = angular.element('<source></source>')
.attr('src', url.replace(/\.gifv\b/i, ".mp4"))
.attr('type', 'video/mp4');
velement.append(src);
} else {
src = angular.element('<source></source>')
.attr('src', url);
velement.append(src);
}
element.innerHTML = velement.prop('outerHTML');
};
}
});
/*
* Cloud Music Embedded Players
*/
var cloudmusicPlugin = new UrlPlugin('cloud music', function(url) {
/* SoundCloud http://help.soundcloud.com/customer/portal/articles/247785-what-widgets-can-i-use-from-soundcloud- */
var element;
if (url.match(/^https?:\/\/soundcloud\.com\//)) {
element = angular.element('<iframe></iframe>')
.attr('width', '100%')
.attr('height', '120')
.attr('scrolling', 'no')
.attr('frameborder', 'no')
.attr('src', 'https://w.soundcloud.com/player/?url=' + url + '&amp;color=ff6600&amp;auto_play=false&amp;show_artwork=true');
return element.prop('outerHTML');
}
/* MixCloud */
if (url.match(/^https?:\/\/([a-z]+\.)?mixcloud\.com\//)) {
element = angular.element('<iframe></iframe>')
.attr('width', '480')
.attr('height', '60')
.attr('frameborder', '0')
.attr('src', '//www.mixcloud.com/widget/iframe/?feed=' + url + '&mini=1&stylecolor=&hide_artwork=&embed_type=widget_standard&hide_tracklist=1&hide_cover=');
return element.prop('outerHTML');
}
});
/*
* Google Maps
*/
var googlemapPlugin = new UrlPlugin('Google Map', function(url) {
if (url.match(/^https?:\/\/maps\.google\./i) || url.match(/^https?:\/\/(?:[\w]+\.)?google\.[\w]+\/maps/i)) {
var element = angular.element('<iframe></iframe>')
.attr('width', '450')
.attr('height', '350')
.attr('frameborder', '0')
.attr('scrolling', 'no')
.attr('marginheight', '0')
.attr('src', url + '&output=embed');
return element.prop('outerHTML');
}
});
/*
* Asciinema plugin
*/
var asciinemaPlugin = new UrlPlugin('ascii cast', function(url) {
var regexp = /^https?:\/\/(?:www\.)?asciinema\.org\/a\/([0-9a-z]+)/i,
match = url.match(regexp);
if (match) {
var id = match[1];
return function() {
var element = this.getElement();
var scriptElem = document.createElement('script');
scriptElem.src = 'https://asciinema.org/a/' + id + '.js';
scriptElem.id = 'asciicast-' + id;
scriptElem.async = true;
element.appendChild(scriptElem);
};
}
});
var yrPlugin = new UrlPlugin('meteogram', function(url) {
var regexp = /^https?:\/\/(?:www\.)?yr\.no\/(place|stad|sted|sadji|paikka)\/(([^\s.;,(){}<>\/]+\/){3,})/;
var match = url.match(regexp);
if (match) {
return function() {
var element = this.getElement();
var language = match[1];
var location = match[2];
var city = match[match.length - 1].slice(0, -1);
url = "http://www.yr.no/" + language + "/" + location + "avansert_meteogram.png";
var ielement = angular.element('<img>')
.attr('src', url)
.attr('alt', 'Meteogram for ' + city);
element.innerHTML = ielement.prop('outerHTML');
};
}
});
// Embed GitHub gists
var gistPlugin = new UrlPlugin('Gist', function(url) {
// ignore trailing slashes and anchors
var regexp = /^(https:\/\/gist\.github\.com\/(?:.*?))[\/]?(?:\#.*)?$/i;
var match = url.match(regexp);
if (match) {
// get the URL from the match to trim away pseudo file endings and request parameters
url = match[1] + '.json';
// load gist asynchronously -- return a function here
return function() {
var element = this.getElement();
jsonp(url, function(data) {
// Add the gist stylesheet only once
if (document.querySelectorAll('link[rel=stylesheet][href="' + data.stylesheet + '"]').length < 1) {
var stylesheet = '<link rel="stylesheet" href="' + data.stylesheet + '"></link>';
document.getElementsByTagName('head')[0].innerHTML += stylesheet;
}
element.innerHTML = '<div style="clear:both">' + data.div + '</div>';
});
};
}
});
var pastebinPlugin = new UrlPlugin('Pastebin', function(url) {
var regexp = /^https?:\/\/pastebin\.com\/([^.?]+)/i;
var match = url.match(regexp);
if (match) {
var id = match[1],
embedurl = "https://pastebin.com/embed_iframe/" + id,
element = angular.element('<iframe></iframe>')
.attr('src', embedurl)
.attr('width', '100%')
.attr('height', '480');
return element.prop('outerHTML');
}
});
/* match giphy links and display the assocaited gif images
* sample input: http://giphy.com/gifs/eyes-shocked-bird-feqkVgjJpYtjy
* sample output: https://media.giphy.com/media/feqkVgjJpYtjy/giphy.gif
*/
var giphyPlugin = new UrlPlugin('Giphy', function(url) {
var regex = /^https?:\/\/giphy\.com\/gifs\/.*-(.*)\/?/i;
// on match, id will contain the entire url in [0] and the giphy id in [1]
var id = url.match(regex);
if (id) {
var src = "https://media.giphy.com/media/" + id[1] + "/giphy.gif";
return function() {
var element = this.getElement();
var gelement = angular.element('<a></a>')
.attr('target', '_blank')
.attr('href', url)
.append(angular.element('<img>')
.addClass('embed')
.attr('src', src));
element.innerHTML = gelement.prop('outerHTML');
};
}
});
var tweetPlugin = new UrlPlugin('Tweet', function(url) {
var regexp = /^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/i;
var match = url.match(regexp);
if (match) {
url = 'https://api.twitter.com/1/statuses/oembed.json?id=' + match[2];
return function() {
var element = this.getElement();
jsonp(url, function(data) {
// separate the HTML into content and script tag
var scriptIndex = data.html.indexOf("<script ");
var content = data.html.substr(0, scriptIndex);
// Set DNT (Do Not Track)
content = content.replace("<blockquote class=\"twitter-tweet\">", "<blockquote class=\"twitter-tweet\" data-dnt=\"true\">");
element.innerHTML = content;
// The script tag needs to be generated manually or the browser won't load it
var scriptElem = document.createElement('script');
// Hardcoding the URL here, I don't suppose it's going to change anytime soon
scriptElem.src = "https://platform.twitter.com/widgets.js";
element.appendChild(scriptElem);
});
};
}
});
/*
* Vine plugin
*/
var vinePlugin = new UrlPlugin('Vine', function (url) {
var regexp = /^https?:\/\/(www\.)?vine\.co\/v\/([a-zA-Z0-9]+)(\/.*)?/i,
match = url.match(regexp);
if (match) {
var id = match[2], embedurl = "https://vine.co/v/" + id + "/embed/simple?audio=1";
var element = angular.element('<iframe></iframe>')
.addClass('vine-embed')
.attr('src', embedurl)
.attr('width', '600')
.attr('height', '600')
.attr('frameborder', '0');
return element.prop('outerHTML') + '<script async src="https://platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>';
}
});
/*
* Streamable Embedded Player
*/
var streamablePlugin = new UrlPlugin('Streamable video', function(url) {
var regexp = /^https?:\/\/streamable\.com\/s?\/?(.+)/,
match = url.match(regexp);
if (match) {
var id = match[1], embedurl = 'https://streamable.com/s/' + id;
var element = angular.element('<iframe></iframe>')
.attr('src', embedurl)
.attr('width', '480')
.attr('height', '270')
.attr('frameborder', '0');
return element.prop('outerHTML');
}
});
return {
plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, videoPlugin, audioPlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin, gistPlugin, pastebinPlugin, giphyPlugin, tweetPlugin, vinePlugin, streamablePlugin]
};
});
})();