parent
1e443da137
commit
1cfdba4d3a
@ -1,21 +0,0 @@ |
||||
Template.oembedBaseWidget.helpers |
||||
template: -> |
||||
if this._overrideTemplate |
||||
return this._overrideTemplate |
||||
|
||||
if this.headers?.contentType?.match(/image\/.*/)? |
||||
return 'oembedImageWidget' |
||||
|
||||
if this.headers?.contentType?.match(/audio\/.*/)? |
||||
return 'oembedAudioWidget' |
||||
|
||||
if this.headers?.contentType?.match(/video\/.*/)? or this.meta?.twitterPlayerStreamContentType?.match(/video\/.*/)? |
||||
return 'oembedVideoWidget' |
||||
|
||||
if this.meta?.oembedHtml? |
||||
return 'oembedFrameWidget' |
||||
|
||||
if this.meta?.sandstorm?.grain? |
||||
return 'oembedSandstormGrain' |
||||
|
||||
return 'oembedUrlWidget' |
||||
@ -0,0 +1,28 @@ |
||||
Template.oembedBaseWidget.helpers({ |
||||
template() { |
||||
let contentType; |
||||
if (this.headers) { |
||||
contentType = this.headers.contentType; |
||||
} |
||||
|
||||
if (this._overrideTemplate) { |
||||
return this._overrideTemplate; |
||||
} |
||||
if (this.headers && contentType && contentType.match(/image\/.*/)) { |
||||
return 'oembedImageWidget'; |
||||
} |
||||
if (this.headers && contentType && contentType.match(/audio\/.*/)) { |
||||
return 'oembedAudioWidget'; |
||||
} |
||||
if ((this.headers && contentType && contentType.match(/video\/.*/)) || (this.meta && this.meta.twitterPlayerStreamContentType && this.meta.twitterPlayerStreamContentType.match(/video\/.*/))) { |
||||
return 'oembedVideoWidget'; |
||||
} |
||||
if (this.meta && this.meta.oembedHtml) { |
||||
return 'oembedFrameWidget'; |
||||
} |
||||
if (this.meta && this.meta.sandstorm && this.meta.sandstorm.grain) { |
||||
return 'oembedSandstormGrain'; |
||||
} |
||||
return 'oembedUrlWidget'; |
||||
} |
||||
}); |
||||
@ -1,7 +0,0 @@ |
||||
Template.oembedAudioWidget.helpers |
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,10 @@ |
||||
Template.oembedAudioWidget.helpers({ |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
if (this.collapsed) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault); |
||||
} |
||||
} |
||||
}); |
||||
@ -1,7 +0,0 @@ |
||||
Template.oembedFrameWidget.helpers |
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,10 @@ |
||||
Template.oembedFrameWidget.helpers({ |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
if (this.collapsed) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault) === true; |
||||
} |
||||
} |
||||
}); |
||||
@ -1,17 +0,0 @@ |
||||
Template.oembedImageWidget.helpers |
||||
loadImage: -> |
||||
|
||||
if Meteor.user()?.settings?.preferences?.autoImageLoad is false and this.downloadImages? is not true |
||||
return false |
||||
|
||||
if Meteor.Device.isPhone() and Meteor.user()?.settings?.preferences?.saveMobileBandwidth and this.downloadImages? is not true |
||||
return false |
||||
|
||||
return true |
||||
|
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,22 @@ |
||||
Template.oembedImageWidget.helpers({ |
||||
loadImage() { |
||||
const user = Meteor.user(); |
||||
|
||||
if (user && user.settings && user.settings.preferences && user.settings.preferences.autoImageLoad === false && this.downloadImages) { |
||||
return false; |
||||
} |
||||
if (Meteor.Device.isPhone() && user() && user.settings && user.settings.preferences && user.settings.preferences.saveMobileBandwidth && this.downloadImages) { |
||||
return false; |
||||
} |
||||
return true; |
||||
}, |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
|
||||
if (this.collapsed != null) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault) === true; |
||||
} |
||||
} |
||||
}); |
||||
@ -1,17 +0,0 @@ |
||||
Template.oembedSandstormGrain.helpers |
||||
token: -> |
||||
return @meta.sandstorm.grain.token |
||||
appTitle: -> |
||||
return @meta.sandstorm.grain.appTitle.defaultText |
||||
grainTitle: -> |
||||
return @meta.sandstorm.grain.grainTitle |
||||
appIconUrl: -> |
||||
return @meta.sandstorm.grain.appIconUrl |
||||
descriptor: -> |
||||
return @meta.sandstorm.grain.descriptor |
||||
window.sandstormOembed = (e) -> |
||||
e = e or window.event |
||||
src = e.target or e.srcElement |
||||
token = src.getAttribute "data-token" |
||||
descriptor = src.getAttribute "data-descriptor" |
||||
Meteor.call "sandstormOffer", token, descriptor |
||||
@ -0,0 +1,25 @@ |
||||
Template.oembedSandstormGrain.helpers({ |
||||
token() { |
||||
return this.meta.sandstorm.grain.token; |
||||
}, |
||||
appTitle() { |
||||
return this.meta.sandstorm.grain.appTitle.defaultText; |
||||
}, |
||||
grainTitle() { |
||||
return this.meta.sandstorm.grain.grainTitle; |
||||
}, |
||||
appIconUrl() { |
||||
return this.meta.sandstorm.grain.appIconUrl; |
||||
}, |
||||
descriptor() { |
||||
return this.meta.sandstorm.grain.descriptor; |
||||
} |
||||
}); |
||||
|
||||
window.sandstormOembed = function(e) { |
||||
e = e || window.event; |
||||
const src = e.target || e.srcElement; |
||||
const token = src.getAttribute('data-token'); |
||||
const descriptor = src.getAttribute('data-descriptor'); |
||||
return Meteor.call('sandstormOffer', token, descriptor); |
||||
}; |
||||
@ -1,57 +0,0 @@ |
||||
getTitle = (self) -> |
||||
if not self.meta? |
||||
return |
||||
|
||||
return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle |
||||
|
||||
getDescription = (self) -> |
||||
if not self.meta? |
||||
return |
||||
|
||||
description = self.meta.ogDescription or self.meta.twitterDescription or self.meta.description |
||||
if not description? |
||||
return |
||||
|
||||
return _.unescape description.replace /(^[“\s]*)|([”\s]*$)/g, '' |
||||
|
||||
|
||||
Template.oembedUrlWidget.helpers |
||||
description: -> |
||||
description = getDescription this |
||||
return Blaze._escape(description) if _.isString description |
||||
|
||||
title: -> |
||||
title = getTitle this |
||||
return Blaze._escape(title) if _.isString title |
||||
|
||||
target: -> |
||||
if not this.parsedUrl?.host || !document?.location?.host || this.parsedUrl.host isnt document.location.host |
||||
return '_blank' |
||||
|
||||
image: -> |
||||
if not this.meta? |
||||
return |
||||
|
||||
decodedOgImage = @meta.ogImage?.replace?(/&/g, '&') |
||||
|
||||
url = this.meta.msapplicationTileImage or decodedOgImage or this.meta.twitterImage |
||||
|
||||
if not url? |
||||
return |
||||
|
||||
if url.indexOf('//') is 0 |
||||
url = "#{this.parsedUrl.protocol}#{url}" |
||||
|
||||
else if url.indexOf('/') is 0 and this.parsedUrl?.host? |
||||
url = "#{this.parsedUrl.protocol}//#{this.parsedUrl.host}#{url}" |
||||
|
||||
return url |
||||
|
||||
show: -> |
||||
return getDescription(this)? or getTitle(this)? |
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,67 @@ |
||||
const getTitle = function(self) { |
||||
if (self.meta == null) { |
||||
return; |
||||
} |
||||
return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle; |
||||
}; |
||||
|
||||
const getDescription = function(self) { |
||||
if (self.meta == null) { |
||||
return; |
||||
} |
||||
const description = self.meta.ogDescription || self.meta.twitterDescription || self.meta.description; |
||||
if (description == null) { |
||||
return; |
||||
} |
||||
return _.unescape(description.replace(/(^[“\s]*)|([”\s]*$)/g, '')); |
||||
}; |
||||
|
||||
Template.oembedUrlWidget.helpers({ |
||||
description() { |
||||
const description = getDescription(this); |
||||
if (_.isString(description)) { |
||||
return Blaze._escape(description); |
||||
} |
||||
}, |
||||
title() { |
||||
const title = getTitle(this); |
||||
if (_.isString(title)) { |
||||
return Blaze._escape(title); |
||||
} |
||||
}, |
||||
target() { |
||||
if (!(this.parsedUrl && this.parsedUrl.host) || !(document && document.location && document.location.host) || (this.parsedUrl && this.parsedUrl.host !== document.location.host)) { |
||||
return '_blank'; |
||||
} |
||||
}, |
||||
image() { |
||||
if (this.meta == null) { |
||||
return; |
||||
} |
||||
let decodedOgImage; |
||||
if (this.meta.ogImage && this.meta.ogImage.replace) { |
||||
decodedOgImage = this.meta.ogImage.replace(/&/g, '&'); |
||||
} |
||||
let url = this.meta.msapplicationTileImage || decodedOgImage || this.meta.twitterImage; |
||||
if (url == null) { |
||||
return; |
||||
} |
||||
if (url.indexOf('//') === 0) { |
||||
url = `${ this.parsedUrl.protocol }${ url }`; |
||||
} else if (url.indexOf('/') === 0 && (this.parsedUrl && this.parsedUrl.host)) { |
||||
url = `${ this.parsedUrl.protocol }//${ this.parsedUrl.host }${ url }`; |
||||
} |
||||
return url; |
||||
}, |
||||
show() { |
||||
return (getDescription(this) != null) || (getTitle(this) != null); |
||||
}, |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
if (this.collapsed != null) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault) === true; |
||||
} |
||||
} |
||||
}); |
||||
@ -1,22 +0,0 @@ |
||||
getTitle = (self) -> |
||||
if not self.meta? |
||||
return |
||||
|
||||
return self.meta.ogTitle or self.meta.twitterTitle or self.meta.title or self.meta.pageTitle |
||||
|
||||
|
||||
Template.oembedVideoWidget.helpers |
||||
url: -> |
||||
return @meta?.twitterPlayerStream or @url |
||||
|
||||
contentType: -> |
||||
return @meta?.twitterPlayerStreamContentType or @headers?.contentType |
||||
|
||||
title: -> |
||||
return getTitle @ |
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,35 @@ |
||||
const getTitle = function(self) { |
||||
if (self.meta == null) { |
||||
return; |
||||
} |
||||
return self.meta.ogTitle || self.meta.twitterTitle || self.meta.title || self.meta.pageTitle; |
||||
}; |
||||
|
||||
Template.oembedVideoWidget.helpers({ |
||||
url() { |
||||
if (this.meta && this.meta.twitterPlayerStream) { |
||||
return this.meta.twitterPlayerStream; |
||||
} else if (this.url) { |
||||
return this.url; |
||||
} |
||||
}, |
||||
contentType() { |
||||
if (this.meta && this.meta.twitterPlayerStreamContentType) { |
||||
return this.meta.twitterPlayerStreamContentType; |
||||
} else if (this.headers && this.headers.contentType) { |
||||
return this.headers.contentType; |
||||
} |
||||
}, |
||||
title() { |
||||
return getTitle(this); |
||||
}, |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
if (this.collapsed) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault) === true; |
||||
} |
||||
} |
||||
|
||||
}); |
||||
@ -1,7 +0,0 @@ |
||||
Template.oembedYoutubeWidget.helpers |
||||
|
||||
collapsed: -> |
||||
if this.collapsed? |
||||
return this.collapsed |
||||
else |
||||
return Meteor.user()?.settings?.preferences?.collapseMediaByDefault is true |
||||
@ -0,0 +1,10 @@ |
||||
Template.oembedYoutubeWidget.helpers({ |
||||
collapsed() { |
||||
const user = Meteor.user(); |
||||
if (this.collapsed) { |
||||
return this.collapsed; |
||||
} else { |
||||
return (user && user.settings && user.settings.preferences && user.settings.preferences.collapseMediaByDefault) === true; |
||||
} |
||||
} |
||||
}); |
||||
@ -1,30 +0,0 @@ |
||||
RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base |
||||
constructor: -> |
||||
super('oembed_cache') |
||||
@tryEnsureIndex { 'updatedAt': 1 } |
||||
|
||||
|
||||
# FIND ONE |
||||
findOneById: (_id, options) -> |
||||
query = |
||||
_id: _id |
||||
|
||||
return @findOne query, options |
||||
|
||||
|
||||
# INSERT |
||||
createWithIdAndData: (_id, data) -> |
||||
record = |
||||
_id: _id |
||||
data: data |
||||
updatedAt: new Date |
||||
|
||||
record._id = @insert record |
||||
return record |
||||
|
||||
# REMOVE |
||||
removeAfterDate: (date) -> |
||||
query = |
||||
updatedAt: |
||||
$lte: date |
||||
@remove query |
||||
@ -0,0 +1,38 @@ |
||||
|
||||
RocketChat.models.OEmbedCache = new class extends RocketChat.models._Base { |
||||
constructor() { |
||||
super('oembed_cache'); |
||||
this.tryEnsureIndex({ 'updatedAt': 1 }); |
||||
} |
||||
|
||||
//FIND ONE
|
||||
findOneById(_id, options) { |
||||
const query = { |
||||
_id |
||||
}; |
||||
return this.findOne(query, options); |
||||
} |
||||
|
||||
//INSERT
|
||||
createWithIdAndData(_id, data) { |
||||
const record = { |
||||
_id, |
||||
data, |
||||
updatedAt: new Date |
||||
}; |
||||
record._id = this.insert(record); |
||||
return record; |
||||
} |
||||
|
||||
//REMOVE
|
||||
removeAfterDate(date) { |
||||
const query = { |
||||
updatedAt: { |
||||
$lte: date |
||||
} |
||||
}; |
||||
return this.remove(query); |
||||
} |
||||
}; |
||||
|
||||
|
||||
@ -1,84 +0,0 @@ |
||||
URL = Npm.require('url') |
||||
QueryString = Npm.require('querystring') |
||||
|
||||
class Providers |
||||
providers: [] |
||||
|
||||
@getConsumerUrl: (provider, url) -> |
||||
urlObj = URL.parse provider.endPoint, true |
||||
urlObj.query['url'] = url |
||||
delete urlObj.search |
||||
return URL.format urlObj |
||||
|
||||
registerProvider: (provider) -> |
||||
this.providers.push(provider) |
||||
|
||||
getProviders: () -> |
||||
return this.providers |
||||
|
||||
getProviderForUrl: (url) -> |
||||
return _.find this.providers, (provider) -> |
||||
candidate = _.find provider.urls, (re) -> |
||||
return re.test url |
||||
return candidate? |
||||
|
||||
providers = new Providers() |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://soundcloud.com/\\S+')] |
||||
endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150' |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')] |
||||
endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200' |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')] |
||||
endPoint: 'https://www.youtube.com/oembed?maxheight=200' |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')] |
||||
endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150' |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')] |
||||
endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200' |
||||
providers.registerProvider |
||||
urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')] |
||||
endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200' |
||||
|
||||
RocketChat.oembed = {} |
||||
RocketChat.oembed.providers = providers |
||||
|
||||
RocketChat.callbacks.add 'oembed:beforeGetUrlContent', (data) -> |
||||
if data.parsedUrl? |
||||
url = URL.format data.parsedUrl |
||||
provider = providers.getProviderForUrl url |
||||
if provider? |
||||
consumerUrl = Providers.getConsumerUrl provider, url |
||||
consumerUrl = URL.parse consumerUrl, true |
||||
_.extend data.parsedUrl, consumerUrl |
||||
data.urlObj.port = consumerUrl.port |
||||
data.urlObj.hostname = consumerUrl.hostname |
||||
data.urlObj.pathname = consumerUrl.pathname |
||||
data.urlObj.query = consumerUrl.query |
||||
delete data.urlObj.search |
||||
delete data.urlObj.host |
||||
|
||||
return data |
||||
, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before' |
||||
|
||||
RocketChat.callbacks.add 'oembed:afterParseContent', (data) -> |
||||
if data.parsedUrl?.query? |
||||
queryString = data.parsedUrl.query |
||||
if _.isString data.parsedUrl.query |
||||
queryString = QueryString.parse data.parsedUrl.query |
||||
if queryString.url? |
||||
url = queryString.url |
||||
provider = providers.getProviderForUrl url |
||||
if provider? |
||||
if data.content?.body? |
||||
try |
||||
metas = JSON.parse data.content.body; |
||||
_.each metas, (value, key) -> |
||||
if _.isString value |
||||
data.meta[changeCase.camelCase('oembed_' + key)] = value |
||||
data.meta['oembedUrl'] = url |
||||
|
||||
return data |
||||
, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after' |
||||
@ -0,0 +1,120 @@ |
||||
/*globals changeCase */ |
||||
|
||||
|
||||
const URL = Npm.require('url'); |
||||
|
||||
const QueryString = Npm.require('querystring'); |
||||
|
||||
class Providers { |
||||
constructor() { |
||||
this.providers = []; |
||||
} |
||||
|
||||
static getConsumerUrl(provider, url) { |
||||
const urlObj = URL.parse(provider.endPoint, true); |
||||
urlObj.query['url'] = url; |
||||
delete urlObj.search; |
||||
return URL.format(urlObj); |
||||
} |
||||
|
||||
registerProvider(provider) { |
||||
return this.providers.push(provider); |
||||
} |
||||
|
||||
getProviders() { |
||||
return this.providers; |
||||
} |
||||
|
||||
getProviderForUrl(url) { |
||||
return _.find(this.providers, function(provider) { |
||||
const candidate = _.find(provider.urls, function(re) { |
||||
return re.test(url); |
||||
}); |
||||
return candidate != null; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
const providers = new Providers(); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://soundcloud.com/\\S+')], |
||||
endPoint: 'https://soundcloud.com/oembed?format=json&maxheight=150' |
||||
}); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://vimeo.com/[^/]+'), new RegExp('https?://vimeo.com/channels/[^/]+/[^/]+'), new RegExp('https://vimeo.com/groups/[^/]+/videos/[^/]+')], |
||||
endPoint: 'https://vimeo.com/api/oembed.json?maxheight=200' |
||||
}); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://www.youtube.com/\\S+'), new RegExp('https?://youtu.be/\\S+')], |
||||
endPoint: 'https://www.youtube.com/oembed?maxheight=200' |
||||
}); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://www.rdio.com/\\S+'), new RegExp('https?://rd.io/\\S+')], |
||||
endPoint: 'https://www.rdio.com/api/oembed/?format=json&maxheight=150' |
||||
}); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://www.slideshare.net/[^/]+/[^/]+')], |
||||
endPoint: 'https://www.slideshare.net/api/oembed/2?format=json&maxheight=200' |
||||
}); |
||||
|
||||
providers.registerProvider({ |
||||
urls: [new RegExp('https?://www.dailymotion.com/video/\\S+')], |
||||
endPoint: 'https://www.dailymotion.com/services/oembed?maxheight=200' |
||||
}); |
||||
|
||||
RocketChat.oembed = {}; |
||||
|
||||
RocketChat.oembed.providers = providers; |
||||
|
||||
RocketChat.callbacks.add('oembed:beforeGetUrlContent', function(data) { |
||||
if (data.parsedUrl != null) { |
||||
const url = URL.format(data.parsedUrl); |
||||
const provider = providers.getProviderForUrl(url); |
||||
if (provider != null) { |
||||
let consumerUrl = Providers.getConsumerUrl(provider, url); |
||||
consumerUrl = URL.parse(consumerUrl, true); |
||||
_.extend(data.parsedUrl, consumerUrl); |
||||
data.urlObj.port = consumerUrl.port; |
||||
data.urlObj.hostname = consumerUrl.hostname; |
||||
data.urlObj.pathname = consumerUrl.pathname; |
||||
data.urlObj.query = consumerUrl.query; |
||||
delete data.urlObj.search; |
||||
delete data.urlObj.host; |
||||
} |
||||
} |
||||
return data; |
||||
}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-before'); |
||||
|
||||
RocketChat.callbacks.add('oembed:afterParseContent', function(data) { |
||||
if (data.parsedUrl && data.parsedUrl.query) { |
||||
let queryString = data.parsedUrl.query; |
||||
if (_.isString(data.parsedUrl.query)) { |
||||
queryString = QueryString.parse(data.parsedUrl.query); |
||||
} |
||||
if (queryString.url != null) { |
||||
const url = queryString.url; |
||||
const provider = providers.getProviderForUrl(url); |
||||
if (provider != null) { |
||||
if (data.content && data.content.body) { |
||||
try { |
||||
const metas = JSON.parse(data.content.body); |
||||
_.each(metas, function(value, key) { |
||||
if (_.isString(value)) { |
||||
return data.meta[changeCase.camelCase(`oembed_${ key }`)] = value; |
||||
} |
||||
}); |
||||
data.meta['oembedUrl'] = url; |
||||
} catch (error) { |
||||
console.log(error); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return data; |
||||
}, RocketChat.callbacks.priority.MEDIUM, 'oembed-providers-after'); |
||||
@ -1,256 +0,0 @@ |
||||
URL = Npm.require('url') |
||||
querystring = Npm.require('querystring') |
||||
request = HTTPInternals.NpmModules.request.module |
||||
iconv = Npm.require('iconv-lite') |
||||
ipRangeCheck = Npm.require('ip-range-check') |
||||
he = Npm.require('he') |
||||
jschardet = Npm.require('jschardet') |
||||
|
||||
OEmbed = {} |
||||
|
||||
# Detect encoding |
||||
# Priority: |
||||
# Detected == HTTP Header > Detected == HTML meta > HTTP Header > HTML meta > Detected > Default (utf-8) |
||||
# See also: https://www.w3.org/International/questions/qa-html-encoding-declarations.en#quickanswer |
||||
getCharset = (contentType, body) -> |
||||
contentType = contentType || '' |
||||
binary = body.toString('binary') |
||||
|
||||
detected = jschardet.detect(binary) |
||||
if detected.confidence > 0.8 |
||||
detectedCharset = detected.encoding.toLowerCase() |
||||
|
||||
m1 = contentType.match(/charset=([\w\-]+)/i) |
||||
if m1 |
||||
httpHeaderCharset = m1[1].toLowerCase() |
||||
|
||||
m2 = binary.match(/<meta\b[^>]*charset=["']?([\w\-]+)/i) |
||||
if m2 |
||||
htmlMetaCharset = m2[1].toLowerCase() |
||||
|
||||
if detectedCharset |
||||
if detectedCharset == httpHeaderCharset |
||||
result = httpHeaderCharset |
||||
else if detectedCharset == htmlMetaCharset |
||||
result = htmlMetaCharset |
||||
|
||||
unless result |
||||
result = httpHeaderCharset || htmlMetaCharset || detectedCharset |
||||
|
||||
return result || 'utf-8' |
||||
|
||||
toUtf8 = (contentType, body) -> |
||||
return iconv.decode(body, getCharset(contentType, body)) |
||||
|
||||
getUrlContent = (urlObj, redirectCount = 5, callback) -> |
||||
if _.isString(urlObj) |
||||
urlObj = URL.parse urlObj |
||||
|
||||
parsedUrl = _.pick urlObj, ['host', 'hash', 'pathname', 'protocol', 'port', 'query', 'search', 'hostname'] |
||||
|
||||
ignoredHosts = RocketChat.settings.get('API_EmbedIgnoredHosts').replace(/\s/g, '').split(',') or [] |
||||
if parsedUrl.hostname in ignoredHosts or ipRangeCheck(parsedUrl.hostname, ignoredHosts) |
||||
return callback() |
||||
|
||||
safePorts = RocketChat.settings.get('API_EmbedSafePorts').replace(/\s/g, '').split(',') or [] |
||||
if parsedUrl.port and safePorts.length > 0 and parsedUrl.port not in safePorts |
||||
return callback() |
||||
|
||||
data = RocketChat.callbacks.run 'oembed:beforeGetUrlContent', |
||||
urlObj: urlObj |
||||
parsedUrl: parsedUrl |
||||
|
||||
if data.attachments? |
||||
return callback null, data |
||||
|
||||
url = URL.format data.urlObj |
||||
opts = |
||||
url: url |
||||
strictSSL: !RocketChat.settings.get 'Allow_Invalid_SelfSigned_Certs' |
||||
gzip: true |
||||
maxRedirects: redirectCount |
||||
headers: |
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36' |
||||
|
||||
headers = null |
||||
statusCode = null |
||||
error = null |
||||
chunks = [] |
||||
chunksTotalLength = 0 |
||||
|
||||
stream = request opts |
||||
stream.on 'response', (response) -> |
||||
statusCode = response.statusCode |
||||
headers = response.headers |
||||
if response.statusCode isnt 200 |
||||
return stream.abort() |
||||
|
||||
stream.on 'data', (chunk) -> |
||||
chunks.push chunk |
||||
chunksTotalLength += chunk.length |
||||
if chunksTotalLength > 250000 |
||||
stream.abort() |
||||
|
||||
stream.on 'end', Meteor.bindEnvironment -> |
||||
if error? |
||||
return callback null, { |
||||
error: error |
||||
parsedUrl: parsedUrl |
||||
} |
||||
|
||||
buffer = Buffer.concat(chunks) |
||||
|
||||
callback null, { |
||||
headers: headers |
||||
body: toUtf8(headers['content-type'], buffer) |
||||
parsedUrl: parsedUrl |
||||
statusCode: statusCode |
||||
} |
||||
|
||||
stream.on 'error', (err) -> |
||||
error = err |
||||
|
||||
OEmbed.getUrlMeta = (url, withFragment) -> |
||||
getUrlContentSync = Meteor.wrapAsync getUrlContent |
||||
|
||||
urlObj = URL.parse url |
||||
|
||||
if withFragment? |
||||
queryStringObj = querystring.parse urlObj.query |
||||
queryStringObj._escaped_fragment_ = '' |
||||
urlObj.query = querystring.stringify queryStringObj |
||||
|
||||
path = urlObj.pathname |
||||
if urlObj.query? |
||||
path += '?' + urlObj.query |
||||
|
||||
urlObj.path = path |
||||
|
||||
content = getUrlContentSync urlObj, 5 |
||||
if !content |
||||
return |
||||
|
||||
if content.attachments? |
||||
return content |
||||
|
||||
metas = undefined |
||||
|
||||
if content?.body? |
||||
metas = {} |
||||
content.body.replace /<title[^>]*>([^<]*)<\/title>/gmi, (meta, title) -> |
||||
metas.pageTitle ?= he.unescape title |
||||
|
||||
content.body.replace /<meta[^>]*(?:name|property)=[']([^']*)['][^>]*\scontent=[']([^']*)['][^>]*>/gmi, (meta, name, value) -> |
||||
metas[changeCase.camelCase(name)] ?= he.unescape value |
||||
|
||||
content.body.replace /<meta[^>]*(?:name|property)=["]([^"]*)["][^>]*\scontent=["]([^"]*)["][^>]*>/gmi, (meta, name, value) -> |
||||
metas[changeCase.camelCase(name)] ?= he.unescape value |
||||
|
||||
content.body.replace /<meta[^>]*\scontent=[']([^']*)['][^>]*(?:name|property)=[']([^']*)['][^>]*>/gmi, (meta, value, name) -> |
||||
metas[changeCase.camelCase(name)] ?= he.unescape value |
||||
|
||||
content.body.replace /<meta[^>]*\scontent=["]([^"]*)["][^>]*(?:name|property)=["]([^"]*)["][^>]*>/gmi, (meta, value, name) -> |
||||
metas[changeCase.camelCase(name)] ?= he.unescape value |
||||
|
||||
|
||||
if metas.fragment is '!' and not withFragment? |
||||
return OEmbed.getUrlMeta url, true |
||||
|
||||
headers = undefined |
||||
|
||||
if content?.headers? |
||||
headers = {} |
||||
for header, value of content.headers |
||||
headers[changeCase.camelCase(header)] = value |
||||
|
||||
if content?.statusCode isnt 200 |
||||
return data |
||||
|
||||
data = RocketChat.callbacks.run 'oembed:afterParseContent', |
||||
meta: metas |
||||
headers: headers |
||||
parsedUrl: content.parsedUrl |
||||
content: content |
||||
|
||||
return data |
||||
|
||||
OEmbed.getUrlMetaWithCache = (url, withFragment) -> |
||||
cache = RocketChat.models.OEmbedCache.findOneById url |
||||
if cache? |
||||
return cache.data |
||||
|
||||
data = OEmbed.getUrlMeta url, withFragment |
||||
|
||||
if data? |
||||
try |
||||
RocketChat.models.OEmbedCache.createWithIdAndData url, data |
||||
catch e |
||||
console.error 'OEmbed duplicated record', url |
||||
|
||||
return data |
||||
|
||||
return |
||||
|
||||
getRelevantHeaders = (headersObj) -> |
||||
headers = {} |
||||
for key, value of headersObj |
||||
if key.toLowerCase() in ['contenttype', 'contentlength'] and value?.trim() isnt '' |
||||
headers[key] = value |
||||
|
||||
if Object.keys(headers).length > 0 |
||||
return headers |
||||
return |
||||
|
||||
getRelevantMetaTags = (metaObj) -> |
||||
tags = {} |
||||
for key, value of metaObj |
||||
if /^(og|fb|twitter|oembed|msapplication).+|description|title|pageTitle$/.test(key.toLowerCase()) and value?.trim() isnt '' |
||||
tags[key] = value |
||||
|
||||
if Object.keys(tags).length > 0 |
||||
return tags |
||||
return |
||||
|
||||
OEmbed.rocketUrlParser = (message) -> |
||||
if Array.isArray message.urls |
||||
attachments = [] |
||||
changed = false |
||||
message.urls.forEach (item) -> |
||||
if item.ignoreParse is true then return |
||||
if item.url.startsWith "grain://" |
||||
changed = true |
||||
item.meta = |
||||
sandstorm: |
||||
grain: item.sandstormViewInfo |
||||
return |
||||
|
||||
if not /^https?:\/\//i.test item.url then return |
||||
|
||||
data = OEmbed.getUrlMetaWithCache item.url |
||||
|
||||
if data? |
||||
if data.attachments |
||||
attachments = _.union attachments, data.attachments |
||||
else |
||||
if data.meta? |
||||
item.meta = getRelevantMetaTags data.meta |
||||
|
||||
if data.headers? |
||||
item.headers = getRelevantHeaders data.headers |
||||
|
||||
item.parsedUrl = data.parsedUrl |
||||
changed = true |
||||
|
||||
if attachments.length |
||||
RocketChat.models.Messages.setMessageAttachments message._id, attachments |
||||
|
||||
if changed is true |
||||
RocketChat.models.Messages.setUrlsById message._id, message.urls |
||||
|
||||
return message |
||||
|
||||
RocketChat.settings.get 'API_Embed', (key, value) -> |
||||
if value |
||||
RocketChat.callbacks.add 'afterSaveMessage', OEmbed.rocketUrlParser, RocketChat.callbacks.priority.LOW, 'API_Embed' |
||||
else |
||||
RocketChat.callbacks.remove 'afterSaveMessage', 'API_Embed' |
||||
Loading…
Reference in new issue