Skip to content

Instantly share code, notes, and snippets.

@Buildstarted
Created April 13, 2015 05:36
Show Gist options
  • Save Buildstarted/c45d64215b43b094df46 to your computer and use it in GitHub Desktop.
Save Buildstarted/c45d64215b43b094df46 to your computer and use it in GitHub Desktop.
Directly ported content providers from http://jabbr.net to typescript
module ContentProviders {
enum DeferredState {
Done,
Failed,
Pending
}
export class Deferred {
private _done: Function[];
private _fail: Function[];
private _always: Function[];
private _state: DeferredState;
private _arguments: any;
public Result: any[];
constructor();
constructor(init: Function);
constructor(init?: Function) {
this._done = new Array<Function>();
this._fail = new Array<Function>();
this._state = DeferredState.Pending;
if (init) {
init.apply(this, [this]);
}
}
private Execute(actions: Function[], args: any): void {
var i = actions.length;
args = Array.prototype.slice.call(args);
this._arguments = args;
while (i--) {
actions[i].apply(null, args);
}
}
public Resolve(...args: any[]): void {
this._state = DeferredState.Done;
this.Result = args;
this.Execute(this._done, arguments);
}
public Reject(): void {
this._state = DeferredState.Failed;
this.Execute(this._fail, arguments);
}
public Done(callback: Function): Deferred {
if (this._state === DeferredState.Done) {
callback.apply(null, this._arguments);
} else {
this._done.push(callback);
}
return this;
}
public Fail(callback: Function): Deferred {
if (this._state === DeferredState.Failed) {
callback.apply(null, this._arguments);
} else {
this._fail.push(callback);
}
return this;
}
public Always(callback: Function): Deferred {
if (this._state !== DeferredState.Pending) {
callback.apply(null, this._arguments);
} else {
this._fail.push(callback);
this._done.push(callback);
}
return this;
}
public static When(callbacks: Deferred[]): Deferred {
var callbackCount = callbacks.length;
if (callbackCount === 1) {
return callbacks[0];
}
var deferred = new Deferred();
var i: number = callbackCount;
while (i--) {
callbacks[i].Done(() => {
callbackCount -= 1;
if (callbackCount == 0) {
deferred.Resolve();
}
});
}
return deferred;
}
}
export interface IContentProvider {
GetContent(requestUri: string): Deferred;
IsValidContent(uri: string): boolean;
}
export class ContentProviderResult {
Title: string;
Content: string;
}
export class Uri {
private _anchor: HTMLAnchorElement;
constructor(url: string) {
this._anchor = document.createElement("a");
this._anchor.href = url;
}
public get Protocol(): string { return this._anchor.protocol; }
public get Hostname(): string { return this._anchor.hostname; }
public get Port(): string { return this._anchor.port; }
public get Pathname(): string { return this._anchor.pathname; }
public get Search(): string { return this._anchor.search; }
public get Hash(): string { return this._anchor.hash; }
public get Host(): string { return this._anchor.host; }
}
export class CollapsibleContentProvider implements IContentProvider {
private ContentFormat: string = "<div class=\"collapsible_content\"><h3 class=\"collapsible_title\">{0}</h3><div class=\"collapsible_box\">{1}</div></div>";
private HtmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp("(\d+)");
}
public GetContent(requestUri: string): Deferred {
var twitter = new TwitterLinkExpander();
var finalUrl = twitter.GetContent(requestUri);
var d = new Deferred();
finalUrl.Done((url) => {
var result = this.GetCollapsibleContent(url);
result.Done((result: ContentProviderResult) => {
if (this.IsCollapsible && result != null) {
var contentTitle: string = this.HtmlEscape(result.Title);
//string format this later
result.Content = result.Content;
}
d.Resolve(result);
});
});
return d;
}
public GetCollapsibleContent(requestUri: string): Deferred {
throw "Must override GetCollapsibleContent";
}
public ExtractParameters(responseUri: string): Array<string> {
//get final url
var list = responseUri.match(this.ParameterExtractionRegex);
var result: Array<string> = [];
if (list != null && list.length > 0) {
for (var i = 1; i < list.length; i++) {
if (list[i] != null) {
result.push(list[i]);
}
}
}
return result;
}
public IsValidContent(uri: string): boolean {
return false;
}
public get IsCollapsible(): boolean {
return true;
}
}
export class EmbedContentProvider extends CollapsibleContentProvider {
public get Domains(): Array<string> {
return null;
}
public get MediaFormatString(): string {
return null;
}
public GetCollapsibleContent(requestUri: string): Deferred {
var d = new Deferred();
var args = this.ExtractParameters(requestUri);
if (args == null || !args.length) {
d.Resolve(null);
}
var result = new ContentProviderResult();
result.Title = requestUri;
result.Content = ContentProviders.stringFormat(this.MediaFormatString, args);
d.Resolve(result);
return d;
}
public IsValidContent(url: string): boolean {
for (var i in this.Domains) {
if (url.lastIndexOf(this.Domains[i], 0) === 0) {
return true;
}
}
return false;
}
}
export class ImgurContentProvider extends CollapsibleContentProvider {
public GetCollapsibleContent(requestUri: string): Deferred {
var d = new Deferred();
var id = requestUri.split("/");
var result = new ContentProviderResult();
result.Content = ContentProviders.stringFormat("<a rel=\"nofollow external\" target=\"_blank\" href=\"{1}\"><img src=\"https://i.imgur.com/{0}.png\" style=\"max-width: 300px; max-height: 300px;\" /></a>", id[id.length - 1], requestUri);
result.Title = requestUri;
d.Resolve(result)
return d;
}
private IsMatch(source: string, regex: RegExp): boolean {
var match = source.match(regex);
return match != null && match.length > 0;
}
public IsValidContent(url: string): boolean {
var uri = new Uri(url);
var isProperDomain = this.IsMatch(uri.Host, /imgur.com/i) ||
this.IsMatch(uri.Host, /www.imgur.com/i) ||
this.IsMatch(uri.Host, /i.imgur.com/i);
var result = isProperDomain &&
//!(uri.Pathname.indexOf("/a/") == 0) &&
!(uri.Pathname == "/") &&
!(uri.Pathname.indexOf(".") != -1);
return result;
}
}
export class TwitterLinkExpander extends CollapsibleContentProvider {
public GetContent(requestUri: string): Deferred {
var d = new Deferred();
if (this.IsValidContent(requestUri)) {
var http = new XMLHttpRequest();
http.open('GET', '/resolve?url=' + requestUri, true);
http.onreadystatechange = function (e) {
if (http.readyState == 4) {
var result = JSON.parse(http.responseText);
d.Resolve(result.result);
}
};
http.send();
} else {
d.Resolve(requestUri);
}
return d;
}
public IsValidContent(url: string): boolean {
var uri = new Uri(url);
var matches = uri.Host.match(/t\.co/i);
return matches != null && matches.length > 0;
}
}
export class AudioContentProvider implements IContentProvider {
public IsValidContent(url: string): boolean {
return
url.indexOf(".mp3", url.length - 4) !== -1 ||
url.indexOf(".wav", url.length - 4) !== -1 ||
url.indexOf(".ogg", url.length - 4) !== -1;
}
public GetContent(requestUrl: string): Deferred {
var d = new Deferred();
var result = new ContentProviderResult();
result.Title = requestUrl;
result.Content = ContentProviders.stringFormat("<audio controls=\"controls\" src=\"{1}\">{0}</audio>", "Your browser does not support the audio tag.", requestUrl);
d.Resolve(result);
return d;
}
}
export class YoutubeContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"http://www.youtube.com",
"https://www.youtube.com",
"http://youtu.be",
"https://youtu.be",
];
}
public get MediaFormatString(): string {
return "<object width=\"425\" height=\"344\"><param name=\"WMode\" value=\"transparent\"></param><param name=\"movie\" value=\"https://www.youtube.com/v/{0}fs=1\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowScriptAccess\" value=\"always\"></param><embed src=\"https://www.youtube.com/v/{0}?fs=1\" wmode=\"transparent\" type=\"application/x-shockwave-flash\" allowfullscreen=\"true\" allowscriptaccess=\"always\" width=\"425\" height=\"344B\"></embed></object>";
}
public ExtractParameters(requestUrl: string): Array<string> {
var matches = requestUrl.match(/(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?/);
if (matches == null || matches.length < 2 || !matches[1]) {
return null;
}
var videoId = matches[1];
return [videoId];
}
}
export class VimeoContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"http://vimeo.com",
"http://www.vimeo.com"
];
}
public get MediaFormatString(): string {
return "<iframe src=\"//player.vimeo.com/video/{0}?title=0&amp;byline=0&amp;portrait=0&amp;color=c9ff23\" width=\"500\" height=\"271\" frameborder=\"0\" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>";
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp("(\d+)");
}
}
export class TwitPicContentProvider extends CollapsibleContentProvider {
private _twitPicUrlRegex = /^http:\/\/(www\.)?twitpic\.com\/(\w+)/i;
public GetCollapsibleContent(requestUrl: string): Deferred {
return new Deferred((d) => {
var result = new ContentProviderResult();
var match = requestUrl.match(this._twitPicUrlRegex);
if (match != null) {
var id = match[1];
result.Content = stringFormat("<a href=\"http://twitpic.com/{0}\"> <img src=\"http://twitpic.com/show/large/{0}\" style=\"max-width: 300px; max-height: 300px;\" /></a>", id);
result.Title = requestUrl;
d.Resolve(result);
} else {
d.Resolve(null);
}
});
}
public IsValidContent(url: string): boolean {
var match = url.match(this._twitPicUrlRegex);
return match != null && match.length !== 0;
}
}
export class TweetContentProvider extends CollapsibleContentProvider {
private _tweetRegex = new RegExp(".*/(?:statuses|status)/(\d+)");
private static _tweetScript = stringFormat("<div class=\"tweet_{{0}}\"><script src=\"{0}\"></script></div>", htmlEscape("https://api.twitter.com/1/statuses/oembed.json?id={0}&callback=addTweet"));
public GetCollapsibleContent(requestUrl: string): Deferred {
return new Deferred((d: Deferred) => {
var match = requestUrl.match(this._tweetRegex);
if (match != null && match.length !== 0) {
var status = match[1];
if (status != null) {
var result = new ContentProviderResult();
result.Content = stringFormat(TweetContentProvider._tweetScript, status);
result.Title = requestUrl;
d.Resolve(result);
return;
}
}
d.Resolve(null);
});
}
public IsValidContent(url: string): boolean {
return
url.lastIndexOf("http://twitter.com/", 0) === 0 ||
url.lastIndexOf("https://twitter.com/", 0) === 0 ||
url.lastIndexOf("http://www.twitter.com/", 0) === 0 ||
url.lastIndexOf("https://www.twitter.com/", 0) === 0;
}
}
export class SpotifyContentProvider extends CollapsibleContentProvider {
public GetCollapsibleContent(requestUrl: string): Deferred {
return new Deferred((d: Deferred) => {
var spotifyUrl = SpotifyContentProvider.ExtractSpotifyUri(requestUrl);
var result = new ContentProviderResult();
result.Content = stringFormat("<iframe src=\"https://embed.spotify.com/?uri=spotify:{0}\" width=\"300\" height=\"380\" frameborder=\"0\" allowtransparency=\"true\"></iframe>", spotifyUrl);
result.Title = stringFormat("spotify:track:{0}", spotifyUrl);
d.Resolve(result);
});
}
private static ExtractSpotifyUri(url: string) {
return url.substring(1).replace("/", ":");
}
public IsValidContent(url: string): boolean {
return url.lastIndexOf("http://open.spotify.com/", 0) === 0;
}
}
export class PastieContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"http://vimeo.com",
"http://www.vimeo.com"
];
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp("(\d+)");
}
public get IsCollapsible(): boolean {
return false;
}
public get MediaFormatString(): string {
var scriptTagId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return stringFormat(this.ScriptTagFormat, scriptTagId);
}
private get ScriptTagFormat(): string {
return "<div id='{0}'></div><script type='text/javascript'>captureDocumentWrite('http://pastie.org/{{0}}.js', 'http://pastie.org/{{0}}', $('#{0}'));</script>";
}
}
export class MixcloudContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"http://www.mixcloud.com/"
];
}
public get MediaFormatString(): string {
return "<object width=\"100%\" height=\"120\"><param name=\"movie\" value=\"//www.mixcloud.com/media/swf/player/mixcloudLoader.swf?feed={0}&embed_uuid=06c5d381-1643-407d-80e7-2812382408e9&stylecolor=1e2671&embed_type=widget_standard\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"wmode\" value=\"opaque\"></param><param name=\"allowscriptaccess\" value=\"always\"></param><embed src=\"//www.mixcloud.com/media/swf/player/mixcloudLoader.swf?feed={0}&embed_uuid=06c5d381-1643-407d-80e7-2812382408e9&stylecolor=1e2671&embed_type=widget_standard\" type=\"application/x-shockwave-flash\" wmode=\"opaque\" allowscriptaccess=\"always\" allowfullscreen=\"true\" width=\"100%\" height=\"120\"></embed></object>";
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp("(\d+)");
}
public ExtractParameters(url: string): Array<string> {
return [url];
}
public get IsCollapsible(): boolean {
return true;
}
}
export class JoinMeContentProvider extends CollapsibleContentProvider {
private static _iframedMeetingFormat = "<iframe src=\"{0}\" sandbox=\"allow-same-origin allow-scripts allow-popups allow-forms\" width=\"700\" height=\"400\"></iframe>";
public GetCollapsibleContent(requestUrl: string): Deferred {
return new Deferred((d: Deferred) => {
var result = new ContentProviderResult();
result.Title = requestUrl;
result.Content = stringFormat(JoinMeContentProvider._iframedMeetingFormat, requestUrl);
d.Resolve(result);
});
}
public IsValidContent(url: string): boolean {
return url.lastIndexOf("https://join.me/", 0) === 0;
}
}
export class CollegeHumorContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"http://www.collegehumor.com"
];
}
public get MediaFormatString(): string {
return "<object type=\"application/x-shockwave-flash\" data=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\" width=\"600\" height=\"338\"><param name=\"allowfullscreen\" value=\"true\"/><param name=\"wmode\" value=\"transparent\"/><param name=\"allowScriptAccess\" value=\"always\"/><param name=\"movie\" quality=\"best\" value=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\"/><embed src=\"http://www.collegehumor.com/moogaloop/moogaloop.swf?clip_id={0}&use_node_id=true&fullscreen=1\" type=\"application/x-shockwave-flash\" wmode=\"transparent\" width=\"600\" height=\"338\" allowScriptAccess=\"always\"></embed></object>";
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp(".*video/(\d+).*");
}
public ExtractParameters(url: string): Array<string> {
return [url];
}
public get IsCollapsible(): boolean {
return true;
}
}
export class GistContentProvider extends EmbedContentProvider {
public get Domains(): Array<string> {
return [
"https://gist.github.com"
];
}
public get MediaFormatString(): string {
var scriptTagId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
var before = stringFormat("<div id='{0}'></div><script type='text/javascript'>captureDocumentWrite('https://gist.github.com/{{0}}.js', 'https://gist.github.com/{{0}}', $('#{0}'));</script>", scriptTagId);
return before;
}
public get ParameterExtractionRegex(): RegExp {
return new RegExp("(\\w+$)");
}
public get IsCollapsible(): boolean {
return false;
}
}
export class ImageContentProvider extends CollapsibleContentProvider {
public GetCollapsibleContent(requestUrl: string): Deferred {
return new Deferred((d: Deferred) => {
var result = new ContentProviderResult();
result.Title = requestUrl;
result.Content = stringFormat("<a rel=\"nofollow external\" target=\"_blank\" href=\"{0}\"><img src=\"{0}\" style=\"max-width: 300px; max-height: 300px;\" /></a>", requestUrl);
d.Resolve(result);
});
}
public IsValidContent(url: string): boolean {
url = url.toLowerCase();
return url.indexOf(".png", url.length - 4) !== -1 ||
url.indexOf(".bmp", url.length - 4) !== -1 ||
url.indexOf(".jpg", url.length - 4) !== -1 ||
url.indexOf(".jpeg", url.length - 5) !== -1 ||
url.indexOf(".gif", url.length - 4) !== -1;
}
}
export class ResourceProcessor {
private _contentProviders: Array<IContentProvider>;
constructor() {
this._contentProviders = new Array<IContentProvider>();
this._contentProviders.push(new ImgurContentProvider());
this._contentProviders.push(new AudioContentProvider());
this._contentProviders.push(new YoutubeContentProvider());
this._contentProviders.push(new VimeoContentProvider());
this._contentProviders.push(new TwitPicContentProvider());
this._contentProviders.push(new TweetContentProvider());
this._contentProviders.push(new SpotifyContentProvider());
this._contentProviders.push(new PastieContentProvider());
this._contentProviders.push(new MixcloudContentProvider());
this._contentProviders.push(new JoinMeContentProvider());
this._contentProviders.push(new CollegeHumorContentProvider());
this._contentProviders.push(new GistContentProvider());
this._contentProviders.push(new ImageContentProvider());
}
public ExtractResource(url: string): Deferred {
var d = new Deferred();
var twitter = new TwitterLinkExpander();
var handler = twitter.GetContent(url);
handler.Done((uri) => {
var validProviders: Array<IContentProvider> = [];
for (var i in this._contentProviders) {
if (this._contentProviders[i].IsValidContent(uri)) {
validProviders.push(this._contentProviders[i]);
}
}
var tasks = [];
var completed: boolean = false;
var count = validProviders.length;
for (var i in validProviders) {
var task = validProviders[i].GetContent(uri);
tasks.push(task);
}
if (tasks.length > 0) {
Deferred.When(tasks)
.Done(function () {
for (var i in tasks) {
var task = tasks[i];
if (task.Result != null && task.Result[0] != null) {
d.Resolve(task.Result[0]);
return;
}
}
d.Resolve(null);
});
} else {
d.Resolve(null);
}
});
return d;
}
}
export function stringFormat(formatString: string, ...args: any[]) {
var builder = [];
var st = 0;
var j = 0;
for (var i = 0; i < formatString.length; i++) {
switch (st) {
case 0:
{
switch (formatString[i]) {
case "{":
st = 1;
builder.push(formatString.substr(j, i - j));
j = i + 1;
break;
case "}":
st = 2;
builder.push(formatString.substr(j, i - j));
j = i + 1;
break;
default:
st = 0;
break;
}
break;
}
case 1:
{
switch (formatString[i]) {
case "{":
builder.push("{");
j = i + 1;
st = 0;
break;
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
st = 3;
break;
default:
console.error("stringFormat: Pattern error processing string", formatString, "at index", i);
return null;
}
break;
}
case 2:
{
switch (formatString[i]) {
case "}":
builder.push("}");
j = i + 1;
st = 0;
break;
default:
console.error("stringFormat: Pattern error processing string", formatString, "at index", i);
return null;
}
break;
}
case 3:
{
switch (formatString[i]) {
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
st = 3;
break;
case "}":
{
var d = formatString.substr(j, i - j);
var n = parseInt(d);
if (n + 1 >= arguments.length) {
console.error("stringFormat: The parameter {" + n + "} was not provided. Format string:", formatString);
return null;
}
builder.push(arguments[n + 1]);
st = 0;
j = i + 1;
break;
}
default:
console.error("stringFormat: Pattern error processing string", formatString, "at index", i);
return null;
}
break;
}
}
}
if (i > j) {
builder.push(formatString.substr(j, i - j));
}
return builder.join("");
}
export function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment