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.