Created
June 19, 2022 19:37
-
-
Save Achterstraat/7422ecb6a12e64cdbbd1c7428fc4b25d to your computer and use it in GitHub Desktop.
Use SSH, Telegram or any Webbrowser to Sync Amazon Blink Mediafiles to Synology (with or without Cronjobs)
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
<?php | |
class Blink | |
{ | |
public $account = [ | |
'credentials' => [ | |
'email' => '', | |
'password' => '', | |
], | |
'region' => 'rest-prod.immedia-semi.com', | |
]; | |
public $regions = []; | |
public $storage = '/volume1/surveillance'; | |
public $telegram = [ | |
'bot' => '', | |
'chat' => '', | |
'data' => null | |
]; | |
public $networks = []; | |
public $sync_modules = []; | |
public $cameras = []; | |
public $sirens = []; | |
public $chimes = []; | |
public $doorbells = []; | |
public $device_limits = []; | |
public $domain = null; | |
public $error = null; | |
public function __construct() | |
{ | |
ini_set('display_errors', 1); | |
ini_set('display_startup_errors', 1); | |
error_reporting(E_ALL); | |
$this->account(null, 'account'); | |
$this->domain = substr($_SERVER['HTTP_HOST'], strpos($_SERVER['HTTP_HOST'], '.')+1); | |
if(!$this->regions()) | |
{ | |
$this->error = 'Regions'; | |
} | |
return $this; | |
} | |
public function account($data = null, $name = 'account') | |
{ | |
if($this->is('telegram')) | |
{ | |
$file = dirname(__FILE__).'/account.txt'; | |
if(is_null($data)) | |
{ | |
if(is_file($file)) | |
{ | |
$account = json_decode($this->cryptor(file_get_contents($file), 'decrypt'), true); | |
$this->account = $account; | |
return $account; | |
} | |
return false; | |
} | |
elseif(is_array($data)) | |
{ | |
return file_put_contents($file, $this->cryptor(json_encode($data))); | |
} | |
return @unlink($file); | |
} | |
$account = $this->cookie('account', $data); | |
if($account) | |
{ | |
$this->account = $account; | |
} | |
return $account; | |
} | |
public function cookie($name = null, $value = null, $secs = 3600) | |
{ | |
if(is_string($name)) | |
{ | |
if(isset($_COOKIE[$name])) | |
{ | |
if(is_null($value)) | |
{ | |
$data = (empty($_COOKIE[$name]) ? '' : stripslashes($_COOKIE[$name])); | |
return (empty($data) ? false : json_decode($this->cryptor($data, 'decrypt'), true)); | |
} | |
elseif($value) | |
{ | |
return setcookie($name, $this->cryptor((is_array($value) ? json_encode($value) : $value)), (time()+$secs), '/', '.'.$this->domain); | |
} | |
else | |
{ | |
return setcookie($name, $this->cryptor(false), (time()-$secs), '/', '.'.$this->domain); | |
} | |
} | |
elseif(is_null($value)) | |
{ | |
return false; | |
} | |
else | |
{ | |
unset($_COOKIE[$name]); | |
return setcookie($name, $this->cryptor((is_array($value) ? json_encode($value) : $value)), (time()+$secs), '/', '.'.$this->domain); | |
} | |
} | |
return false; | |
} | |
public function cryptor($data, $action = 'encrypt') | |
{ | |
$vars = [ | |
'iv' => '1234567890987654', | |
'key' => 'AmazonBl!nk', | |
'method' => 'AES-128-CBC', | |
'options' => 0, | |
]; | |
$length = openssl_cipher_iv_length($vars['method']); | |
switch($action) | |
{ | |
case 'encrypt': { | |
return openssl_encrypt($data, $vars['method'], $vars['key'], $vars['options'], $vars['iv']); | |
} | |
case 'decrypt': { | |
return openssl_decrypt($data, $vars['method'], $vars['key'], $vars['options'], $vars['iv']); | |
} | |
default: { | |
return false; | |
} | |
} | |
} | |
public function curl($uri, $fields = [], $headers = [], $type = 'POST', $file = false) | |
{ | |
$curl = curl_init(); | |
curl_setopt($curl, CURLOPT_URL, $uri); | |
if(in_array($type, ['POST'])) | |
{ | |
curl_setopt($curl, CURLOPT_POST, 1); | |
curl_setopt($curl, CURLOPT_POSTFIELDS, (is_array($fields) ? json_encode($fields) : $fields)); | |
} | |
curl_setopt($curl, CURLOPT_USERAGENT, 'AmazonBl!nkert'); | |
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); | |
curl_setopt($curl, CURLOPT_HTTPHEADER, array_merge([ | |
'Content-Type: application/json' | |
], $headers)); | |
$response = curl_exec($curl); | |
curl_close($curl); | |
return ($file ? $response : json_decode($response, true)); | |
} | |
public function form($type = 'login') | |
{ | |
$form = false; | |
switch($type) | |
{ | |
case 'login': { | |
$form = '<form action="/" method="POST"> | |
<div> | |
<label for="email">E-mail:</label> | |
<input type="text" name="email" id="email" value="'.(empty($this->account['credentials']['email']) ? '' : $this->account['credentials']['email']).'" autocomplete="off"> | |
</div> | |
<div> | |
<label for="pin">Password:</label> | |
<input type="password" name="password" id="password" value="'.(empty($this->account['credentials']['password']) ? '' : $this->account['credentials']['password']).'" autocomplete="off"> | |
</div> | |
<div> | |
<label for="region">Region:</label> | |
<select name="region" id="region">'; | |
foreach($this->regions as $region => $name) | |
{ | |
$form .= '<option value="'.$region.'" '.(!empty($this->account['region']) && $region == $this->account['region'] ? 'selected="selected"' : '').'">'.$name.'</option>'; | |
} | |
$form .= '</select> | |
</div> | |
<div> | |
<label for="pin">Storage:</label> | |
<input type="text" name="storage" id="storage" value="'.(empty($this->storage) || !is_dir($this->storage) ? '' : $this->storage).'" autocomplete="off"> | |
</div> | |
<button type="submit">Check</button> | |
</form>'; | |
break; | |
} | |
case 'pin': { | |
$form = '<form action="/" method="POST"> | |
<div> | |
<label for="pin">PIN:</label> | |
<input type="text" name="pin" id="pin" inputmode="numeric" pattern="[0-9]{6}" autocomplete="off"> | |
</div> | |
<button type="submit">Check</button> | |
</form>'; | |
break; | |
} | |
} | |
return $form; | |
} | |
public function homescreen() | |
{ | |
$result = $this->curl('https://rest-prod.immedia-semi.com/api/v3/accounts/'.$this->account['account']['account_id'].'/homescreen', [], [ | |
'Host: '.$this->account['region'], | |
'Token-auth: '.$this->account['auth']['token'], | |
], 'GET'); | |
foreach(['networks', 'sync_modules', 'cameras', 'doorbells', 'device_limits'] as $name) | |
{ | |
if(isset($result[$name])) | |
{ | |
$this->{$name} = $result[$name]; | |
} | |
} | |
return $result; | |
} | |
public function is($type, $data = []) | |
{ | |
if(is_array($type)) | |
{ | |
foreach($type as $t) | |
{ | |
if(!$this->is($t, $data)) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
else | |
{ | |
switch($type) | |
{ | |
case 'account': | |
case 'verify': { | |
return (array_key_exists($type, $this->account) ? true : false); | |
} | |
case 'cli': | |
case 'curl': | |
case 'wget': | |
case 'cron': { | |
return (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('~^(curl|wget)~i', $_SERVER['HTTP_USER_AGENT']) || php_sapi_name() == 'cli'); | |
} | |
case 'json': { | |
$json = json_decode($data, true); | |
return ($json && $data != $json); | |
} | |
case 'telegram': { | |
return (substr($_SERVER['REMOTE_ADDR'], 0, 7) == '91.108.' || isset($_SERVER['HTTP_USER_AGENT']) && preg_match('~^telegram~i', $_SERVER['HTTP_USER_AGENT'])); | |
} | |
case 'temperature': { | |
return ((($data-32)*5)/9); | |
} | |
} | |
} | |
return false; | |
} | |
public function login($data = []) | |
{ | |
$email = (empty($data['email']) ? false : $data['email']); | |
$password = (empty($data['password']) ? false : $data['password']); | |
$region = (empty($data['region']) || !array_key_exists($data['region'], $this->regions) ? false : $data['region']); | |
if($email && $password && $region) | |
{ | |
$this->account = array_merge($this->account, [ | |
'credentials' => [ | |
'email' => $email, | |
'password' => $password, | |
], | |
'region' => $region | |
]); | |
$result = $this->curl('https://rest-prod.immedia-semi.com/api/v5/account/login', array_merge($this->account['credentials'], [ | |
'unique_id' => '00000000-1111-0000-1111-00000000000' | |
], [ | |
'Host: '.$this->account['region'], | |
])); | |
if(empty($result['account'])) | |
{ | |
$this->error = 'Auth'.(empty($result['message']) ? '</b>!' : ':</b> '.$result['message']); | |
return false; | |
} | |
$this->account = array_merge($this->account, $result); | |
$this->account($this->account, 'account'); | |
return $this->account; | |
} | |
return false; | |
} | |
public function logout() | |
{ | |
$this->account(false, 'account'); | |
$this->refresh(); | |
} | |
public function medias() | |
{ | |
$m = 0; | |
$p = 1; | |
while(true) | |
{ | |
$result = $this->curl('https://'.$this->account['region'].'/api/v1/accounts/'.$this->account['account']['account_id'].'/media/changed?since=2015-04-19T23:11:20+0000&page='.$p, [], [ | |
'Host: '.$this->account['region'], | |
'token-auth: '.$this->account['auth']['token'] | |
], 'GET'); | |
if(!empty($result['media'])) | |
{ | |
foreach($result['media'] as $media) | |
{ | |
$dir = $this->storage.'/'.$media['device_name'].'/'.date('YmdA', strtotime($media['created_at'])); | |
if(!is_dir($dir)) | |
{ | |
mkdir($dir, 0755, true); | |
} | |
$source = ''; | |
if(in_array($media['source'], ['button_press', 'pir', 'snapshot'])) | |
{ | |
$sources = ['button_press' => 'button', 'liveview' => 'watching', 'pir' => 'motion']; | |
$source = '-'.(array_key_exists($media['source'], $sources) ? $sources[$media['source']] : $media['source']); | |
} | |
$type = '.'.substr($media['media'], strrpos($media['media'], '.')+1); | |
$file = $dir.'/'.$media['device_name'].'-'.date('YmdA', strtotime($media['created_at'])).'-'.date('His', strtotime($media['created_at'])).'-'.strtotime($media['created_at']).$source.$type; | |
if(!is_file($file)) | |
{ | |
$result = $this->curl('https://'.$this->account['region'].$media['media'], [], [ | |
'Host: '.$this->account['region'], | |
'token-auth: '.$this->account['auth']['token'] | |
], 'GET', $file); | |
file_put_contents($file, $result); | |
touch($file, strtotime($media['created_at'])); | |
$m++; | |
} | |
} | |
$p++; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
return $m; | |
} | |
public function redirect($uri) | |
{ | |
header('Location: '.$uri); | |
exit(); | |
} | |
public function refresh() | |
{ | |
return $this->redirect('/'); | |
} | |
public function regions() | |
{ | |
$result = $this->curl('https://rest-prod.immedia-semi.com/regions', [], [], 'GET'); | |
if(!empty($result['regions'])) | |
{ | |
foreach($result['regions'] as $region => $name) | |
{ | |
$this->regions['rest-'.$region.'.immedia-semi.com'] = ucwords(strtolower($name['friendly_name']), '- '); | |
} | |
$this->regions['rest-prod.immedia-semi.com'] = 'General'; | |
ksort($this->regions); | |
return $this->regions; | |
} | |
return false; | |
} | |
public function telegram($message = false, $chat = null, $reply = null) | |
{ | |
$chat = (is_null($chat) ? $this->telegram['chat'] : $chat); | |
$message = (empty($message) ? false : $message); | |
return $this->curl('https://api.telegram.org/bot'.$this->telegram['bot'].'/sendMessage', [ | |
'chat_id' => $this->telegram['chat'], | |
'parse_mode' => 'HTML', | |
'reply_to_message_id' => (is_null($reply) ? '' : $reply), | |
'text' => $message | |
], [ | |
'Content-Type: application/json' | |
]); | |
} | |
public function verify($data = []) | |
{ | |
if($this->is('telegram')) | |
{ | |
$this->account(null, 'account'); | |
} | |
$result = $this->curl('https://'.$this->account['region'].'/api/v4/account/'.$this->account['account']['account_id'].'/client/'.$this->account['account']['client_id'].'/pin/verify', [ | |
'pin' => $data['pin'] | |
], [ | |
'Host: '.$this->account['region'], | |
'token-auth: '.$this->account['auth']['token'] | |
]); | |
if(empty($result['valid'])) | |
{ | |
$this->error = 'Verify'.(empty($result['message']) ? '</b>!' : ':</b> '.$result['message']); | |
return false; | |
} | |
$this->account = array_merge($this->account, ['verify' => $data['pin']]); | |
$this->account($this->account, 'account'); | |
return $this->account; | |
} | |
} | |
$blink = (new \Blink()); | |
if($blink->is('cron')) | |
{ | |
if($blink->login(array_merge($blink->account['credentials'], ['region' => $blink->account['region']]))) | |
{ | |
$blink->telegram('What\'s Amazon\'s Blink verificationcode?'); | |
} | |
} | |
elseif($blink->is('telegram')) | |
{ | |
$data = json_decode(file_get_contents('php://input'), true); | |
if(is_array($data)) | |
{ | |
$chat = $data['message']['chat']['id']; | |
$reply = $data['message']['message_id']; | |
$text = trim($data['message']['text']); | |
if(preg_match('~^([0-9]{6})$~', $text)) | |
{ | |
$verify = $blink->verify(['pin' => $text]); | |
if($verify) | |
{ | |
if($blink->homescreen()) | |
{ | |
$medias = $blink->medias(); | |
$blink->telegram($medias.' files downloaded!', $chat, $reply); | |
} | |
else | |
{ | |
$blink->telegram('Can\'t find any file..', $chat, $reply); | |
} | |
} | |
else | |
{ | |
$blink->telegram('Code "'.$text.'" is incorrect, try again?', $chat, $reply); | |
} | |
} | |
else | |
{ | |
switch($text) | |
{ | |
case '/blink': { | |
if($blink->login(array_merge($blink->account['credentials'], ['region' => $blink->account['region']]))) | |
{ | |
$blink->telegram('What\'s Amazon\'s Blink verificationcode?', $chat, $reply); | |
} | |
break; | |
} | |
} | |
} | |
} | |
} | |
else | |
{ ?> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Sync Blink 2 Synology</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
h1 { | |
font-size: 1.5em; | |
color: #525252; | |
} | |
body { | |
font-family: 'Open Sans', sans-serif; | |
background: #3498db; | |
margin: 0 auto; | |
width: 100%; | |
text-align: center; | |
margin: 20px 0px 20px 0px; | |
} | |
.box { | |
background: #fff; | |
width: 300px; | |
border-radius: 6px; | |
margin: 0 auto 0 auto; | |
padding: 0px 0px 10px 0px; | |
border: #2980b9 4px solid; | |
} | |
.footer { | |
padding: 5px; | |
text-align: left; | |
} | |
button { | |
background: #3498db; | |
width: 90%; | |
padding: 8px; | |
color: white; | |
border-radius: 4px; | |
border: #3498db 1px solid; | |
margin: 25px auto 0px; | |
font-weight: bold; | |
font-size: 0.8em; | |
} | |
button:hover { | |
background: #fff; | |
color: #3498db; | |
cursor: pointer; | |
} | |
input, select { | |
border: #ccc 1px solid; | |
border-bottom: #ccc 2px solid; | |
padding: 8px; | |
width: 250px; | |
margin-top: 10px; | |
font-size: 1em; | |
border-radius: 4px; | |
} | |
select { | |
width: 265px; | |
} | |
input:focus, select:focus { | |
background: #eee; | |
} | |
label { | |
display: inline-block; | |
padding: 8px; | |
width: 250px; | |
font-size: 1em; | |
font-weight: bold; | |
text-align: left !important; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="box"> | |
<h1>Sync Blink 2 Synology</h1> | |
<?php | |
if(isset($_GET['logout'])) | |
{ | |
$blink->logout(); | |
} | |
elseif(isset($_REQUEST['email'], $_REQUEST['password'], $_REQUEST['region'])) | |
{ | |
if($blink->login($_REQUEST)) | |
{ | |
echo $blink->form('pin'); | |
} | |
else | |
{ | |
echo $blink->form('login'); | |
} | |
} | |
elseif(isset($_REQUEST['pin'])) | |
{ | |
if($blink->verify($_REQUEST)) | |
{ | |
$blink->refresh(); | |
} | |
else | |
{ | |
echo $blink->form('pin'); | |
} | |
} | |
else | |
{ | |
if($blink->is(['account', 'verify'])) | |
{ | |
if($blink->homescreen()) | |
{ | |
$medias = $blink->medias(); | |
echo $medias.' files downloaded!'; | |
} | |
} | |
else | |
{ | |
echo $blink->form('login'); | |
} | |
} | |
?> | |
<hr> | |
<div class="footer"> | |
<b>Error <?=(is_null($blink->error) ? ':</b> -' : $blink->error);?> | |
</div> | |
<?php | |
if(isset($blink->account['auth'])) | |
{ | |
echo '<p><a href="/?logout">Logout</a></p>'; | |
} | |
?> | |
</div> | |
</body> | |
</html> | |
<?php } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment