removing deleted files

get_list_of_videos
Mark Cotton 2023-07-19 23:08:00 -06:00
parent 6cab281987
commit 66d601aaf2
8 changed files with 0 additions and 1409 deletions

View File

@ -1,569 +0,0 @@
import json
import logging
import requests
from .settings import *
from datetime import datetime, timedelta
from pytz import timezone
logging.basicConfig(level=logging.INFO)
class EagleEyev3():
"""
Class representing the EagleEyev3 client.
"""
def __init__(self):
"""
Initializes the EagleEyev3 client object.
"""
self.client_id = None
self.client_secret = None
self.access_token = None
self.refresh_token = None
self.redirect_uri = None
self._load_vars_from_settings()
self.user_base_url = None
self.current_user = None
self.users = []
self.bridges = []
self.cameras = []
self.switches = []
self.users = []
self.accounts = []
self.user_tz_obj = None
self.lazy_login = True
if self.lazy_login:
try:
self._load_access_token()
except FileNotFoundError as e:
logging.warn("self.lazy_login is set to {self.lazy_login} but could not find .lazy_login file to load")
def _load_vars_from_settings(self):
"""
Load variables from the settings module.
"""
self.client_id = settings.client_id
self.client_secret = settings.client_secret
self.server_protocol = settings.server_protocol
self.server_host = settings.server_host
self.server_port = settings.server_port
self.server_path = settings.server_path
# Combine server_protocol, server_host, and server_port to make the redirect_uri
# Note: Please see the note in settings.py about trailing slashes and modify this line if needed
self.redirect_uri = f"{self.server_protocol}://{self.server_host}:{self.server_port}/{self.server_path}"
def _save_access_token(self):
with open(".lazy_login", "w") as json_file:
json.dump({
'access_token': self.access_token,
'refresh_token': self.refresh_token,
'current_user': self.current_user
}, json_file)
def _load_access_token(self):
with open(".lazy_login", "r") as json_file:
saved = json.load(json_file)
if 'access_token' in saved:
self.access_token = saved['access_token']
if 'refresh_token' in saved:
self.refresh_token = saved['refresh_token']
self.get_base_url(cascade=True)
def time_now(self):
return datetime.now(tz=self.user_tz_obj).isoformat(timespec='milliseconds')
def time_before(self, ts=None, hours=6):
if ts == None:
ts = datetime.now(tz=self.user_tz_obj)
if type(ts) == str:
ts = datetime.fromisoformat(ts)
return (ts - timedelta(hours=hours)).isoformat(timespec='milliseconds')
def login_tokens(self, code=None, cascade=True):
"""
Obtains login tokens using the authorization code.
Args:
code (str): The authorization code.
cascade (bool): Indicates whether to cascade and get the base URL and current user information.
Returns:
dict: Dictionary containing the success status, response HTTP status code, data, and current user information.
"""
baseUrl = "https://auth.eagleeyenetworks.com/oauth2/token"
pathUrl = f"?grant_type=authorization_code&scope=vms.all&code={code}&redirect_uri={self.redirect_uri}" # Note the trailing slash, make sure it matches the whitelist
url = baseUrl + pathUrl
# Send a POST request to obtain login tokens
response = requests.post(url, auth=(self.client_id, self.client_secret))
response_json = response.json()
logging.info(f"{response.status_code} in login_tokens")
if response.status_code == 200:
success = True
self.access_token = response_json['access_token']
self.refresh_token = response_json['refresh_token']
if self.lazy_login:
self._save_access_token()
if cascade:
self.get_base_url()
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json,
'current_user': self.current_user
}
def logout(self):
"""
Revokes token.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = "https://auth.eagleeyenetworks.com/oauth2/revoke"
payload = {
"token": self.access_token,
"token_type_hint": "access_token"
}
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json",
"Content-type": "application/json"
}
# Send a POST request to obtain the base URL
response = requests.post(url, json=payload, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in logout")
if response.status_code == 200:
success = True
else:
success = False
logging.info(f"call to logout: {response_json}")
self.access_token = None
self.refresh_token = None
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_base_url(self, cascade=True):
"""
Obtains the base URL for the user.
Args:
cascade (bool): Indicates whether to cascade and get the current user information.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = "https://api.eagleeyenetworks.com/api/v3.0/clientSettings"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
# Send a GET request to obtain the base URL
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_base_url")
if response.status_code == 200:
success = True
if 'httpsBaseUrl' in response_json and 'hostname' in response_json['httpsBaseUrl']:
self.user_base_url = response_json['httpsBaseUrl']['hostname']
if cascade:
self.get_current_user()
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_current_user(self):
"""
Obtains the information of the current user.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/users/self?include=timeZone"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
# Send a GET request to obtain the current user information
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_current_user")
if response.status_code == 200:
success = True
self.current_user = response_json
self.user_tz_obj = timezone(response_json['timeZone']['timeZone'])
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_users(self):
"""
Obtains the list of users.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/users?include=timeZone"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_users")
if response.status_code == 200:
success = True
self.users = [i for i in response_json['results']]
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_cameras(self):
"""
Obtains the list of cameras.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/cameras?include=status"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_cameras")
if response.status_code == 200:
success = True
self.cameras = [
Camera(id=i['id'],\
name=i['name'],\
status=i['status'],\
account_id=i['accountId'],\
bridge_id=i['bridgeId'],\
user_base_url=self.user_base_url,\
een_instance=self)
for i in response_json['results']]
for camera in self.cameras:
camera.user_base_url = self.user_base_url
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_bridges(self):
"""
Obtains the list of bridges.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/bridges"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_bridges")
if response.status_code == 200:
success = True
self.bridges = [i for i in response_json['results']]
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_switches(self):
"""
Obtains the list of switches.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/switches"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_switches")
if response.status_code == 200:
success = True
self.switches = [i for i in response_json['results']]
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_available_devices(self, deviceType__in="camera"):
"""
Obtains the list of available devices.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/availableDevices?deviceType__in={deviceType__in}"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_available_devices")
if response.status_code == 200:
success = True
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_multi_cameras(self):
"""
Obtains the list of multi-cameras.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/multiCameras"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_multi_cameras")
if response.status_code == 200:
success = True
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
def get_list_of_feeds(self):
"""
Obtains the list of feeds.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
url = f"https://{self.user_base_url}/api/v3.0/feeds"
headers = {
"Authorization": f"Bearer {self.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.info(f"{response.status_code} in get_list_of_feeds")
if response.status_code == 200:
success = True
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}
class Device():
def __init__(self, id=None, name=None, status=None, account_id=None, user_base_url=None, een_instance=None):
self.id = id
self.name = name
self.status = status
self.account_id = account_id
self.user_base_url = user_base_url,
self.een_instance = een_instance
def get_id(self):
return self.id
def get_status(self):
return self.status['connectionStatus']
def is_online(self):
return self.status['connectionStatus'] == "online"
def is_offline(self):
return self.status['connectionStatus'] == "deviceOffline" or self.status['connectionStatus'] == "bridgeOffline"
def __repr__(self):
if self.is_online():
online = ''
elif self.is_offline():
online = ''
else:
online = ''
return f"{online} [{self.id}] - {self.name}"
class Camera(Device):
def __init__(self, id=None, name=None, status=None, account_id=None, bridge_id=None, user_base_url=None, een_instance=None):
super().__init__(id=id, name=name, status=status, account_id=account_id, user_base_url=user_base_url, een_instance=een_instance)
self.bridge_id = bridge_id
self.previews = []
self.videos = []
self.events = {
'status': [],
'motion': []
}
def get_list_of_events(self, start_timestamp=None, end_timestamp=None):
"""
Obtains the list of events.
Returns:
dict: Dictionary containing the success status, response HTTP status code, and data.
"""
if start_timestamp == None or end_timestamp == None:
return {
"success": False,
"response_http_status": None,
"data": { 'msg': 'get_list_of_events called without required args, needs start_timestamp, end_timestamp' }
}
url = f"https://{self.user_base_url}/api/v3.0/events?pageSize=100&include=een.deviceCloudStatusUpdate.v1&startTimestamp__gte={start_timestamp}&endTimestamp__lte={end_timestamp}&actor=camera%3A{self.id}&type__in=een.deviceCloudStatusUpdateEvent.v1"
headers = {
"Authorization": f"Bearer {self.een_instance.access_token}",
"Accept": "application/json"
}
response = requests.get(url, headers=headers)
response_json = response.json()
logging.debug(f"{response.status_code} returned from {url} with {headers} and {response.text}")
logging.info(f"{response.status_code} in get_list_of_events")
if response.status_code == 200:
success = True
# filter events by type
[self.events['status'].append(i) for i in response.json()['results'] if i['type'] == 'een.deviceCloudStatusUpdateEvent.v1']
[self.events['motion'].append(i) for i in response.json()['results'] if i['type'] == 'een.motionDetectionEvent.v1']
# remove duplicates
seen = set()
self.events['status'] = [event for event in self.events['status'] if event['endTimestamp'] and event['id'] not in seen and not seen.add(event['id'])]
seen = set()
self.events['motion'] = [event for event in self.events['motion'] if event['id'] not in seen and not seen.add(event['id'])]
# sort by event startTimestamp descending
self.events['status'] = sorted(self.events['status'], key=lambda x: x['startTimestamp'], reverse=True)
self.events['motion'] = sorted(self.events['motion'], key=lambda x: x['startTimestamp'], reverse=True)
else:
success = False
return {
"success": success,
"response_http_status": response.status_code,
"data": response_json
}

View File

@ -1,14 +0,0 @@
# Set up your application and get client id/secrete first
# https://developerv3.eagleeyenetworks.com/page/my-application
client_id = ""
client_secret = ""
# you will need to add approved redirect_uris in your application
# this examples assumes you've added http://127.0.0.1:3333/login_callback
# change the following variables if you did something different
# Note: do not use localhost for server_host, use 127.0.0.1 instead
server_protocol = "http"
server_host = "127.0.0.1"
server_port = "3333"
server_path = "login_callback"

View File

@ -1,459 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "d4582341",
"metadata": {},
"source": [
"# EagleEyev3 Playground\n",
"\n",
"To make this playground work, it is easier to read the `access_token` off the filesystem but you can always run the example server `python server.py` to go thorugh the Oauth2 flow. By default it will save the `access_token` into a file named `.lazy_login`. The module looks for that file and tries reading t"
]
},
{
"cell_type": "markdown",
"id": "8355d241",
"metadata": {},
"source": [
"## Import Module"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1394471a",
"metadata": {},
"outputs": [],
"source": [
"from EagleEyev3 import EagleEyev3"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "486a2537",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:root:200 in get_base_url\n",
"INFO:root:200 in get_current_user\n"
]
}
],
"source": [
"een = EagleEyev3()"
]
},
{
"cell_type": "markdown",
"id": "51b8b66e",
"metadata": {},
"source": [
"## Adjust Log Level"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "06d91db2",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logger = logging.getLogger()\n",
"#logger.setLevel('DEBUG')\n",
"#logger.setLevel('INFO')\n",
"logger.setLevel('WARN')\n",
"#logger.setLevel('ERROR')\n",
"#logger.setLevel('CRITICAL')"
]
},
{
"cell_type": "markdown",
"id": "0311109c-869c-4190-97c1-a6e717a8eeba",
"metadata": {},
"source": [
"## Who am I"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "e14e2be5-a5f9-4b8c-ae60-76c61cb61b8b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Mark Cotton - mcotton@mcottondesign.com'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f\"{een.current_user['firstName']} {een.current_user['lastName']} - {een.current_user['email']}\""
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "4ef47ae2-a010-4b7e-87f6-3dbf0a047e16",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'eyJraWQiOiI2ODYxYjBjYS0wZjI2LTExZWQtODYxZC0wMjQyYWMxMjAwMDIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJjYWZlZGVmMiIsImF1ZCI6InZtcy5hcGkiLCJpc3MiOiJ2bXMuYXV0aC52MSIsInZtc19hY2NvdW50IjoiMDAwMjgyMDEiLCJleHAiOjE2ODkzNzA1MTIsImlhdCI6MTY4ODc2NTcxMiwianRpIjoiYTM1Nzk3MmRmNzdiNTAwYTQ3NjBlNGY1MGYwNjNhMjUiLCJjbGllbnRfaWQiOiIzMmRhYzMzMDY0OWI0ODI3OWY5Y2FjYzJiNmY1N2FlNSIsInZtc19jbHVzdGVyIjoiYzAxMiJ9.EEmCaFqduOV5_cQ-VejeBXGbKLj1yHrxGkcMgqPUN1jYY0wR9bO7rkEwQh59Dj-1fr8pKsbrUr6DPDLfkaSIRlxpjdlsEBdzAmoZUpzPUfL9QzJu0C04OJU0gcBh7fwur7L2fMVthaZ6OKfThdM29qzRH5RR9gSAJGeNe08n4IvFVvuL80yvczgOiQwSzkg4PuHpbImDa44U7-qC8CrRvQ_TqsX6ziNzw-XmxZxXEwtZxkAOFDFaIsA8V4ezbQ_TSY6EElCnyyKLLI46-KIsGevx8dxa2NrhPFvC725dMhg-OPaCEF62sOlTrlHVBcv_e9MBk1VZLoDLStkSvU7Dxg'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"een.access_token"
]
},
{
"cell_type": "markdown",
"id": "a22ff6c2",
"metadata": {},
"source": [
"## Get Cameras"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "bb457850",
"metadata": {},
"outputs": [],
"source": [
"ret = een.get_list_of_cameras()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "c43f1db1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[✅ [1001423e] - ATM & Wine,\n",
" ✅ [100d8666] - Cash Register,\n",
" ✅ [10012735] - Fuel Dock,\n",
" ✅ [1002584c] - Safe]"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[i for i in een.cameras if i.is_online()]"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "f4c6fe67",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[ [10090759] - Benny Camera,\n",
" [1003e10b] - Driveway,\n",
" [100ba388] - Front Door,\n",
" [100b7b3b] - Max Camera,\n",
" [1009ae55] - Office]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"[i for i in een.cameras if not i.is_online()]"
]
},
{
"cell_type": "markdown",
"id": "8c140aaf-766f-4255-94ef-199d17cbc7a6",
"metadata": {},
"source": [
"## Getting list of Events"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "74e78ee1-33b8-4a88-9d23-cd6281603a5b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2023-07-07T16:41:31.990-05:00 2023-07-07T10:41:31.990-05:00\n"
]
}
],
"source": [
"print(een.time_now(), een.time_before())"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "c84c30dd-4b7c-415b-8e6f-e77de70d1924",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"for i in range(0,4):\n",
" ts = een.time_now()\n",
"\n",
" for cam in een.cameras:\n",
" blah = cam.get_list_of_events(end_timestamp=een.time_before(ts=ts, hours=(6*i)), \\\n",
" start_timestamp=een.time_before(ts=ts, hours=(6*(i+1))) )"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "13809cc7-9ec2-4e15-9495-e64feaecca6d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"18"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(een.cameras[2].events['status'])"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "67d4f79b-2b43-4bdb-9068-28ac9d8d921c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"len(een.cameras[0].events['motion'])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "8be8d503-b46d-4ba7-884e-2c21c3987129",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ATM & Wine - 2023-07-07T18:49:29.664+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T18:48:51.444+00:00 - error \n",
"ATM & Wine - 2023-07-07T16:22:04.146+00:00 - online \n",
"ATM & Wine - 2023-07-07T16:21:41.960+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T16:18:16.383+00:00 - error \n",
"ATM & Wine - 2023-07-07T16:16:31.511+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T16:15:57.174+00:00 - error \n",
"ATM & Wine - 2023-07-07T15:44:21.759+00:00 - online \n",
"ATM & Wine - 2023-07-07T15:41:31.997+00:00 - online \n",
"ATM & Wine - 2023-07-07T12:46:47.672+00:00 - bridgeOffline \n",
"ATM & Wine - 2023-07-07T11:24:46.454+00:00 - online \n",
"ATM & Wine - 2023-07-07T11:22:37.461+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T11:21:59.127+00:00 - error \n",
"ATM & Wine - 2023-07-07T09:44:24.462+00:00 - online \n",
"ATM & Wine - 2023-07-07T09:41:33.395+00:00 - online \n",
"ATM & Wine - 2023-07-07T06:00:38.322+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T06:00:00.175+00:00 - error \n",
"ATM & Wine - 2023-07-07T04:07:20.003+00:00 - online \n",
"ATM & Wine - 2023-07-07T04:05:21.238+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T04:04:42.160+00:00 - error \n",
"ATM & Wine - 2023-07-07T03:44:26.904+00:00 - online \n",
"ATM & Wine - 2023-07-07T03:41:56.630+00:00 - online \n",
"ATM & Wine - 2023-07-07T01:48:05.792+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-07T01:47:35.155+00:00 - error \n",
"ATM & Wine - 2023-07-06T23:19:05.335+00:00 - online \n",
"ATM & Wine - 2023-07-06T23:16:58.926+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T23:16:28.421+00:00 - error \n",
"ATM & Wine - 2023-07-06T23:10:32.790+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T21:44:28.864+00:00 - online \n",
"ATM & Wine - 2023-07-06T21:41:57.860+00:00 - online \n",
"ATM & Wine - 2023-07-06T20:08:47.208+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T20:08:16.933+00:00 - error \n",
"ATM & Wine - 2023-07-06T20:03:05.801+00:00 - online \n",
"ATM & Wine - 2023-07-06T20:02:43.659+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T20:01:41.457+00:00 - error \n",
"ATM & Wine - 2023-07-06T19:59:57.405+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T19:59:24.184+00:00 - error \n",
"ATM & Wine - 2023-07-06T19:31:55.867+00:00 - online \n",
"ATM & Wine - 2023-07-06T19:29:46.708+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T19:29:16.013+00:00 - error \n",
"ATM & Wine - 2023-07-06T18:24:27.389+00:00 - online \n",
"ATM & Wine - 2023-07-06T18:22:22.328+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T18:21:52.102+00:00 - error \n",
"ATM & Wine - 2023-07-06T15:52:12.759+00:00 - online \n",
"ATM & Wine - 2023-07-06T15:50:09.509+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T15:49:39.152+00:00 - error \n",
"ATM & Wine - 2023-07-06T15:41:58.888+00:00 - online \n",
"ATM & Wine - 2023-07-06T15:11:05.317+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T15:10:35.056+00:00 - error \n",
"ATM & Wine - 2023-07-06T14:59:51.787+00:00 - online \n",
"ATM & Wine - 2023-07-06T14:58:55.970+00:00 - bridgeOffline \n",
"ATM & Wine - 2023-07-06T14:58:40.967+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T12:58:36.884+00:00 - online \n",
"ATM & Wine - 2023-07-06T12:56:38.478+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T12:56:07.792+00:00 - error \n",
"ATM & Wine - 2023-07-06T12:49:42.742+00:00 - online \n",
"ATM & Wine - 2023-07-06T12:47:32.312+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T12:47:01.925+00:00 - error \n",
"ATM & Wine - 2023-07-06T11:40:03.505+00:00 - online \n",
"ATM & Wine - 2023-07-06T11:38:03.703+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T11:37:23.038+00:00 - error \n",
"ATM & Wine - 2023-07-06T11:31:52.611+00:00 - online \n",
"ATM & Wine - 2023-07-06T11:29:42.541+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T11:29:08.199+00:00 - error \n",
"ATM & Wine - 2023-07-06T09:42:01.013+00:00 - online \n",
"ATM & Wine - 2023-07-06T09:38:38.302+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T09:38:07.937+00:00 - error \n",
"ATM & Wine - 2023-07-06T08:31:02.432+00:00 - online \n",
"ATM & Wine - 2023-07-06T08:28:53.086+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T08:28:22.667+00:00 - error \n",
"ATM & Wine - 2023-07-06T04:07:10.931+00:00 - online \n",
"ATM & Wine - 2023-07-06T04:05:11.397+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T04:04:38.124+00:00 - error \n",
"ATM & Wine - 2023-07-06T03:43:19.799+00:00 - online \n",
"ATM & Wine - 2023-07-06T02:15:51.335+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-06T02:15:19.584+00:00 - error \n",
"ATM & Wine - 2023-07-05T21:43:39.111+00:00 - online \n",
"ATM & Wine - 2023-07-05T18:35:23.024+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T18:34:44.881+00:00 - error \n",
"ATM & Wine - 2023-07-05T17:13:14.356+00:00 - online \n",
"ATM & Wine - 2023-07-05T17:11:09.341+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T17:10:29.222+00:00 - error \n",
"ATM & Wine - 2023-07-05T15:43:44.544+00:00 - online \n",
"ATM & Wine - 2023-07-05T15:33:04.629+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T15:32:34.148+00:00 - error \n",
"ATM & Wine - 2023-07-05T15:13:36.151+00:00 - online \n",
"ATM & Wine - 2023-07-05T15:11:33.954+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T15:10:54.632+00:00 - error \n",
"ATM & Wine - 2023-07-05T14:24:50.082+00:00 - online \n",
"ATM & Wine - 2023-07-05T14:22:50.393+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T14:22:12.040+00:00 - error \n",
"ATM & Wine - 2023-07-05T14:02:42.746+00:00 - online \n",
"ATM & Wine - 2023-07-05T14:00:34.085+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T13:59:55.709+00:00 - error \n",
"ATM & Wine - 2023-07-05T10:43:54.157+00:00 - online \n",
"ATM & Wine - 2023-07-05T10:41:56.025+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T10:41:23.556+00:00 - error \n",
"ATM & Wine - 2023-07-05T10:10:27.404+00:00 - online \n",
"ATM & Wine - 2023-07-05T10:08:19.943+00:00 - deviceOffline \n",
"ATM & Wine - 2023-07-05T10:07:50.455+00:00 - error \n",
"ATM & Wine - 2023-07-05T09:44:11.782+00:00 - online \n",
"Cash Register - 2023-07-07T20:02:49.237+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-07T20:02:31.354+00:00 - deviceOffline \n",
"Cash Register - 2023-07-07T16:01:54.202+00:00 - online \n",
"Cash Register - 2023-07-07T16:01:52.891+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-07T15:44:21.759+00:00 - online \n",
"Cash Register - 2023-07-07T15:41:31.997+00:00 - online \n",
"Cash Register - 2023-07-06T23:01:59.550+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-06T21:44:28.864+00:00 - online \n",
"Cash Register - 2023-07-06T21:41:57.860+00:00 - online \n",
"Cash Register - 2023-07-06T14:58:55.385+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-06T14:58:40.382+00:00 - deviceOffline \n",
"Cash Register - 2023-07-06T10:01:40.245+00:00 - online \n",
"Cash Register - 2023-07-06T10:01:39.158+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-06T09:42:01.013+00:00 - online \n",
"Cash Register - 2023-07-05T20:06:43.349+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-05T18:01:54.584+00:00 - online \n",
"Cash Register - 2023-07-05T18:01:53.582+00:00 - bridgeOffline \n",
"Cash Register - 2023-07-05T15:43:44.544+00:00 - online \n",
"Safe - 2023-07-06T14:58:55.537+00:00 - bridgeOffline \n",
"Safe - 2023-07-06T14:58:40.535+00:00 - deviceOffline \n",
"Safe - 2023-07-06T09:42:01.013+00:00 - online \n",
"Safe - 2023-07-06T07:27:05.695+00:00 - bridgeOffline \n",
"Safe - 2023-07-06T03:43:19.799+00:00 - online \n",
"Safe - 2023-07-06T02:05:12.252+00:00 - bridgeOffline \n",
"Safe - 2023-07-05T21:43:39.111+00:00 - online \n"
]
}
],
"source": [
"for cam in een.cameras:\n",
" for i in cam.events['status']:\n",
" print(f\"{cam.name} - {i['startTimestamp']} - {i['data'][0]['newStatus']['connectionStatus']} \")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1cebbb4e-4c4e-4ab0-9627-251eb812b2f1",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8a69185-54cc-4c74-9c9b-a807e601be83",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

85
app.py
View File

@ -1,85 +0,0 @@
import json, requests
from flask import Flask, request, session, render_template, redirect, url_for
from flask_session import Session
import logging
logger = logging.getLogger()
#logger.setLevel('DEBUG')
logger.setLevel('INFO')
#logger.setLevel('WARN')
#logger.setLevel('ERROR')
#logger.setLevel('CRITICAL')
from EagleEyev3 import EagleEyev3
app = Flask(__name__)
SESSION_TYPE = 'filesystem'
app.config.from_object(__name__)
Session(app)
@app.route('/')
def index():
if 'een' in session:
een = session['een']
else:
een = EagleEyev3()
session['een'] = een
# using current_user as a proxy for an established valid session
if een.access_token == None:
base_url = "https://auth.eagleeyenetworks.com/oauth2/authorize"
path_url = f"?client_id={een.client_id}&response_type=code&scope=vms.all&redirect_uri={een.redirect_uri}"
return redirect(f"{base_url}{path_url}")
# call this to check if session is actually valid
check = een.get_current_user()
if 'success' not in check or check['success'] == False:
base_url = "https://auth.eagleeyenetworks.com/oauth2/authorize"
path_url = f"?client_id={een.client_id}&response_type=code&scope=vms.all&redirect_uri={een.redirect_uri}"
return redirect(f"{base_url}{path_url}")
else:
logging.info(f"{check['success']} - check get_current_user")
values = {
"current_user": een.current_user
}
return render_template('index.html', template_values=values)
@app.route('/login_callback')
def login_callback():
# This is getting the ?code= querystring value from the HTTP request.
code = request.args.get('code')
if 'een' in session:
een = session['een']
else:
een = EagleEyev3()
if (code):
# use the include code parameter to complete login process
oauth_object = een.login_tokens(code)
return redirect(url_for('index'))
@app.route('/logout')
def logout():
if 'een' in session:
een = session['een']
een.logout()
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host=een.server_host, port=een.server_port)

View File

@ -1,15 +0,0 @@
pytz==2023.3
requests==2.29.0
blinker==1.6.2
certifi==2023.5.7
charset-normalizer==3.1.0
click==8.1.3
Flask==2.3.2
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
pytz==2023.3
requests==2.29.0
urllib3==1.26.16
Werkzeug==2.3.6

View File

@ -1,131 +0,0 @@
{% if "X-PJAX" not in request.headers %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
{% block title %}
<title>classifier.een.cloud</title>
{% endblock %}
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="//cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
<link href="/static/dataTables.bootstrap4.min.css">
<!-- Custom styles for this template -->
<!-- <link href="album.css" rel="stylesheet">
<link href="custom.css" rel="stylesheet"> -->
<link href="/static/style.css" rel="stylesheet">
<!-- self-hosted fontawesome -->
<link href="/static/css/all.css" rel="stylesheet">
{% block style %}
{% endblock %}
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
<li class="nav-item active">
<a href="/" class="navbar-brand d-flex align-items-center">
<strong><small>status</small>.mcotton.space</strong>
</a>
</li>
</ul>
<span class="navbar-text">
{% block current_user %}
{% endblock %}
</span>
</form>
</div>
</nav>
</header>
<main role="main">
{% endif %}
{% block main %}
{% endblock %}
{% if "X-PJAX" not in request.headers %}
</main>
<footer class="text-muted">
<div class="container">
<p class="float-right">
</p>
<p></p>
</div>
</footer>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script>
<script src="/static/js/jquery.pjax.js"></script>
<script>
$(document).ready(function() {
$('#user_mode_toggle').on('click', function(event) {
event.stopPropagation();
$.ajax({
type: "POST",
url: '/api/v1/user_mode/toggle',
dataType: "text",
contentType : 'application/json',
success: function(data) {
//$('#labelModal-add').text('Saved');
//$('#labelModal').modal('hide');
location.reload();
},
error: function(data) {
console.log(data)
$('#labelModal-add').text('Failed');
//$('#labelModal').modal('hide');
//location.reload();
}
});
});
});
</script>
{% block script %}
{% endblock%}
</body>
</html>
{% endif %}

View File

@ -1,21 +0,0 @@
{% extends "base.html" %}
{% block style %}
{% endblock %}
{% block current_user %}
{{ template_values['current_user']['email'] }}
<a href="/logout" tabindex="-1">Logout</a>
{% endblock %}
{% block main %}
<div class="container text-center">
<div class="row">
<div class="col-md-6 offset-3">
<h1>Hello {{ template_values['current_user']['firstName'] }} {{ template_values['current_user']['lastName'] }}</h1>
</div>
</div>
</div>
{% endblock %}

115
tests.py
View File

@ -1,115 +0,0 @@
import unittest
from unittest.mock import MagicMock, patch
from EagleEyev3 import EagleEyev3
class TestEagleEyev3(unittest.TestCase):
def setUp(self):
# Create an instance of EagleEyev3 for testing
self.eagle_eye = EagleEyev3()
@patch('EagleEyev3.requests')
def test_login_tokens_success(self, mock_requests):
# Mock the requests.post method to return a successful response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {'access_token': 'token', 'refresh_token': 'refresh_token'}
mock_requests.post.return_value = mock_response
result = self.eagle_eye.login_tokens(code='authorization_code', cascade=True)
self.assertTrue(result['success'])
self.assertEqual(result['response_http_status'], 200)
self.assertEqual(result['data']['access_token'], 'token')
self.assertEqual(result['data']['refresh_token'], 'refresh_token')
@patch('EagleEyev3.requests')
def test_login_tokens_failure(self, mock_requests):
# Mock the requests.post method to return an unsuccessful response
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.json.return_value = {}
mock_requests.post.return_value = mock_response
result = self.eagle_eye.login_tokens(code='authorization_code', cascade=True)
self.assertFalse(result['success'])
self.assertEqual(result['response_http_status'], 400)
self.assertEqual(result['data'], {})
@patch('EagleEyev3.requests')
def test_get_base_url_success(self, mock_requests):
# Set access_token for testing
self.eagle_eye.access_token = 'access_token'
# Mock the requests.get method to return a successful response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {'httpsBaseUrl': {'hostname': 'base_url'}}
mock_requests.get.return_value = mock_response
result = self.eagle_eye.get_base_url(cascade=True)
self.assertTrue(result['success'])
self.assertEqual(result['response_http_status'], 200)
self.assertEqual(self.eagle_eye.user_base_url, 'base_url')
self.assertIsNotNone(result['data'])
@patch('EagleEyev3.requests')
def test_get_base_url_failure(self, mock_requests):
# Set access_token for testing
self.eagle_eye.access_token = 'access_token'
# Mock the requests.get method to return an unsuccessful response
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.json.return_value = {}
mock_requests.get.return_value = mock_response
result = self.eagle_eye.get_base_url(cascade=True)
self.assertFalse(result['success'])
self.assertEqual(result['response_http_status'], 400)
self.assertEqual(self.eagle_eye.user_base_url, None)
self.assertEqual(result['data'], {})
@patch('EagleEyev3.requests')
def test_get_current_user_success(self, mock_requests):
# Set access_token and user_base_url for testing
self.eagle_eye.access_token = 'access_token'
self.eagle_eye.user_base_url = 'base_url'
# Mock the requests.get method to return a successful response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {'user_id': 'user123'}
mock_requests.get.return_value = mock_response
result = self.eagle_eye.get_current_user()
self.assertTrue(result['success'])
self.assertEqual(result['response_http_status'], 200)
self.assertEqual(result['data']['user_id'], 'user123')
@patch('EagleEyev3.requests')
def test_get_current_user_failure(self, mock_requests):
# Set access_token and user_base_url for testing
self.eagle_eye.access_token = 'access_token'
self.eagle_eye.user_base_url = 'base_url'
# Mock the requests.get method to return an unsuccessful response
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.json.return_value = {}
mock_requests.get.return_value = mock_response
result = self.eagle_eye.get_current_user()
self.assertFalse(result['success'])
self.assertEqual(result['response_http_status'], 400)
self.assertEqual(result['data'], {})
# Write similar tests for the remaining methods
if __name__ == '__main__':
unittest.main()