Created
November 1, 2018 23:55
-
-
Save kmark/02a64a7f29da8f674a062e36f3276270 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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