Download SurfTheChannel videos

By Tim McCormack Last update Jul 7, 2008 — Installed 745 times.
// ==UserScript==
// @name           Download SurfTheChannel videos
// @namespace      tag:brainonfire.net,2008-06-14:stcdownload
// @description    Decrypts the SurfTheChannel video source and adds a download link to the sidebar. This script is extremely powerful, and will contact an external keyserver whenever it discovers a movie it cannot download. The keyserver will generate a new userscript for you to download. Generally works for the Movies and TV sections, but content hosted by YouKu won't be downloadable.
// @include        http://www.surfthechannel.com/*
// @include        http://surfthechannel.com/*
// @version        1.3.0
// @changelog      Since 1.2: Fix bug that caused Part-finding to fail on first part.
// ==/UserScript==

var stcd =
{
	version: '1.0',
	srv: 'http://phyzomebox.no-ip.info/stc/unknown-skin.php'
};


/* From http://wiki.greasespot.net/Code_snippets */
function $xpath(p, context)
{
	if(!context)
		context = document;
	var i, arr = [], xpr = document.evaluate(p, context, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for(i = 0; item = xpr.snapshotItem(i); i++)
		arr.push(item);
	return arr;
}

/** Put a big ol' fail message at the top of the page */
function failMessage(msg)
{
	var failBar = document.createElement('p');
	failBar.appendChild(document.createTextNode('Userscript failure: '+msg));
	failBar.setAttribute('style', 'color: white; background-color: red; padding: 0.3em 0.6em; margin: 0; font-weight: bold; border: 3px solid white');
	document.body.insertBefore(failBar, document.body.firstChild);
	return;
}


//harvest data
var embed = $xpath('//div[@id="flashcontent2"]/embed[starts-with(@flashvars, "input_str=")]');

if(embed.length !== 1)
	return;

embed = embed[0];
var flashvars = embed.getAttribute('flashvars');

//check key DB
//setup constants
var knownSkins =
{
	'1':
	[
		'53245346354634563247123YjQ1ZTc1OTI5OT401823741237409812734083428034532828928384944213412344',
		'1234123412735723450982734509827345982345OT3748102349871230489712304987059823405981233848848392983829204982310948234059823450982345',
		'841239048809809809238409309109108123478901234jEjdQjd71230497123047123409871230498712309487120938471029347810982374292890820938120495994928393810991928384'
	],

	'2':
	[
		'1349834568902348908903456ASdfed890345890245892345890345689034568903428901328902463890435679Fdsa083456890dfsafsqt434',
		'123345358931942135asdrq325jqwer231451kl23412341452334528925436jk436264536jk345l3345233245k456813458903145803245',
		'345895468903458903428902348D9435895489456897453897342789234187912378934789578945789435asdD78931247S892789asdsFGfs0231589071258790236407892657089'
	],

	'3':
	[
		'23988905348903451890456298071345908342689045678905627890234607893457809342576324789025634780923457890256789023adgs780gfeqw7890qtag78DSA52345ASee',
		'284328248902435890234890453893548902345890354890158901238902634789026347894358715437890325708914537809623478092564asdfagAase355387903245',
		'34129842378931204798345702124986984647890341252346579193758342658347981587749023487509234978523704895723045897s348907adsf9087a39873'
	],

	'4':
	[
		'3952437890346570923470913527890249078460789534680792436078934527890236478904356SDrtwsdfgsfgd234782347247809245378092345780243578093123424278',
		'53764972809567513208957240695730198740342761019237852039847590234875019837528340572345292107895340798',
		'5346890312306543778347890462323470580791237089153279082346780623479081345780912358914570894367890457890340789342675089235'
	],

	'5':
	[
		'2378902345780234582d34578902g3G4578DS9023478902345790832456789123679812367893451689asd7153g467985123679159678145678943678923456789061567157689253678925',
		'26746137029347230459870948752010347202349750430985723049572108074570237453892475098275097543059872523845029381890234857097',
		'1273408917234097145234asfa52345723490578234059732405987dfsa09874350987as0d9f78097843205897203489752038947509283475098712048971203894'
	],

	'6':
	[
		'5324j65745768567435257097142098rf07af8130298742078f0asd7f137490982743013927409351234jklq5h4243hkl52hj345kjl2345jkl23h4lj5kh1l2khj34123lkh',
		'326746759665764634512334895456726435133j52h54hjl3456hlj4567hjlk312klhj1234ghjk1234ghjkbm2345b34263745674356523412',
		'4643458l657j736l234kj3142kjh534hlkjh847lk3j45h123lkhj41lkj4h5354l7kh56h96789hj80764573645213245234634564575456756523142354345623wel4jhk13253457456763454353124535474565343456345'
	],

	'7':
	[
		'435253765049678523019583245345h2435123412kl412564576547623542536658780789845kj647756734573457890243790823434270527347503701430746adf1234712389',
		'f24634h7jkl342jh42453455436kjlhgsdfthj456234kl5jhtadlflv3245357456jl7j234l5134123hjk4h326456jkl2345hj234kj5h',
		'j65k34l53125flv3452351234kjh1234123j4j123jgh45675768234534746573241514324flv3452345234512433456j15314324'
	],

	'8':
	[
		'645776324157890234578092347081235708923147809123789afds789035278903217844783flv128231812382345',
		'54675644536345789043789053467089523478901234789454678342324flv3874123902arsdf34127839041234',
		'4657362132475677354412348970as78979fdas7890789014327890a7890sdfa7089as70df70flv132741209384123894'
	],

	'9':
	[
		'2761758091537890263470893524780912347809132789fa78901342781427890312478903412781flv12381238341289123891428038041023adfs38094123',
		'373564789345892351840987fds1234708912347891209348flv12347982340673240894342789026347098123789031247890234170892347890flv324523',
		'324758310912347012937f7as0d9785412370470238478189g73174890123748912flv13241874905498753412347896435987456x43252345'
	],

	'10':
	[
		'r2381490fasd890r3091248490q8230958adsf890358490123849130289412394890flv374123984712983472834967523918423894a9sdf97834127894',
		'312472378490230189237r7098670893456789037908123478913flv34123784901237423405789w7asd87913879014',
		'32453238724589z890fsad7089q34590812377r90flv7324890138as0df7089531428479123794'
	],

	'13':
	[
		'wrucasachechachusechevetaphekasewrujethesafranapechawepEcrecatru',
		'YAqazetHuspaYUgawreCruxuHatHAdePhaTHEJuprephaBasPebabrUZusubUhax',
		'yequQAqabaNujeweDEswukayasaDRuJespuduspASavupasegAnuqatuvuquxuva'
	],

	'12':
	[
		'trustedrafrecraDruchakecHepRuyachEcafejeprudRuBrutredrUvucruSast',
		'dreGuraSpemunuPunexANuhevAbRUxequpuCRukUSWuPhuFrewArakuTruwaDRec',
		'yuhecraTebrayanaspathuswechemepaGAPhUdunuQEFAdaqaSpuFapHuxaYemAc'
	],

	'15':
	[
		'TaswaPReXefePhuwubabrusweWruChedRepraTHeNuyeyUtutejAtHepAjeterAc',
		'cHujeswArevuzubrezUdEswePaPhexefAwrUceDutrUfrusathatranuwrEHewre',
		'wuspUPhujafakuteFufuvaWrugespaZaspuPracrequspamaraswufuvevupruwe'
	],

	'11':
	[
		'HatuFuswecruspuspupreZERaxUtrEdREpHaGavagebunahaqeFesumawREPrutR',
		'mAprucufrayAXuvepRuwucredRusezeBrezathaspachukUphamukeNufrechedr',
		'bruHAdraqUwetUHEKujaguyUPrUtHaYAStebaDusPebusPePAsaRaSwefraZAnUw'
	],

	'14':
	[
		'frerETusecrAveswajeWrusTuwruPraTutheDuwraqaBacuxUxebaPETEcEtRunE',
		'xEhunUbamAchecrePUsAsUveSeFustumehATarucEnEPheSwuVamubAzemespape',
		'suHEdefucERedUxephuZErEPrUmEstEFrePrufebabewrUcrethaMadruRawrast'
	],


};

//find sidebar
var sidebar = $xpath('//div[@class="item-summary-tr"]|//div[@id="video-right"]/div[@class="info"]|//div[@class="item-summary"]//div[@class="info"]'); //various versions
if(sidebar.length !== 1)
	failMessage('Could not find sidebar, please upgrade this greasemonkey script.', true);
sidebar = sidebar[0];
sidebar.style.height = 'auto';

//define actions

/**
 * Put the download (or info) link on the sidebar.
 */
function attachDownloadLink(linkInfo)
{
	var dl = document.createElement('a');
	dl.appendChild(document.createTextNode(linkInfo.text));
	dl.setAttribute('href', linkInfo.href);
	dl.style.backgroundColor = 'yellow';
	dl.style.color = 'blue';

	sidebar.insertBefore(dl, sidebar.firstChild);
}

/**
 * Retrieve data from a sidebar node's text().
 */
function extractSidebarField(textNodes)
{
	return textNodes.map(function(el){return el.textContent;}).join('').replace(/^\s*:\s*(.+?)\s*$/, '$1');
}

/**
 * Create a nice filename for this video. Returns null if cannot build.
 */
function buildPrettyFileName()
{
	var showBlock = $xpath('.//*[span[@class="htitle"][text()="Show"]]/a/text()', sidebar);
	var titleBlock = $xpath('.//*[span[@class="htitle"][text()="Episode Title"]]/text()', sidebar);
	var seasonBlock = $xpath('.//*[span[@class="htitle"][text()="Season"]]/text()', sidebar);
	var episodeBlock = $xpath('.//*[span[@class="htitle"][text()="Episode"]]/text()', sidebar);
	var partsBlock = $xpath('.//*[span[@class="htitle"][text()="Parts"]]/text()', sidebar);

	var isSeries = (seasonBlock.length === 1) && (episodeBlock.length === 1);
	if(showBlock.length < 1 || titleBlock.length < 1 || partsBlock.length < 1)
	{
		failMessage('Could not read sidebar data: Show, title, parts. (Link will not be pretty-printed.)', false);
		return null;
	}

	var epiTitle = extractSidebarField(titleBlock);
	var partNum = extractSidebarField(partsBlock).replace(/^[, ]*Part ([0-9]+)[, ]*$/, '$1');
	if(/^[0-9]+$/.test(partNum) != true) // perhaps == " : N/A"
		partNum = null;
	
	// intro of name (none for a non-series)
	
	var prettyFileName = '';
	
	if(isSeries)
	{
		var showTitle = extractSidebarField(showBlock);	
		var episodeNum = extractSidebarField(episodeBlock);
		var seasonNum = extractSidebarField(seasonBlock);
		
		prettyFileName = showTitle+' S'+seasonNum+'E'+episodeNum+': ';
	}
	
	// feature title
	
	prettyFileName += epiTitle;
	
	// part number
	
	if(partNum !== null)
	{
		prettyFileName += ' (part '+partNum+')';
	}
	
	// extension
	
	prettyFileName += '.flv';
	
	return prettyFileName;
}

/**
 * Use a pretty download link if possible.
 */
function buildDownloadLink(stcLink)
{
	//can we parse it?
	var stcID = /^http:\/\/getlink.surfthechannel.com\/flv\/(([0-9a-z]|%2[BD])+={0,2}).flv$/gi.exec(stcLink);
	
	if(stcID === null) // well, we tried
		return stcLink;
	stcID = stcID[1];
	
	//encode pretty filename
	var prettyFileName = buildPrettyFileName();
	if(prettyFileName === null) // again, we tried
		return stcLink;
	
	//use a redirector service
	return 'http://lab.brainonfire.net/director/download.php/'+encodeURIComponent(prettyFileName)+'?stc='+encodeURIComponent(stcID);
}

/**
 * Ask a keyserver for help if the skinID is unknown.
 */
function resolveUnknownSkin(skinID)
{
	GM_xmlhttpRequest(
	{
		method: 'POST',
		url: stcd.srv,
		data: 'version=' + stcd.version + '&id=' + skinID,
		headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-STCD-check': 'yes'},
//		overrideMimeType: 'text/xml; charset=UTF-8',
		onerror: function(response)
		{
			attachDownloadLink({text: 'Unknown encryption keys, could not contact keyserver. Please try back tomorrow. If this persists, leave a comment on the userscript page.', href: usorgURL});
		},
		onload: function(response)
		{
			if(response.status !== 200)
			{
				attachDownloadLink({text: 'Unknown encryption keys, keyserver returned an error. Please try back tomorrow. If this persists, leave a comment on the userscript page. Error: '.response.responseText, href: usorgURL});
				return;
			}
			
			var linkInfo = eval('(' + response.responseText + ')');
			attachDownloadLink(linkInfo);
		}
	});
}

/**
 * Grab, decode, and display the download link.
 */
function displayDownloadLink()
{
	//grab input data
	var working = /^input_str=(.+?)&/.exec(flashvars)[1];
	var skinID = /&config=\/skin2\/dynamic\.php\?xmlnum=([0-9]+)[&-]/.exec(flashvars);
	attachDownloadLink
	if(skinID === null)
	{
		attachDownloadLink({text: 'Cannot determine skinID. Perhaps there is a newer version of the script?', href: usorgURL});
		return;
	}
		
	var skinID = skinID[1];

	if(!knownSkins[skinID])
	{
		resolveUnknownSkin(skinID, andThen);
		return; // we'll do this asynchronously
	}

	var skinKeys = knownSkins[skinID];

	//decrypt
	working = atob(working);

	var pass = undefined;
	for each(pass in skinKeys)
	{
		working = working.split(pass).join('');
	}

	working = atob(working);
	
	attachDownloadLink({text: 'Download FLV', href: buildDownloadLink(working)});
}

//Get it and display it
displayDownloadLink();