Skip to content

Instantly share code, notes, and snippets.

@protocool
Created February 14, 2011 02:29
  • Star 76 You must be signed in to star a gist
  • Fork 22 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save protocool/825404 to your computer and use it in GitHub Desktop.
Sample caveatPatchor.js file for use in Propane 1.1.2 and above
/*
As of version 1.1.2, Propane will load and execute the contents of
~Library/Application Support/Propane/unsupported/caveatPatchor.js
immediately following the execution of its own enhancer.js file.
You can use this mechanism to add your own customizations to Campfire
in Propane.
Below you'll find two customization examples.
1 - A responder that adds avatars to your chat view
2 - A responder that displays CloudApp images inline
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var displayAvatars = true;
var displayCloudAppImages = true;
/*
Display avatars in the chat view - based on code originally by @tmm1
*/
if (displayAvatars) {
Object.extend(Campfire.Message.prototype, {
addAvatar: function() {
if (this.actsLikeTextMessage()) {
var author = this.authorElement();
var avatar = '';
if (author.visible()) {
author.hide();
if (this.bodyCell.select('strong').length === 0) {
this.bodyCell.insert({top: '<strong style="color:#333;">'+author.textContent+'</strong><br>'});
avatar = author.getAttribute('data-avatar') || 'http://asset1.37img.com/global/missing/avatar.png?r=3';
author.insert({after: '<img alt="'+this.author()+'" width="32" height="32" align="top" style="opacity: 1.0; margin-left: 5px; border-radius:3px" src="'+avatar+'">'});
}
}
}
}
});
/* if you can wrap rather than rewrite, use swizzle like this: */
swizzle(Campfire.Message, {
setAuthorVisibilityInRelationTo: function($super, message) {
$super(message);
this.addAvatar();
}
});
/* defining a new responder is probably the best way to insulate your hacks from Campfire and Propane */
Campfire.AvatarMangler = Class.create({
initialize: function(chat) {
this.chat = chat;
var messages = this.chat.transcript.messages;
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
message.addAvatar();
}
this.chat.layoutmanager.layout();
this.chat.windowmanager.scrollToBottom();
},
onMessagesInserted: function(messages) {
var scrolledToBottom = this.chat.windowmanager.isScrolledToBottom();
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
message.addAvatar();
}
if (scrolledToBottom) {
this.chat.windowmanager.scrollToBottom();
}
}
});
/* Here is how to install your responder into the running chat */
Campfire.Responders.push("AvatarMangler");
window.chat.installPropaneResponder("AvatarMangler", "avatarmangler");
}
/*
Display CloudApp images inline.
This responder illustrates using Propane's requestJSON service to request
JSON from remote (non-authenticated) servers and have the results passed
to a callback of your choosing.
*/
if (displayCloudAppImages) {
Campfire.CloudAppExpander = Class.create({
initialize: function(chat) {
this.chat = chat;
var messages = this.chat.transcript.messages;
for (var i = 0; i < messages.length; i++) {
this.detectCloudAppURL(messages[i]);
}
},
detectCloudAppURL: function(message) {
/* we are going to use the messageID to uniquely identify our requestJSON request
so we don't check pending messages */
if (!message.pending() && message.kind === 'text') {
var links = message.bodyElement().select('a:not(image)');
if (links.length != 1) {
return;
}
var href = links[0].getAttribute('href');
var match = href.match(/^https?:\/\/cl.ly\/image\/[A-Za-z0-9]+\/?$/);
if (!match) return;
window.propane.requestJSON(message.id(), href, 'window.chat.cloudappexpander', 'onEmbedDataLoaded', 'onEmbedDataFailed');
}
},
onEmbedDataLoaded: function(messageID, data) {
var message = window.chat.transcript.getMessageById(messageID);
if (!message) return;
if (data['item_type'] === 'image') {
var imageURL = data['content_url'];
message.resize((function() {
message.bodyCell.insert({bottom: '<div style="width:100%; margin-top:5px; padding-top: 5px; border-top:1px dotted #ccc;"><a href="'+imageURL+'" class="image loading" target="_blank">' + '<img src="'+imageURL+'" onload="$dispatch(&quot;inlineImageLoaded&quot;, this)" onerror="$dispatch(&quot;inlineImageLoadFailed&quot;, this)" /></a></div>'});
}).bind(this));
}
},
onEmbedDataFailed: function(messageID) {
/* No cleanup required, we only alter the HTML after we get back a succesful load from the data */
},
onMessagesInsertedBeforeDisplay: function(messages) {
for (var i = 0; i < messages.length; i++) {
this.detectCloudAppURL(messages[i]);
}
},
onMessageAccepted: function(message, messageID) {
this.detectCloudAppURL(message);
}
});
Campfire.Responders.push("CloudAppExpander");
window.chat.installPropaneResponder("CloudAppExpander", "cloudappexpander");
}
@pixelmatrix
Copy link

Does propane have a requestXML that works like requestJSON? Is there any sort of documentation on the API at all?

@protocool
Copy link
Author

With requestJSON I parse the remote site's JSON into Cocoa data structures and then serialize it out again as a method invocation string. Doing it this way sanitizes the JSON (avoiding eval risks), eliminates potential memory leaks and avoids any nasty crashes that might happen by letting javascript poke back into Propane too much...

As for the API, it's just:

window.propane.requestJSON(unique_id, href, qualified_object_name, success_method_name, failure_method_name)

From that I will request the JSON from the specified href and if I get a 200 response with a valid json payload I'll call:

qualified_object_name.success_method_name(unique_id, SanitizedPayload);

If the response I got was non-200 or the json parse failed then I call:

qualified_object_name.failure_method_name(unique_id)

Unfortunately I can't do the same sort of thing with a requestXML because the payload is probably much more than just dictionaries, arrays and key/value pairs. About the best thing I could do would be to pass in the response body as a string and let your callback method figure it out. Given that most people will just do something.innerHTML = payload, it kind of scares the heck out of me security-wise.

Ultimately I think the best solution is coming in Propane2 - a fully native interface (no webkit) and a scripting environment that's a first-class citizen, able to do pretty much anything that the compiled code can.

@pixelmatrix
Copy link

Interesting. So, I was hoping to make the cl.ly thing for droplr. They don't have a JSON api just yet, so maybe i'll wait until they do. Or maybe i'll write an intermediary XML to JSON parser service and throw it up on Heroku.

@protocool
Copy link
Author

The intermediary may be your best bet right now.

I'm actually waiting to see how long it will be before someone goes hog-wild and hooks up a responder to a public intermediary like http://oohembed.com/

@protocool
Copy link
Author

And before anyone reading this asks: no, you can't embed flash videos. Propane disables flash, which is one of the reasons you can leave it running for longer than two hours.

@pixelmatrix
Copy link

Thanks for the help. I'll see what I can do for the intermediary service. Should be pretty dang easy. I use droplr all the time, so it would definitely be worth it to have support for that.

@pixelmatrix
Copy link

Alrighty, http://droplr-json.heroku.com is up, and my fork includes a hack for Droplr image embeds :D

@phred
Copy link

phred commented Oct 6, 2011

@protocool: like this? https://gist.github.com/1268036

Looks like oohembed got bought by Embedly. Someone please improve my hack. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment