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 hidden or 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