Beispiel #1
0
    def __init__(
        self,
        refresh_rate=DEFAULT_REFRESH,
        motion_interval=DEFAULT_MOTION_INTERVAL,
        no_owls=False,
    ):
        """
        Initialize Blink system.

        :param refresh_rate: Refresh rate of blink information.
                             Defaults to 15 (seconds)
        :param motion_interval: How far back to register motion in minutes.
                                Defaults to last refresh time.
                                Useful for preventing motion_detected property
                                from de-asserting too quickly.
        :param no_owls: Disable searching for owl entries (blink mini cameras only known entity).  Prevents an uneccessary API call if you don't have these in your network.
        """
        self.auth = Auth()
        self.account_id = None
        self.client_id = None
        self.network_ids = []
        self.urls = None
        self.sync = CaseInsensitiveDict({})
        self.last_refresh = None
        self.refresh_rate = refresh_rate
        self.networks = []
        self.cameras = CaseInsensitiveDict({})
        self.video_list = CaseInsensitiveDict({})
        self.motion_interval = motion_interval
        self.version = __version__
        self.available = False
        self.key_required = False
        self.homescreen = {}
        self.no_owls = no_owls
Beispiel #2
0
    def reauth(self):
        self.blink = Blink()

        creds = self.blink_location / "creds.json"
        started = False

        print("Logging in to Blink...")
        if creds.is_file():
            auth = Auth(json_load(creds))
            auth.no_prompt = True
            self.blink.auth = auth
            started = self.blink.start()

        return started
Beispiel #3
0
 def test_empty_init(self, getpwd, genuid):
     """Test initialization with no params."""
     auth = Auth()
     self.assertDictEqual(auth.data, {})
     getpwd.return_value = "bar"
     genuid.return_value = 1234
     with mock.patch("builtins.input", return_value="foo"):
         auth.validate_login()
     expected_data = {
         "username": "******",
         "password": "******",
         "uid": 1234,
         "device_id": const.DEVICE_ID,
     }
     self.assertDictEqual(auth.data, expected_data)
Beispiel #4
0
 def test_barebones_init(self, getpwd, genuid):
     """Test basebones initialization."""
     login_data = {"username": "******", "password": "******"}
     auth = Auth(login_data)
     self.assertDictEqual(auth.data, login_data)
     getpwd.return_value = "bar"
     genuid.return_value = 1234
     with mock.patch("builtins.input", return_value="foo"):
         auth.validate_login()
     expected_data = {
         "username": "******",
         "password": "******",
         "uid": 1234,
         "device_id": const.DEVICE_ID,
     }
     self.assertDictEqual(auth.data, expected_data)
Beispiel #5
0
    async def async_step_user(self, user_input=None):
        """Handle a flow initiated by the user."""
        errors = {}
        data = {CONF_USERNAME: "", CONF_PASSWORD: "", "device_id": DEVICE_ID}
        if user_input is not None:
            data[CONF_USERNAME] = user_input["username"]
            data[CONF_PASSWORD] = user_input["password"]

            self.auth = Auth(data, no_prompt=True)
            await self.async_set_unique_id(data[CONF_USERNAME])

            try:
                await self.hass.async_add_executor_job(validate_input,
                                                       self.hass, self.auth)
                return self._async_finish_flow()
            except Require2FA:
                return await self.async_step_2fa()
            except InvalidAuth:
                errors["base"] = "invalid_auth"
            except Exception:  # pylint: disable=broad-except
                _LOGGER.exception("Unexpected exception")
                errors["base"] = "unknown"

        data_schema = {
            vol.Required("username"): str,
            vol.Required("password"): str,
        }

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema(data_schema),
            errors=errors,
        )
def blink_video_schedule(event, context):
    """Triggered from a message on a Cloud Pub/Sub topic.
    Args:
         event (dict): Event payload.
         context (google.cloud.functions.Context): Metadata for the event."""

    FILENAME = 'blink_creds.json'

    #FILENAME = re.sub(r"\/.*\/(.*\.\w{1,4}",r'\1',FILE)
    #BLOB_UPLOAD = BLINK_BUCKET.blob(f"{create_file_path()[1:]}/{FILENAME}") #Set filename format (uploads/year/month/filename).
    #BLOB_UPLOAD.upload_from_filename(FILE)

    USER_NAME, PASSWORD = load_credentials()

    AUTH = Auth({"username": USER_NAME, "password": PASSWORD}, no_prompt=True)

    pubsub_message = base64.b64decode(event['data']).decode('utf-8')
    if pubsub_message == 'RUN':
        blink = Blink()
        blink.auth = AUTH
        blink.start()
        AUTH.send_auth_key(blink, '167363')
        blink.setup_post_verify()

        #print(type(blink.save(f'{FILENAME}')))

        CREDS = json_load("blink_creds.json")

        blob_blink = BLINK_BUCKET.blob('blink_creds.json')

        blob_blink.upload_from_string(data=json.dumps(CREDS),
                                      content_type='application/json')

        print('i am before the cameras')
        print(blink.cameras.items())
        try:
            for name, camera in blink.cameras.items():
                print('i am inside the camera')
                print(name)  # Name of the camera
                print(
                    camera.attributes)  # Print available attributes of camera
        except ValueError:
            print('there is some error')

        blink.download_videos(since='2018/07/04 09:34')
        return "SUCCESS"
Beispiel #7
0
def start_blink_session(
    blink_config_file: str, blink_username, blink_password
) -> (bool, object, object):
    """Starts a blink cam session

    :param blink_config_file: blink session config file path
    :type blink_config_file: string
    :param blink_username: blink username
    :type blink_username: string
    :param blink_password: blink password
    :type blink_password: string
    :return: authentication_success for existing session or 2FA token required, blink instance, auth instance
    :rtype authentication_success: boolean
    :rtype blink: class
    :rtype auth: class
    """
    blink = Blink(refresh_rate=3)

    if os.path.exists(blink_config_file):
        logger.info("using existing blink_config.json")
        auth = Auth(json_load(blink_config_file), no_prompt=True)
        authentication_success = True
    else:
        logger.info(
            "no blink_config.json found - 2FA " + "authentication token required"
        )
        auth = Auth(
            {"username": blink_username, "password": blink_password}, no_prompt=True
        )
        authentication_success = None

    blink.auth = auth
    opts = {"retries": 10, "backoff": 2}
    blink.auth.session = blink.auth.create_session(opts=opts)
    try:
        logger.info("start blink session")
        blink.start()
    except Exception as err:
        logger.info("blink session exception occured: {0}".format(err))
        pass

    return authentication_success, blink, auth
Beispiel #8
0
    def get(self, force_reset=False):
        if force_reset or not self._auth_file_exists():
            self.blink = self._refresh_auth(reset=True)
        else:
            self.blink.auth = Auth(login_data=json_load(self.auth_file), no_prompt=True)
            self.blink.start()

            if not self.blink.available:
                return self._refresh_auth(reset=False)

        return self.blink
Beispiel #9
0
    def _refresh_auth(self, reset=False):
        self.blink = Blink()
        with_sleep = False

        if not reset and self._auth_file_exists():
            self.blink.auth = Auth(json_load(self.auth_file))
            with_sleep = True
        else:
            self.blink.auth = Auth()

        self.blink.start()

        # write auth file
        self.blink.save(self.auth_file)
        print('Auth file updated: ' + self.auth_file)

        if with_sleep:
            time.sleep(3)

        return self.get(force_reset=False)
Beispiel #10
0
def InitBlink():
    global blinkStatus, statusMessage
    if not os.path.exists(blinkCredentials):
        blinkStatus = -1
        statusMessage ='Blink credential file '+blinkCredentials+' does not exist.'
    else:
        auth = Auth(json_load(blinkCredentials))
        blink.auth = auth
        blink.start()
        blinkStatus = 1
        statusMessage = 'Blink started ok'
    print ('Initblink finished: '+statusMessage)
Beispiel #11
0
 def test_full_init(self):
     """Test full initialization."""
     login_data = {
         "username": "******",
         "password": "******",
         "token": "token",
         "host": "host",
         "region_id": "region_id",
         "client_id": "client_id",
         "account_id": "account_id",
         "uid": 1234,
         "notification_key": 4321,
         "device_id": "device_id",
     }
     auth = Auth(login_data)
     self.assertEqual(auth.token, "token")
     self.assertEqual(auth.host, "host")
     self.assertEqual(auth.region_id, "region_id")
     self.assertEqual(auth.client_id, "client_id")
     self.assertEqual(auth.account_id, "account_id")
     auth.validate_login()
     self.assertDictEqual(auth.login_attributes, login_data)
Beispiel #12
0
def _blink_startup_wrapper(hass, entry):
    """Startup wrapper for blink."""
    blink = Blink()
    auth_data = deepcopy(dict(entry.data))
    blink.auth = Auth(auth_data, no_prompt=True)
    blink.refresh_rate = entry.options.get(CONF_SCAN_INTERVAL,
                                           DEFAULT_SCAN_INTERVAL)

    if blink.start():
        blink.setup_post_verify()
    elif blink.auth.check_key_required():
        _LOGGER.debug("Attempting a reauth flow")
        _reauth_flow_wrapper(hass, auth_data)

    return blink
Beispiel #13
0
def start():
    """Startup blink app."""
    blink = Blink()
    blink.auth = Auth(json_load(CREDFILE))
    blink.start()
    return blink
Beispiel #14
0
OFFSET_SECONDS = 60
TIMEOUT_SECONDS = 60
NOTIFY_ENTITY_NAMES = ["mobile_app_dullage_s_iphone", "mobile_app_iphone"]
# NOTIFY_ENTITY_NAMES = ["mobile_app_dullage_s_iphone"]  # Debug

VIDEO_FILE = os.path.join(SAVE_PATH, VIDEO_FILENAME)
IMAGE_FILE = os.path.join(SAVE_PATH, IMAGE_FILENAME)

with open(SECRETS_FILE, "r") as secrets_file:
    secrets = yaml.load(secrets_file)

blink = blinkpy.Blink()
auth = Auth(
    {
        "username": secrets["blinkUsername"],
        "password": secrets["blinkPassword"],
        "device_id": "Doorbell Script",
    },
    no_prompt=True,
)
blink.auth = auth
blink.start()

from_time = time.time() - OFFSET_SECONDS
loop_start = datetime.today()
while True:
    videos = api.request_videos(blink, time=from_time)["media"]

    if len(videos) >= 1:
        break

    if datetime.today() >= (loop_start + timedelta(seconds=TIMEOUT_SECONDS)):
Beispiel #15
0
try:
    username = data["username"]
    password = data["password"]
except KeyError:
    print(f" ... File contents of {auth_file} incorrect.")
    print(" ... Require username and password at minimum.")
    print(" ... Exiting.")
    sys.exit(1)

if save_session:
    print(f" ... Saving session file to {session_path}.")
    json_save({"file": auth_file}, session_path)

blink = Blink()
auth = Auth(data)
blink.auth = auth

print(" ... Starting Blink.")
print("")
blink.start()
print("")
print(" ... Printing login response.")
print("")
print(blink.auth.login_response)
print("")
print(" ... Printing login attributes.")
print("")
print(blink.auth.login_attributes)
print("")
input(" ... Press any key to continue: ")
Beispiel #16
0
from html.parser import HTMLParser
from urllib.request import urlopen
import json, time
from blinkpy.blinkpy import Blink
from blinkpy.auth import Auth

blink = Blink()
# https://pypi.org/project/blinkpy/
auth = Auth(
    {
        "username": "******",
        "password": "******"
    },
    no_prompt=False)
trusted_mac_addresses = ["aa:bb:cc:dd:ee:ff"]
blink.auth = auth
blink.start()

# all this shit below here is to parse my router's device list properly. i love proper object notation, and tried to do this without regex. ;p
in_table = False
this_device = []
row_name = ""
last_tag = ""
device_list = {}


class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        global in_table, this_device, row_name, last_tag, device_list
        if tag == "table":
            in_table = True
Beispiel #17
0
 def setUp(self):
     """Set up Login Handler."""
     self.auth = Auth()
