Skip to content

Instantly share code, notes, and snippets.

@kmark
Created November 1, 2018 23:55
Show Gist options
  • Save kmark/02a64a7f29da8f674a062e36f3276270 to your computer and use it in GitHub Desktop.
Save kmark/02a64a7f29da8f674a062e36f3276270 to your computer and use it in GitHub Desktop.
#!/usr/bin/env php
<?php
/*
* Screencast.com Account Media Downloader
*
* Copyright 2018 Kevin Mark
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ----------
*
* Tested on PHP 7.2.11
* How to archive your Screencast.com account in 10 steps:
*
* 1) Run "composer require guzzlehttp/guzzle" in the same directory
* as this file.
* 2) Login to Screencast.com in Chrome
* 3) Navigate to https://www.screencast.com/users/yourusernamehere
* 4) Open the Developer Tools (View -> Developer -> Developer Tools).
* 5) Click on the Network tab
* 6) Refresh the page
* 7) Scroll to the top of that big list
* 8) You'll see your username at the top. Click that.
* 9) Copy everything after "Cookie:" until you get to the next listing to your clipboard
* 10) Run this script either directly or invoking PHP manually:
* $ php ScreencastDotComDownloader.php yourusernamehere 'paste your cookies here' /directory/to/download/to
* It's imperative that you surround your cookie paste with quotes as above does.
*/
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Cookie\SetCookie;
require __DIR__ . '/vendor/autoload.php';
if ($argc < 4) {
println('Not enough args');
exit(1);
}
doIt($argv[1], fromChromeCookies($argv[2]), $argc < 3 ? '.' : $argv[3]);
function doIt(string $username, array $cookies, string $dir) {
if (!is_dir($dir)) {
println("Provided directory does not exist: " . $dir);
exit(1);
}
$jar = new CookieJar(false, $cookies);
$client = new Client([
'cookies' => $jar,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
'Accept' => '*/*',
],
]);
println('Enumerating media...');
$index = 0;
$discoveredMedia = [];
do {
$request = $client->post("https://www.screencast.com/users/{$username}/ShowMore", [
'form_params' => [
'Index' => $index++,
'removedCount' => 0,
],
]);
if ($request->getStatusCode() != 200) throw new \RuntimeException('Status not 200');
$json = \GuzzleHttp\json_decode($request->getBody()->getContents());
$html = new \SimpleXMLElement('<div>' . $json->mediaSets . '</div>');
foreach ($html as $node) {
$guid = (string) $node->attributes()['data-guid'];
if (empty($guid)) {
throw new \InvalidArgumentException('GUID failure!');
}
$discoveredMedia[$guid] = [
'guid' => $guid,
'folder' => (string) $html->xpath('//li[@data-guid=\''.$guid.'\']//span[@class=\'media-item-stat media-containing-folder\']')[0],
];
}
println(sprintf('Found %d media items', count($discoveredMedia)));
} while (!$json->allContentLoaded);
println('Retrieving CSRF token...');
$userPageRequest = $client->get("https://www.screencast.com/users/{$username}");
if ($userPageRequest->getStatusCode() != 200) throw new \RuntimeException('Status not 200');
preg_match('/<input name="__RequestVerificationToken" type="hidden" value="(.+)" \/>/', $userPageRequest->getBody()->getContents(), $matches);
$csrfToken = $matches[1];
println('Retrieving download URLs...');
$progress = 0;
foreach ($discoveredMedia as $guid => &$data) {
$progress++;
println(sprintf('Fetching URL %d of %d (%d%%)', $progress, count($discoveredMedia), $progress / count($discoveredMedia) * 100));
$getDownloadUrlRequest = $client->post("https://www.screencast.com/users/{$username}/DownloadMediaSets", [
'form_params' => [
'mediaSets' => $guid,
'__RequestVerificationToken' => $csrfToken,
],
]);
if ($getDownloadUrlRequest->getStatusCode() != 200) throw new \RuntimeException('Status not 200');
try {
$getDownloadUrlJson = \GuzzleHttp\json_decode($getDownloadUrlRequest->getBody()->getContents());
} catch (\InvalidArgumentException $ex) {
$data['url'] = null;
println(sprintf('Failed to retrieve URL for %s', $guid));
continue;
}
if (!$getDownloadUrlJson->success) throw new \RuntimeException('Success is false');
if (empty($getDownloadUrlJson->downloadUrls)) throw new \RuntimeException('No Download Urls');
$data['url'] = $getDownloadUrlJson->downloadUrls[0];
}
println('Downloading media...');
$downloaded = array_reduce(array_filter($discoveredMedia, function (array $data) : bool {
return $data['url'] != null;
}), function (array $progress, array $data) use ($client, $dir) : array {
$progress[0]++;
println(sprintf('Downloading %d of %d (%d%%)', $progress[0], $progress[2], $progress[0] / $progress[2] * 100));
if (isset($data['folder']) && $data['folder'] != '') {
$folder = $dir . DIRECTORY_SEPARATOR . $data['folder'];
if (!is_dir($folder) && !mkdir($folder)) {
println(sprintf('Failed to create directory "%s" for %s', $data['folder'], $data['guid']));
return $progress;
}
} else {
$folder = $dir;
}
$tmpFile = tempnam(sys_get_temp_dir(), 'scrdown');
$response = $client->get($data['url'], [
'sink' => $tmpFile,
]);
preg_match('/filename="(.+)"/', $response->getHeader('Content-Disposition')[0], $matches);
rename($tmpFile, $folder . DIRECTORY_SEPARATOR . $matches[1]);
$progress[1]++;
return $progress;
}, [0, 0, count($discoveredMedia)]);
println(sprintf('Successfully downloaded %d of %d items', $downloaded[1], $downloaded[2]));
}
function println(string $msg) {
echo $msg . "\n";
}
function fromChromeCookies(string $str) : array {
$cookies = [];
foreach(explode('; ', $str) as $item) {
$cookies[] = SetCookie::fromString($item . '; path=/; domain=.screencast.com; Expires=Tue, 19 Jan 2038 03:14:07 GMT;');
};
return $cookies;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment