Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Listens for links, chat complete a video if that's where the link points #91

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions builtins/__tests__/core.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,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})
Expand Down
53 changes: 53 additions & 0 deletions builtins/core.other.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,59 @@ feature.listen({
}
})

// Media Picker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice!

Travis is sad about the code formatting: https://travis-ci.com/other-xyz/other.js/jobs/54105617

Would you mind please fixing it up first? I'm happy to consider adjusting any of our style rules (loved the semicolon nixing), however I've seen a lot of benefit from adhering to some set of guidelines consistently.

The easiest workflow for sticking to the style guide is to install an eslint plugin for your editor of choice. Instructions for Atom and Sublime are here:
https://github.com/other-xyz/other.js/blob/master/CONTRIBUTING.md#initial-setup

Or at the end of the day, just make sure the tests are happy:
https://github.com/other-xyz/other.js/blob/master/CONTRIBUTING.md#testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

Visually speaking, I enjoy JS coding styles that have more space around things. I find it easier to follow/read. I have also found (N=1) that visually tighter code incents me to write more clever/terse code so as to fit in.

I guess I have preferences and also feel like my preferences don't count too strongly because I am not in the code everyday. Good PSP stuffs.

// Paste in a URL and if we understand it, show a chat complete to embed
// the URL

feature.listen({
to: 'link',
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',
body: `url=${videoURL}`,
headers: {
'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')

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}

// 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}}]
}
}

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}}
]
}
}

return {chatCompletions: []}
})
}
})

// Emoji tokens
const emoji = getEmoji()
feature.listen({
Expand Down
4 changes: 4 additions & 0 deletions lib/Feature.js
Original file line number Diff line number Diff line change
@@ -1,9 +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 {environment} from './environment'
import {userAgent} from './userAgent'

Expand Down Expand Up @@ -75,6 +78,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 => {
Expand Down
50 changes: 50 additions & 0 deletions lib/LinkListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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\-]+)?$/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry to be a drag, but we've gotta add some basic tests for this new code.

Code like this (and especially hairy regexes) are basically impossible to touch in the future without some tests in place. If we get lax right off the bat, I'm worried other.js will immediately become legacy code rather than a solid foundation to build upon.

The existing tests should be pretty straightforward to copy/paste/hack. I'm happy to help. See:
https://github.com/other-xyz/other.js/blob/master/builtins/__tests__/core.spec.js

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense that we want to start our other.js code culture right.

Here's a philosophical question: I wrote this is an experience test—how does something like this feel to gain intuition on embedded media: it was dL with minimal dW. For experimental commands, it feels like we want to minimize the dW for us all to play, while still retaining and encouraging good coding practices if we likes it. How does we do?

One solution: Maybe I would have committed this to an experimental pack of commands that works on beta.other.chat and in the enterprise app. When it's time to move it over to builtins, then it has to be fully vetted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, agreed. The way things are set up now is that there's no code coverage or similar requirements on the experimental directory. My comments about this only refer to builtins (which is what this patch is modifying).


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

const match = text.match(endingURLRegexp)
const 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