Programmatically Uploading Images To JING At ScreenCast.com Using ColdFusion
Here at work, we use JING all the time to share ideas; it's by far the easiest way that I've ever seen to quickly take screen captures, annotate them, and then share them with other people. When capturing part of your screen using JING, you have the option to upload it ("share it") to ScreenCast.com. Doing this uploads the image to a unique URL and then automatically copies this unique URL to your clipboard (which you can then IM to someone else). 99% of the time, this is perfect, but it does have one limitation - you can only capture what's visible on your screen.
Like I said, in most cases, this is not an issue; but, sometimes, when collaborating on design work, I want to share an image that doesn't quite fit on the screen. I could find other ways to share the image (ex. sending over GTalk); but JING is just so badass that I wanted to see if I could perhaps programmatically integrate with the application (more for fun than anything else). I contacted the ScreenCast.com support center to ask about APIs and they told me it was not currently possible:
Hi Ben. I do understand what you are looking to do. Unfortunately there is not a way to do this currently. However, I would encourage you to share this with our Product Manager and Development Team. Feature requests, feedback and other suggestions are something that help us out immensely, because it allows us to gear our products in a way that fill the needs of our users. While I can (and will) certainly pass on any suggestion you may have, it is best to use the following link - it is a page that logs your request to our development team, and they can read it directly themselves: [feedback-url].
If you have any further questions or concerns, please let me know.
Thanks and take care,
Melissa
I was disheartened by this response, but I was in no-way about to stop digging. I had already looked at the HTTP activity of the JING application and the number of HTTP requests that JING makes just to upload a file is rather enormous; and, it never actually returns the newly created URL (I assume it's generated through some sort of internal algorithm). As such, I couldn't spoof JING. But, what about spoofing ScreenCast.com?
JING is not the only way to move your content to ScreenCast.com - you can also log into the web site and upload files directly to your JING media folder (just one aspect of your ScreenCast.com media library). I figured this would be a prefect opportunity to try taking my CFHTTPSession.cfc ColdFusion component and see if I could programmatically log into the website and upload the file. It took me about 3 hours to figure out, but I finally got it working:
As I was picking my way through the various HTTP requests that were required, I actually discovered that the file upload functionality in my CFHTTPSession.cfc ColdFusion component was completely broken; not only did it try to add the file as a Cookie, it also completely errorred out during debugging. Once I fixed that (and updated the project page), the rest of it was just tracing variables from request to request. Here is the code that I finally came up with:
<!---
Set login credentials for ScreenCast.com. We are
basically going to log into the system and upload the
image programmatically.
<cfset username = "............" />
<cfset password = "............" />
--->
<cfinclude template="./credentials.cfm" />
<!---
This is your display name - it's the folder YOU get for
your content.
--->
<cfset displayName = "BenNadel" />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
This is the file path to the file that we are going to
programmatically upload to the JING service (as hosted on
ScreenCast.com).
--->
<cfset filePath = expandPath( "./sexy.jpg" ) />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
Create the CFHttpSession object that will be used to navigate
through the ScreenCast.com system as if we were logged in.
--->
<cfset httpSession = createObject( "component", "CFHTTPSession" )
.Init(
LogFilePath = ExpandPath( "./log.txt" )
)
/>
<!---
To kick off the session, let's hit the ScreenCast.com website.
This will set us up with our initial cookies and get our URL
referer values ready.
--->
<cfset httpSession
.newRequest( "http://www.screencast.com" )
.get()
/>
<!---
Now that our session has been initialized for the
ScreenCast.com website, we can submit our login information.
These form fields were taken out of the source of the
screenCast.com homepage.
--->
<cfset httpSession
.newRequest( "https://www.screencast.com/signin.aspx" )
.addFormField( "emailAddress", username )
.addFormField( "password", password )
.addFormField( "task", "L" )
.addFormField( "invitationCode", "" )
.addFormField( "playlistId", "" )
.post()
/>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
At this point, our HTTP session used should be logged into the
ScreenCast.com webiste. Now, we can navigate to the JING
folder (we are assuming this exists).
--->
<cfset httpSession
.newRequest( "http://www.screencast.com/users/#displayName#/folders/Jing" )
.get()
/>
<!---
Once we are in the JING folder, let's make a request to the
upload page. This is NOT where we are uploading the file just
yet - this is a call to the Flash Modal Window which uploads
the file eventually. We need this page to scrape some crucial
system variables.
--->
<cfset httpResponse = httpSession
.newRequest( "http://www.screencast.com/controls/lightboxes/upload.aspx" )
.addURL( "userName", displayName )
.addURL( "mediaGroupName", "Jing" )
.addURL( "random", "12654141#randRange( 11111, 99999 )#" )
.get()
/>
<!---
This modal window upload request is going to come back with a
Flash uploader that is written to the page with Javascript.
When this happens, it writes out all of the FORM values that
the flash movie will need to upload in the form of:
so.addVariable( "thumbnail", "False" );
so.addVariable( "viewpage", "False" );
so.addVariable( "multiselect", "True" );
We need to grab thos variables in order to upload the file.
--->
<cfset flashVariables = reMatch(
"addVariable\([^)]+\)",
httpResponse.fileContent
) />
<!---
Now that we have the rough values, we need to extract them
into a collection of form variables. Let's create a form
collection.
--->
<cfset postVariables = [] />
<!---
Loop over the scaped flash variables to parse them and clean
them up.
--->
<cfloop
index="flashVariable"
array="#flashVariables#">
<!--- Extract the name value pair from the variable data. --->
<cfset nameValuePair = reMatch(
"[""'][^""']*[""']",
flashVariable
) />
<!---
Check to see if this value is valid - we only want
to add it to our collection is there are two elements
(name + value) and the value has a length.
--->
<cfif (
(arrayLen( nameValuePair ) eq 2) &&
(len( nameValuePair[ 1 ]) gt 2) &&
(len( nameValuePair[ 2 ]) gt 2)
)>
<!---
Create a post variable (we need to remove the quotes
from the name/value pairs).
--->
<cfset postVariable = {
name = mid(
nameValuePair[ 1 ],
2,
(len( nameValuePair[ 1 ] ) - 2)
),
value = mid(
nameValuePair[ 2 ],
2,
(len( nameValuePair[ 2 ] ) - 2)
)
} />
<!--- Add this to our post variables. --->
<cfset arrayAppend( postVariables, postVariable ) />
</cfif>
</cfloop>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
Now that we have the post values that we are going to need
for the file upload, let's start a new request for the
actual file post. Add the file name and file path.
--->
<cfset httpSession
.newRequest( "http://www.screencast.com/handlers/filereceive.ashx" )
.addFormField( "Filename", listLast( filePath, "\/" ) )
.addFile( "FileData", filePath, "image/*" )
/>
<!--- Loop over the post variables we collected previously. --->
<cfloop
index="postVariable"
array="#postVariables#">
<!--- Add the form field. --->
<cfset httpSession.addFormField(
postVariable.name,
postVariable.value
) />
</cfloop>
<!---
Now that we all the variables, let's post the file upload.
***** THIS IS WHERE WE ARE UPLOADING THE ACTUAL FILE. *****
***** THIS IS WHERE WE ARE UPLOADING THE ACTUAL FILE. *****
--->
<cfset httpResponse = httpSession.post() />
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!---
At this point, the response file content *should* be XML-ish
in nature. Let's take the response and try to wrap it up in
an XML document (the uniqe ID of our upload will be stored
in the "mediaSetId" XML node.
--->
<cftry>
<!--- Create an XML document. --->
<cfxml variable="xmlResponse">
<response>
<cfoutput>
#httpResponse.fileContent#
</cfoutput>
</response>
</cfxml>
<!--- Get the media Set ID from the response. --->
<cfset mediaSetID = xmlResponse.response.mediaSetId.xmlText />
<!--- With the media set ID, build the URL. --->
<cfset jingURL = (
"http://www.screencast.com/users/#displayName#/" &
"folders/Jing/media/#mediaSetID#"
) />
<!--- Link to the upload. --->
<p>
<cfoutput>
<a href="#jingURL#" target="_blank">View JING Upload</a>
</cfoutput>
</p>
<!--- ------------------------------------------------- --->
<!--- Catch any errors. --->
<cfcatch>
<!---
Honestly, if we made it here, then something went
totally wrong - chances are they changed the way
file uploads work and we'll have to tweak our
process.
--->
<p>
There was a problem parsing the file upload response
into an XML document. Check the log files.
</p>
</cfcatch>
</cftry>
I'm not going to go into too much detail since the code is quite heavily commented (plus the video). When all was done, uploading the file took five successive HTTP requests using CFHTTPSession.cfc:
- Initialize the session (hit the home page).
- Submit the login form (log into the website).
- Navigate to the JING media folder.
- Request the upload form (to scrape variables).
- Post the file to the JING media library on ScreenCast.com.
When you post the file, ScreenCast.com returns a pseudo XML document that contains the ID of your upload in one of the response nodes. This ID is what I can then use to create the public URL that I send to someone.
Any time you do something programmatically that is based on data defined within a web page, you are at the mercy of the stability of that page. Should the page change too much, your entire algorithm might break. As such, this approach is never recommended when alternatives exist. Right now, there are no alternatives; but, I think it would be awesome if JING / ScreenCast.com added a public API. While this was mostly for fun, I can totally see some serious potential for this kind of integration.
Want to use code from this post? Check out the license.
Reader Comments
Nice! Sure would be awesome if they released an API for Jing/Screencast!
@Andy,
Yeah, I can see some cool app-integration taking place.
More importantly, I could see this being used in conjunction with AIR 2.0's native application integration process.
http://labs.adobe.com/technologies/air2/
@Andy,
Yeah, that would be very cool!
The tech support people just got back to me (I sent them a link last week); Melissa passed it onto their development staff, so hopefully they will become more aware that this is a piece of functionality that people are looking for.
Hi @Ben and @Andy. I'm Dirk, the Product Manager for Jing and Screencast.com. I saw your post through a Google Alert, and Melissa just forwarded us the link to your Blog as well. Just so you know, we hear you and are doing what we can to work toward our goal of one day offering an open API for both Jing and SC.com. I can't say for sure when we'll be ready, but your wishes are not falling on deaf ears. Keep up the good work, and thanks for sharing your integration work.
Cheers,
Dirk
@Dirk,
That's awesome! Good luck; feel free to ping us for ideas or feedback.