Beispiel #18
0
class TestAuth(unittest.TestCase):
    """Test the Auth class in blinkpy."""
    def setUp(self):
        """Set up Login Handler."""
        self.auth = Auth()

    def tearDown(self):
        """Clean up after test."""
        self.auth = None

    @mock.patch("blinkpy.helpers.util.gen_uid")
    @mock.patch("blinkpy.auth.util.getpass")
    def test_empty_init(self, getpwd, genuid):
        """Test initialization with no params."""
        auth = Auth()
        self.assertDictEqual(auth.data, {})
        getpwd.return_value = "bar"
        genuid.return_value = 1234
        with mock.patch("builtins.input", return_value="foo"):
            auth.validate_login()
        expected_data = {
            "username": "******",
            "password": "******",
            "uid": 1234,
            "device_id": const.DEVICE_ID,
        }
        self.assertDictEqual(auth.data, expected_data)

    @mock.patch("blinkpy.helpers.util.gen_uid")
    @mock.patch("blinkpy.auth.util.getpass")
    def test_barebones_init(self, getpwd, genuid):
        """Test basebones initialization."""
        login_data = {"username": "******", "password": "******"}
        auth = Auth(login_data)
        self.assertDictEqual(auth.data, login_data)
        getpwd.return_value = "bar"
        genuid.return_value = 1234
        with mock.patch("builtins.input", return_value="foo"):
            auth.validate_login()
        expected_data = {
            "username": "******",
            "password": "******",
            "uid": 1234,
            "device_id": const.DEVICE_ID,
        }
        self.assertDictEqual(auth.data, expected_data)

    def test_full_init(self):
        """Test full initialization."""
        login_data = {
            "username": "******",
            "password": "******",
            "token": "token",
            "host": "host",
            "region_id": "region_id",
            "client_id": "client_id",
            "account_id": "account_id",
            "uid": 1234,
            "notification_key": 4321,
            "device_id": "device_id",
        }
        auth = Auth(login_data)
        self.assertEqual(auth.token, "token")
        self.assertEqual(auth.host, "host")
        self.assertEqual(auth.region_id, "region_id")
        self.assertEqual(auth.client_id, "client_id")
        self.assertEqual(auth.account_id, "account_id")
        auth.validate_login()
        self.assertDictEqual(auth.login_attributes, login_data)

    def test_bad_response_code(self):
        """Check bad response code from server."""
        self.auth.is_errored = False
        fake_resp = mresp.MockResponse({"code": 404}, 404)
        with self.assertRaises(exceptions.ConnectionError):
            self.auth.validate_response(fake_resp, True)
        self.assertTrue(self.auth.is_errored)

        self.auth.is_errored = False
        fake_resp = mresp.MockResponse({"code": 101}, 401)
        with self.assertRaises(UnauthorizedError):
            self.auth.validate_response(fake_resp, True)
        self.assertTrue(self.auth.is_errored)

    def test_good_response_code(self):
        """Check good response code from server."""
        fake_resp = mresp.MockResponse({"foo": "bar"}, 200)
        self.auth.is_errored = True
        self.assertEqual(self.auth.validate_response(fake_resp, True),
                         {"foo": "bar"})
        self.assertFalse(self.auth.is_errored)

    def test_response_not_json(self):
        """Check response when not json."""
        fake_resp = "foobar"
        self.auth.is_errored = True
        self.assertEqual(self.auth.validate_response(fake_resp, False),
                         "foobar")
        self.assertFalse(self.auth.is_errored)

    def test_response_bad_json(self):
        """Check response when not json but expecting json."""
        self.auth.is_errored = False
        with self.assertRaises(BlinkBadResponse):
            self.auth.validate_response(None, True)
        self.assertTrue(self.auth.is_errored)

    def test_header(self):
        """Test header data."""
        self.auth.token = "bar"
        expected_header = {
            "TOKEN_AUTH": "bar",
            "user-agent": const.DEFAULT_USER_AGENT,
            "content-type": "application/json",
        }
        self.assertDictEqual(self.auth.header, expected_header)

    def test_header_no_token(self):
        """Test header without token."""
        self.auth.token = None
        self.assertEqual(self.auth.header, None)

    @mock.patch("blinkpy.auth.Auth.validate_login", return_value=None)
    @mock.patch("blinkpy.auth.api.request_login")
    def test_login(self, mock_req, mock_validate):
        """Test login handling."""
        fake_resp = mresp.MockResponse({"foo": "bar"}, 200)
        mock_req.return_value = fake_resp
        self.assertEqual(self.auth.login(), {"foo": "bar"})

    @mock.patch("blinkpy.auth.Auth.validate_login", return_value=None)
    @mock.patch("blinkpy.auth.api.request_login")
    def test_login_bad_response(self, mock_req, mock_validate):
        """Test login handling when bad response."""
        fake_resp = mresp.MockResponse({"foo": "bar"}, 404)
        mock_req.return_value = fake_resp
        self.auth.is_errored = False
        with self.assertRaises(LoginError):
            self.auth.login()
        with self.assertRaises(TokenRefreshFailed):
            self.auth.refresh_token()
        self.assertTrue(self.auth.is_errored)

    @mock.patch("blinkpy.auth.Auth.login")
    def test_refresh_token(self, mock_login):
        """Test refresh token method."""
        mock_login.return_value = {
            "account": {
                "account_id": 5678,
                "client_id": 1234,
                "tier": "test"
            },
            "auth": {
                "token": "foobar"
            },
        }
        self.assertTrue(self.auth.refresh_token())
        self.assertEqual(self.auth.region_id, "test")
        self.assertEqual(self.auth.token, "foobar")
        self.assertEqual(self.auth.client_id, 1234)
        self.assertEqual(self.auth.account_id, 5678)

    @mock.patch("blinkpy.auth.Auth.login")
    def test_refresh_token_failed(self, mock_login):
        """Test refresh token failed."""
        mock_login.return_value = {}
        self.auth.is_errored = False
        with self.assertRaises(TokenRefreshFailed):
            self.auth.refresh_token()
        self.assertTrue(self.auth.is_errored)

    def test_check_key_required(self):
        """Check key required method."""
        self.auth.login_response = {}
        self.assertFalse(self.auth.check_key_required())

        self.auth.login_response = {
            "account": {
                "client_verification_required": False
            }
        }
        self.assertFalse(self.auth.check_key_required())

        self.auth.login_response = {
            "account": {
                "client_verification_required": True
            }
        }
        self.assertTrue(self.auth.check_key_required())

    @mock.patch("blinkpy.auth.api.request_logout")
    def test_logout(self, mock_req):
        """Test logout method."""
        mock_blink = MockBlink(None)
        mock_req.return_value = True
        self.assertTrue(self.auth.logout(mock_blink))

    @mock.patch("blinkpy.auth.api.request_verify")
    def test_send_auth_key(self, mock_req):
        """Check sending of auth key."""
        mock_blink = MockBlink(None)
        mock_req.return_value = mresp.MockResponse({"valid": True}, 200)
        self.assertTrue(self.auth.send_auth_key(mock_blink, 1234))
        self.assertTrue(mock_blink.available)

        mock_req.return_value = mresp.MockResponse(None, 200)
        self.assertFalse(self.auth.send_auth_key(mock_blink, 1234))

        mock_req.return_value = mresp.MockResponse({}, 200)
        self.assertFalse(self.auth.send_auth_key(mock_blink, 1234))

        self.assertTrue(self.auth.send_auth_key(mock_blink, None))

    @mock.patch("blinkpy.auth.api.request_verify")
    def test_send_auth_key_fail(self, mock_req):
        """Check handling of auth key failure."""
        mock_blink = MockBlink(None)
        mock_req.return_value = mresp.MockResponse(None, 200)
        self.assertFalse(self.auth.send_auth_key(mock_blink, 1234))
        mock_req.return_value = mresp.MockResponse({}, 200)
        self.assertFalse(self.auth.send_auth_key(mock_blink, 1234))

    @mock.patch("blinkpy.auth.Auth.validate_response")
    @mock.patch("blinkpy.auth.Auth.refresh_token")
    def test_query_retry(self, mock_refresh, mock_validate):
        """Check handling of request retry."""
        self.auth.session = MockSession()
        mock_validate.side_effect = [UnauthorizedError, "foobar"]
        mock_refresh.return_value = True
        self.assertEqual(self.auth.query(url="http://example.com"), "foobar")

    @mock.patch("blinkpy.auth.Auth.validate_response")
    @mock.patch("blinkpy.auth.Auth.refresh_token")
    def test_query_retry_failed(self, mock_refresh, mock_validate):
        """Check handling of failed retry request."""
        self.auth.session = MockSession()
        mock_validate.side_effect = [UnauthorizedError, BlinkBadResponse]
        mock_refresh.return_value = True
        self.assertEqual(self.auth.query(url="http://example.com"), None)

        mock_validate.side_effect = [UnauthorizedError, TokenRefreshFailed]
        self.assertEqual(self.auth.query(url="http://example.com"), None)

    def test_default_session(self):
        """Test default session creation."""
        sess = self.auth.create_session()
        adapter = sess.adapters["https://"]
        self.assertEqual(adapter.max_retries.total, 3)
        self.assertEqual(adapter.max_retries.backoff_factor, 1)
        self.assertEqual(adapter.max_retries.status_forcelist,
                         [429, 500, 502, 503, 504])

    def test_custom_session_full(self):
        """Test full custom session creation."""
        opts = {"backoff": 2, "retries": 10, "retry_list": [404]}
        sess = self.auth.create_session(opts=opts)
        adapter = sess.adapters["https://"]
        self.assertEqual(adapter.max_retries.total, 10)
        self.assertEqual(adapter.max_retries.backoff_factor, 2)
        self.assertEqual(adapter.max_retries.status_forcelist, [404])

    def test_custom_session_partial(self):
        """Test partial custom session creation."""
        opts1 = {"backoff": 2}
        opts2 = {"retries": 5}
        opts3 = {"retry_list": [101, 202]}
        sess1 = self.auth.create_session(opts=opts1)
        sess2 = self.auth.create_session(opts=opts2)
        sess3 = self.auth.create_session(opts=opts3)
        adapt1 = sess1.adapters["https://"]
        adapt2 = sess2.adapters["https://"]
        adapt3 = sess3.adapters["https://"]

        self.assertEqual(adapt1.max_retries.total, 3)
        self.assertEqual(adapt1.max_retries.backoff_factor, 2)
        self.assertEqual(adapt1.max_retries.status_forcelist,
                         [429, 500, 502, 503, 504])

        self.assertEqual(adapt2.max_retries.total, 5)
        self.assertEqual(adapt2.max_retries.backoff_factor, 1)
        self.assertEqual(adapt2.max_retries.status_forcelist,
                         [429, 500, 502, 503, 504])

        self.assertEqual(adapt3.max_retries.total, 3)
        self.assertEqual(adapt3.max_retries.backoff_factor, 1)
        self.assertEqual(adapt3.max_retries.status_forcelist, [101, 202])
    return user_name, password

def create_file_path():
	year = datetime.datetime.now().strftime("%Y")
	month = datetime.datetime.now().strftime("%m")
	file_path = f'/uploads/{year}/{month}'
	return file_path

FILENAME= 'blink_creds.json'
#FILENAME = re.sub(r"\/.*\/(.*\.\w{1,4}",r'\1',FILE)
#BLOB_UPLOAD = BLINK_BUCKET.blob(f"{create_file_path()[1:]}/{FILENAME}") #Set filename format (uploads/year/month/filename).
#BLOB_UPLOAD.upload_from_filename(FILE)

USER_NAME, PASSWORD = load_credentials()

AUTH = Auth({"username": USER_NAME, "password": PASSWORD}, no_prompt=True)

blink = Blink()
blink.auth = AUTH
blink.start()
AUTH.send_auth_key(blink, '399587')
blink.setup_post_verify()



print(type(blink.save(f'{FILENAME}')))

CREDS = json_load("blink_creds.json")

blob_blink = BLINK_BUCKET.blob('blink_creds.json')
Beispiel #20
0
class Blink:
    """Class to initialize communication."""

    def __init__(
        self,
        refresh_rate=DEFAULT_REFRESH,
        motion_interval=DEFAULT_MOTION_INTERVAL,
        no_owls=False,
    ):
        """
        Initialize Blink system.

        :param refresh_rate: Refresh rate of blink information.
                             Defaults to 15 (seconds)
        :param motion_interval: How far back to register motion in minutes.
                                Defaults to last refresh time.
                                Useful for preventing motion_detected property
                                from de-asserting too quickly.
        :param no_owls: Disable searching for owl entries (blink mini cameras only known entity).  Prevents an uneccessary API call if you don't have these in your network.
        """
        self.auth = Auth()
        self.account_id = None
        self.client_id = None
        self.network_ids = []
        self.urls = None
        self.sync = CaseInsensitiveDict({})
        self.last_refresh = None
        self.refresh_rate = refresh_rate
        self.networks = []
        self.cameras = CaseInsensitiveDict({})
        self.video_list = CaseInsensitiveDict({})
        self.motion_interval = motion_interval
        self.version = __version__
        self.available = False
        self.key_required = False
        self.homescreen = {}
        self.no_owls = no_owls

    @util.Throttle(seconds=MIN_THROTTLE_TIME)
    def refresh(self, force=False, force_cache=False):
        """
        Perform a system refresh.

        :param force: Used to override throttle, resets refresh
        :param force_cache: Used to force update without overriding throttle
        """
        if self.check_if_ok_to_update() or force or force_cache:
            if not self.available:
                self.setup_post_verify()

            self.get_homescreen()
            for sync_name, sync_module in self.sync.items():
                _LOGGER.debug("Attempting refresh of sync %s", sync_name)
                sync_module.refresh(force_cache=(force or force_cache))
            if not force_cache:
                # Prevents rapid clearing of motion detect property
                self.last_refresh = int(time.time())
            return True
        return False

    def start(self):
        """Perform full system setup."""
        try:
            self.auth.startup()
            self.setup_login_ids()
            self.setup_urls()
            self.get_homescreen()
        except (LoginError, TokenRefreshFailed, BlinkSetupError):
            _LOGGER.error("Cannot setup Blink platform.")
            self.available = False
            return False

        self.key_required = self.auth.check_key_required()
        if self.key_required:
            if self.auth.no_prompt:
                return True
            self.setup_prompt_2fa()
        return self.setup_post_verify()

    def setup_prompt_2fa(self):
        """Prompt for 2FA."""
        email = self.auth.data["username"]
        pin = input(f"Enter code sent to {email}: ")
        result = self.auth.send_auth_key(self, pin)
        self.key_required = not result

    def setup_post_verify(self):
        """Initialize blink system after verification."""
        try:
            self.setup_networks()
            networks = self.setup_network_ids()
            cameras = self.setup_camera_list()
        except BlinkSetupError:
            self.available = False
            return False

        for name, network_id in networks.items():
            sync_cameras = cameras.get(network_id, {})
            self.setup_sync_module(name, network_id, sync_cameras)

        self.cameras = self.merge_cameras()

        self.available = True
        self.key_required = False
        return True

    def setup_sync_module(self, name, network_id, cameras):
        """Initialize a sync module."""
        self.sync[name] = BlinkSyncModule(self, name, network_id, cameras)
        self.sync[name].start()

    def get_homescreen(self):
        """Get homecreen information."""
        if self.no_owls:
            _LOGGER.debug("Skipping owl extraction.")
            self.homescreen = {}
            return
        self.homescreen = api.request_homescreen(self)

    def setup_owls(self):
        """Check for mini cameras."""
        network_list = []
        camera_list = []
        try:
            for owl in self.homescreen["owls"]:
                name = owl["name"]
                network_id = str(owl["network_id"])
                if network_id in self.network_ids:
                    camera_list.append(
                        {network_id: {"name": name, "id": network_id, "type": "mini"}}
                    )
                    continue
                if owl["onboarded"]:
                    network_list.append(str(network_id))
                    self.sync[name] = BlinkOwl(self, name, network_id, owl)
                    self.sync[name].start()
        except KeyError:
            # No sync-less devices found
            pass

        self.network_ids.extend(network_list)
        return camera_list

    def setup_camera_list(self):
        """Create camera list for onboarded networks."""
        all_cameras = {}
        response = api.request_camera_usage(self)
        try:
            for network in response["networks"]:
                camera_network = str(network["network_id"])
                if camera_network not in all_cameras:
                    all_cameras[camera_network] = []
                for camera in network["cameras"]:
                    all_cameras[camera_network].append(
                        {"name": camera["name"], "id": camera["id"]}
                    )
            mini_cameras = self.setup_owls()
            for camera in mini_cameras:
                for network, camera_info in camera.items():
                    all_cameras[network].append(camera_info)
            return all_cameras
        except (KeyError, TypeError):
            _LOGGER.error("Unable to retrieve cameras from response %s", response)
            raise BlinkSetupError

    def setup_login_ids(self):
        """Retrieve login id numbers from login response."""
        self.client_id = self.auth.client_id
        self.account_id = self.auth.account_id

    def setup_urls(self):
        """Create urls for api."""
        try:
            self.urls = util.BlinkURLHandler(self.auth.region_id)
        except TypeError:
            _LOGGER.error(
                "Unable to extract region is from response %s", self.auth.login_response
            )
            raise BlinkSetupError

    def setup_networks(self):
        """Get network information."""
        response = api.request_networks(self)
        try:
            self.networks = response["summary"]
        except (KeyError, TypeError):
            raise BlinkSetupError

    def setup_network_ids(self):
        """Create the network ids for onboarded networks."""
        all_networks = []
        network_dict = {}
        try:
            for network, status in self.networks.items():
                if status["onboarded"]:
                    all_networks.append(f"{network}")
                    network_dict[status["name"]] = network
        except AttributeError:
            _LOGGER.error(
                "Unable to retrieve network information from %s", self.networks
            )
            raise BlinkSetupError

        self.network_ids = all_networks
        return network_dict

    def check_if_ok_to_update(self):
        """Check if it is ok to perform an http request."""
        current_time = int(time.time())
        last_refresh = self.last_refresh
        if last_refresh is None:
            last_refresh = 0
        if current_time >= (last_refresh + self.refresh_rate):
            return True
        return False

    def merge_cameras(self):
        """Merge all sync camera dicts into one."""
        combined = CaseInsensitiveDict({})
        for sync in self.sync:
            combined = util.merge_dicts(combined, self.sync[sync].cameras)
        return combined

    def save(self, file_name):
        """Save login data to file."""
        util.json_save(self.auth.login_attributes, file_name)

    def download_videos(
        self, path, since=None, camera="all", stop=10, delay=1, debug=False
    ):
        """
        Download all videos from server since specified time.

        :param path: Path to write files.  /path/<cameraname>_<recorddate>.mp4
        :param since: Date and time to get videos from.
                      Ex: "2018/07/28 12:33:00" to retrieve videos since
                      July 28th 2018 at 12:33:00
        :param camera: Camera name to retrieve.  Defaults to "all".
                       Use a list for multiple cameras.
        :param stop: Page to stop on (~25 items per page. Default page 10).
        :param delay: Number of seconds to wait in between subsequent video downloads.
        :param debug: Set to TRUE to prevent downloading of items.
                      Instead of downloading, entries will be printed to log.
        """
        if since is None:
            since_epochs = self.last_refresh
        else:
            parsed_datetime = parse(since, fuzzy=True)
            since_epochs = parsed_datetime.timestamp()

        formatted_date = util.get_time(time_to_convert=since_epochs)
        _LOGGER.info("Retrieving videos since %s", formatted_date)

        if not isinstance(camera, list):
            camera = [camera]

        for page in range(1, stop):
            response = api.request_videos(self, time=since_epochs, page=page)
            _LOGGER.debug("Processing page %s", page)
            try:
                result = response["media"]
                if not result:
                    raise KeyError
            except (KeyError, TypeError):
                _LOGGER.info("No videos found on page %s. Exiting.", page)
                break

            self._parse_downloaded_items(result, camera, path, delay, debug)

    def _parse_downloaded_items(self, result, camera, path, delay, debug):
        """Parse downloaded videos."""
        for item in result:
            try:
                created_at = item["created_at"]
                camera_name = item["device_name"]
                is_deleted = item["deleted"]
                address = item["media"]
            except KeyError:
                _LOGGER.info("Missing clip information, skipping...")
                continue

            if camera_name not in camera and "all" not in camera:
                _LOGGER.debug("Skipping videos for %s.", camera_name)
                continue

            if is_deleted:
                _LOGGER.debug("%s: %s is marked as deleted.", camera_name, address)
                continue

            clip_address = f"{self.urls.base_url}{address}"
            filename = f"{camera_name}-{created_at}"
            filename = f"{slugify(filename)}.mp4"
            filename = os.path.join(path, filename)

            if not debug:
                if os.path.isfile(filename):
                    _LOGGER.info("%s already exists, skipping...", filename)
                    continue

                response = api.http_get(
                    self,
                    url=clip_address,
                    stream=True,
                    json=False,
                    timeout=TIMEOUT_MEDIA,
                )
                with open(filename, "wb") as vidfile:
                    copyfileobj(response.raw, vidfile)

                _LOGGER.info("Downloaded video to %s", filename)
            else:
                print(
                    (
                        f"Camera: {camera_name}, Timestamp: {created_at}, "
                        "Address: {address}, Filename: {filename}"
                    )
                )
            if delay > 0:
                time.sleep(delay)