From e83fa3d058c65f87a4c158dc0a88f5c0f4b15ca5 Mon Sep 17 00:00:00 2001 From: Aza Raskin Date: Thu, 20 Oct 2016 17:49:28 -0700 Subject: [PATCH 1/3] Listens for links, chat complete a video if that's where the link points --- builtins/core.other.js | 45 ++++++++++++++++++++++++++++++++++ lib/Feature.js | 2 ++ lib/LinkListener.js | 55 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 lib/LinkListener.js diff --git a/builtins/core.other.js b/builtins/core.other.js index f47b6a4..b5f3c28 100644 --- a/builtins/core.other.js +++ b/builtins/core.other.js @@ -71,6 +71,51 @@ feature.listen({ } }) +// Media Picker +// Paste in a URL and if we understand it, show a chat complete to embed +// the URL + +feature.listen({ + to: 'link', + on: function({url}) { + + // Savedeo seems to 500 error with https urls + const videoURL = url.replace(/https/, 'http') + + return fetch( 'https://savedeo.p.mashape.com/download', { + method:'POST', + body: `url=${videoURL}`, + headers: { + 'X-Mashape-Key':'249RMQR2QbmshxAArl8lInJnrDM0p1Tgt3UjsnXmcTbcTqqMLp', + 'Content-Type':'application/x-www-form-urlencoded' + }}) + .then( response => { return response.json() }) + .then( videoData => { + + var mp4s = videoData.formats.filter( v => v.ext == 'mp4' ) + if( mp4s.length == 0 ) return + + const mp4 = mp4s[0] + // Instagram videos don't have a height/width in their JSON + // Could parse it out from the format field, but being lazy. + let size = { height: mp4.height || 640, width: mp4.width || 640 } + + // The link format for YouTube doesn't include an .mp4 at the end, which + // means Other Chat doesn't know how to display it. + // + // @tony suggests we use {stageMessage: attachments[]} in the LinkListener + // but currently that doesn't seem to be getting called. + + return { + chatCompletions: [ { media: { type:'video', url: mp4.url, size: size } } ] + } + + }) + + } +}) + + // Emoji tokens const emoji = getEmoji() feature.listen({ diff --git a/lib/Feature.js b/lib/Feature.js index d1d5b3b..21c226f 100644 --- a/lib/Feature.js +++ b/lib/Feature.js @@ -4,6 +4,7 @@ import CommandListener from './CommandListener' import MentionListener from './MentionListener' import TokenListener from './TokenListener' import WordListener from './WordListener' +import LinkListener from './LinkListener' import {environment} from './environment' import {userAgent} from './userAgent' @@ -75,6 +76,7 @@ class Feature { if (to.commands) this._listeners.push(new CommandListener({commands: to.commands, on})) if (to.words) this._listeners.push(new WordListener({words: to.words, on})) if (to.tokens) this._listeners.push(new TokenListener({tokens: to.tokens, on})) + if (to === 'link') this._listeners.push(new LinkListener({on})) if (!this._userAgent) { this._userAgent = userAgent this._userAgent.on(Events.ACTIVATE_CHAT_COMPLETE_RESULT, event => { diff --git a/lib/LinkListener.js b/lib/LinkListener.js new file mode 100644 index 0000000..d189b3c --- /dev/null +++ b/lib/LinkListener.js @@ -0,0 +1,55 @@ +import Listener from './Listener' + +/** + * Listens for URLS entered by the user. Matches to end of string only. + * + * @inheritdoc + */ + +const endingURLRegexp = /((http[s]?|ftp):\/)?\/?([^:\/\s]+)((\/\w+)*\/)([\w\-\.]+[^#?\s]+)(.*)?(#[\w\-]+)?$/ + +class LinkListener extends Listener { + /** + * @callback LinkListener#onCallback + * @return {?(Promise|Listener~Result)} - The resulting action that the + * user agent should take in response to this ;oml. + */ + + /** + * @param {LinkListener#onCallback} on - Called when one a link is found. + */ + constructor({on}) { + super() + this._on = on + } + + onActivateChatCompleteResult( action, result, message ) { + + // TODO: This does not seem to get called? + // That is, adding a console.log('boom') will not print 'boom' + // and adding a return doesn't effect behavior. + + if (action !== 'default' || !result.text) return null + return {stagedMessage: {text: result.text}} + } + + onSetStagedMessage( message ) { + + // Get text typed so far + const {text} = message + + var match = text.match( endingURLRegexp ) + var linkURL = match ? match[0] : null + + if( linkURL && !linkURL.match(/ /)) { + const result = this._on({url: linkURL}) + return result instanceof Promise ? result : Promise.resolve( result ) + } + + return Promise.resolve( {chatCompletions: []} ) + + } + +} + +export default LinkListener From 386818a171ca2f858f529f5fb7e07d170450a80a Mon Sep 17 00:00:00 2001 From: Aza Raskin Date: Sat, 22 Oct 2016 12:48:35 -0700 Subject: [PATCH 2/3] Added tests, fixed styles --- builtins/__tests__/core.spec.js | 18 +++++++++++ builtins/core.other.js | 57 +++++++++++++++++++-------------- lib/Feature.js | 4 ++- lib/LinkListener.js | 19 ++++------- 4 files changed, 61 insertions(+), 37 deletions(-) diff --git a/builtins/__tests__/core.spec.js b/builtins/__tests__/core.spec.js index 18c2a5a..1063013 100644 --- a/builtins/__tests__/core.spec.js +++ b/builtins/__tests__/core.spec.js @@ -296,6 +296,24 @@ describe('core', () => { }) }) + describe('media links', () => { + it('recognize vimeo link', done => { + core.userAgent.emit('SET_STAGED_MESSAGE', {message: {text: 'https://vimeo.com/channels/staffpicks/182359265'}, tag: 123}) + setImmediate(() => { + const result = [{media: {type: 'video', url: 'https://fpdl.vimeocdn.com/vimeo-prod-skyfire-std-us/01/1471/7/182359265/598591373.mp4?token=580bd041_0x7c0f833a1498c994291875b53acdb7a682c60bb3', size: {height: 1080, width: 1920}}}] + expect(core.userAgent.emit).toHaveBeenCalledWith('SET_CHAT_COMPLETE_RESULTS', {results: result, replyTag: 123}) + }) + }) + + it('recognize instagram link', done => { + core.userAgent.emit('SET_STAGED_MESSAGE', {message: {text: 'https://www.instagram.com/p/5XcsWuERgS/?taken-by=azaaza'}, tag: 123}) + setImmediate(() => { + const result = [{media: {type: "video", url: 'https://scontent.cdninstagram.com/t50.2886-16/11773248_1618146288461442_1339867767_n.mp4', size: {height: 640, width: 640}}}] + expect(core.userAgent.emit).toHaveBeenCalledWith('SET_CHAT_COMPLETE_RESULTS', {results: result, replyTag: 123}) + }) + }) + }) + describe('emoji tokens', () => { it('recognizes smile', done => { core.userAgent.emit('SET_STAGED_MESSAGE', {message: {text: 'hello :smile:'}, tag: 123}) diff --git a/builtins/core.other.js b/builtins/core.other.js index b5f3c28..bdddc2e 100644 --- a/builtins/core.other.js +++ b/builtins/core.other.js @@ -77,45 +77,54 @@ feature.listen({ feature.listen({ to: 'link', - on: function({url}) { - + on({url}) { // Savedeo seems to 500 error with https urls const videoURL = url.replace(/https/, 'http') - return fetch( 'https://savedeo.p.mashape.com/download', { - method:'POST', + return fetch('https://savedeo.p.mashape.com/download', { + method: 'POST', body: `url=${videoURL}`, headers: { - 'X-Mashape-Key':'249RMQR2QbmshxAArl8lInJnrDM0p1Tgt3UjsnXmcTbcTqqMLp', - 'Content-Type':'application/x-www-form-urlencoded' - }}) - .then( response => { return response.json() }) - .then( videoData => { + 'X-Mashape-Key': '249RMQR2QbmshxAArl8lInJnrDM0p1Tgt3UjsnXmcTbcTqqMLp', + 'Content-Type': 'application/x-www-form-urlencoded' + }}) + .then(response => response.json()) + .then(mediaData => { + const mp4s = mediaData.formats.filter(m => m.ext === 'mp4') + const mp3s = mediaData.formats.filter(m => m.ext === 'mp3') - var mp4s = videoData.formats.filter( v => v.ext == 'mp4' ) - if( mp4s.length == 0 ) return + if (mp4s.length !== 0) { + const mp4 = mp4s[0] + // Instagram videos don't have a height/width in their JSON + // Could parse it out from the format field, but being lazy. + const size = {height: mp4.height || 640, width: mp4.width || 640} - const mp4 = mp4s[0] - // Instagram videos don't have a height/width in their JSON - // Could parse it out from the format field, but being lazy. - let size = { height: mp4.height || 640, width: mp4.width || 640 } + // The link format for YouTube doesn't include an .mp4 at the end, which + // means Other Chat doesn't know how to display it. + // + // @tony suggests we use {stageMessage: attachments[]} in the LinkListener + // but currently that doesn't seem to be getting called. - // The link format for YouTube doesn't include an .mp4 at the end, which - // means Other Chat doesn't know how to display it. - // - // @tony suggests we use {stageMessage: attachments[]} in the LinkListener - // but currently that doesn't seem to be getting called. + console.log( JSON.stringify([{media: {type: 'video', url: mp4.url, size}}]) ) - return { - chatCompletions: [ { media: { type:'video', url: mp4.url, size: size } } ] + return { + chatCompletions: [{media: {type: 'video', url: mp4.url, size}}] + } } - }) + else if (mp3s.length !== 0) { + return { + chatCompletions: [ + {media: {type: 'image', url: mediaData.thumbnail, size: {height: 100, width: 100}}}, + {media: {type: 'audio', url: mp3s[0].url}} + ] + } + } + }) } }) - // Emoji tokens const emoji = getEmoji() feature.listen({ diff --git a/lib/Feature.js b/lib/Feature.js index 21c226f..786285a 100644 --- a/lib/Feature.js +++ b/lib/Feature.js @@ -1,10 +1,12 @@ import * as Events from './Events' import Chatternet from './Chatternet' + import CommandListener from './CommandListener' +import LinkListener from './LinkListener' import MentionListener from './MentionListener' import TokenListener from './TokenListener' import WordListener from './WordListener' -import LinkListener from './LinkListener' + import {environment} from './environment' import {userAgent} from './userAgent' diff --git a/lib/LinkListener.js b/lib/LinkListener.js index d189b3c..18747f8 100644 --- a/lib/LinkListener.js +++ b/lib/LinkListener.js @@ -23,33 +23,28 @@ class LinkListener extends Listener { this._on = on } - onActivateChatCompleteResult( action, result, message ) { - + onActivateChatCompleteResult(action, result, message) { // TODO: This does not seem to get called? // That is, adding a console.log('boom') will not print 'boom' // and adding a return doesn't effect behavior. - if (action !== 'default' || !result.text) return null return {stagedMessage: {text: result.text}} } - onSetStagedMessage( message ) { - + onSetStagedMessage(message) { // Get text typed so far const {text} = message - var match = text.match( endingURLRegexp ) - var linkURL = match ? match[0] : null + const match = text.match(endingURLRegexp) + const linkURL = match ? match[0] : null - if( linkURL && !linkURL.match(/ /)) { + if (linkURL && !linkURL.match(/ /)) { const result = this._on({url: linkURL}) - return result instanceof Promise ? result : Promise.resolve( result ) + return result instanceof Promise ? result : Promise.resolve(result) } - return Promise.resolve( {chatCompletions: []} ) - + return Promise.resolve({chatCompletions: []}) } - } export default LinkListener From 84f6437cbb592c5debb4894695a682d0e639f204 Mon Sep 17 00:00:00 2001 From: Aza Raskin Date: Sat, 22 Oct 2016 13:59:41 -0700 Subject: [PATCH 3/3] Removed console.log --- builtins/core.other.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtins/core.other.js b/builtins/core.other.js index afe88e7..addc79a 100644 --- a/builtins/core.other.js +++ b/builtins/core.other.js @@ -116,8 +116,6 @@ feature.listen({ // @tony suggests we use {stageMessage: attachments[]} in the LinkListener // but currently that doesn't seem to be getting called. - console.log( JSON.stringify([{media: {type: 'video', url: mp4.url, size}}]) ) - return { chatCompletions: [{media: {type: 'video', url: mp4.url, size}}] } @@ -132,6 +130,7 @@ feature.listen({ } } + return {chatCompletions: []} }) } })