This commit is contained in:
commit
7e975f33f8
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Patriot_Safey.iml" filepath="$PROJECT_DIR$/.idea/Patriot_Safey.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
logging:
|
||||
tofile: false
|
||||
filename: safey.log
|
||||
level: INFO
|
||||
format: "%(asctime)s [%(levelname)s] %(message)s"
|
||||
office365:
|
||||
tenantid: "6cb8b87e-4706-48cc-9d2d-dbaa71bd73ae"
|
||||
clientid: "e7640a20-a411-4080-8e73-ae2bbbd45505"
|
||||
username: "f1@alarm24.no"
|
||||
password: "Sortland2018!"
|
||||
token_ttl: 1800
|
||||
interval: 30
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
import msal
|
||||
import requests
|
||||
import time
|
||||
import re
|
||||
import traceback
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
# Load YAML config
|
||||
with open("config.yaml", "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# Extract logging config
|
||||
log_config = config.get("logging", {})
|
||||
log_tofile = log_config.get("tofile", "true")
|
||||
log_file = log_config.get("filename", "safey.log")
|
||||
log_level = getattr(logging, log_config.get("level", "INFO").upper(), logging.INFO)
|
||||
log_format = log_config.get("format", "%(asctime)s [%(levelname)s] %(message)s")
|
||||
# Extract config
|
||||
office365 = config.get("office365")
|
||||
|
||||
# Apply logging config
|
||||
if log_config.get("tofile"):
|
||||
logging.basicConfig(
|
||||
filename=log_file,
|
||||
level=log_level,
|
||||
format=log_format,
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format=log_format,
|
||||
)
|
||||
|
||||
logging.info(f"Logging config: {log_config}")
|
||||
logging.info(f"Office365 config: {office365}")
|
||||
|
||||
past_timestamp = 0
|
||||
|
||||
def login_ms365():
|
||||
|
||||
global headers
|
||||
global POST_URL
|
||||
|
||||
# Azure AD credentials
|
||||
CLIENT_ID = office365.get("clientid")
|
||||
TENANT_ID = office365.get("tenantid")
|
||||
USERNAME = office365.get("username")
|
||||
PASSWORD = office365.get("password")
|
||||
|
||||
# Graph scopes and auth settings
|
||||
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
|
||||
SCOPES = ["https://graph.microsoft.com/.default"]
|
||||
|
||||
# Destination for HTTP POST
|
||||
POST_URL = "https://api.alarm24.no:7443/SIA"
|
||||
|
||||
# Acquire token
|
||||
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
|
||||
token_response = app.acquire_token_by_username_password(
|
||||
username=USERNAME,
|
||||
password=PASSWORD,
|
||||
scopes=SCOPES
|
||||
)
|
||||
|
||||
if "access_token" not in token_response:
|
||||
logging.error("❌ Failed to acquire token")
|
||||
logging.error(token_response.get("error_description"))
|
||||
exit(1)
|
||||
|
||||
access_token = token_response["access_token"]
|
||||
headers = {
|
||||
'Authorization': f'Bearer {access_token}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
logging.info("OAuth2 token refreshed!")
|
||||
return headers
|
||||
|
||||
def remove_norwegian(text):
|
||||
return (text.replace("æ", "ae")
|
||||
.replace("ø", "o")
|
||||
.replace("å", "a")
|
||||
.replace("Æ", "Ae")
|
||||
.replace("Ø", "O")
|
||||
.replace("Å", "A"))
|
||||
|
||||
def normalize_string(input_string: str) -> str:
|
||||
return re.sub(r'\s+', ' ', input_string).strip()
|
||||
|
||||
|
||||
def parse_email():
|
||||
# Fetch top 10 recent emails
|
||||
response = requests.get(
|
||||
"https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$top=10&$orderby=receivedDateTime asc",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logging.error("❌ Failed to fetch emails:", response.text)
|
||||
exit(1)
|
||||
|
||||
emails = response.json().get("value", [])
|
||||
logging.info(f"Found {len(emails)} emails")
|
||||
|
||||
# Process matching emails
|
||||
for email in emails:
|
||||
subject = email.get("subject", "")
|
||||
subject_match = re.search(r"#PROM:(\d+)\.?:\s*(.*)", subject)
|
||||
message_id = email["id"]
|
||||
# Get full message to read body
|
||||
msg_detail = requests.get(
|
||||
f"https://graph.microsoft.com/v1.0/me/messages/{message_id}",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if msg_detail.status_code != 200:
|
||||
logging.error(f"❌ Failed to get email body: {msg_detail.text}")
|
||||
continue
|
||||
|
||||
body_html = msg_detail.json().get("body", {}).get("content", "")
|
||||
|
||||
if subject == "Alarm Notification":
|
||||
match = re.search(r"#PROM:(\d+)\.", body_html)
|
||||
match2 = re.search(r'<div style="font-size:13px"><div>(.*?)<br>.*?</div></div>', body_html, re.DOTALL)
|
||||
# Construct JSON
|
||||
subject = remove_norwegian(subject)
|
||||
payload = {
|
||||
"clientid": remove_norwegian(match.group(1)),
|
||||
"signal": 140,
|
||||
"zone": 999,
|
||||
"cameraLink": "TEXT" + remove_norwegian(match2.group(1).strip().replace(" ","_"))
|
||||
}
|
||||
elif subject_match:
|
||||
client_id = int(subject_match.group(1))
|
||||
signal_text = subject_match.group(2).strip().upper()
|
||||
signal_text = normalize_string(signal_text)
|
||||
match3 = re.search(r"utløst av(.*?\..*?)[\.\n<]", body_html, re.IGNORECASE)
|
||||
if match3:
|
||||
device = remove_norwegian(match3.group(1).strip())
|
||||
logging.info(f"Sone tekst: {match3.group(1).strip()}")
|
||||
else:
|
||||
match3 = re.search(r"utløst av(.*?)[\.\n<]", body_html, re.IGNORECASE)
|
||||
if match3:
|
||||
device = remove_norwegian(match3.group(1).strip())
|
||||
logging.info(f"Sone tekst: {match3.group(1).strip()}")
|
||||
else:
|
||||
device = ""
|
||||
|
||||
if "BRANNALARM UTLØST" in signal_text:
|
||||
signal = 110
|
||||
elif "BRANNSIREN STOPPET" in signal_text:
|
||||
signal = 1110
|
||||
elif "FIRE ALARM TRIGGERED" in signal_text:
|
||||
signal = 110
|
||||
elif "SIKKERHETSALARM UTLØST" in signal_text:
|
||||
signal = 130
|
||||
elif "SIKKERHETSALARM AVSLUTTET" in signal_text:
|
||||
signal = 1130
|
||||
elif "ENHETEN ER IKKE TILGJENGELIG" in signal_text:
|
||||
signal = 147
|
||||
elif "ENHET TILGJENGELIG" in signal_text:
|
||||
signal = 1147
|
||||
else:
|
||||
signal = 147
|
||||
signal_text = remove_norwegian(signal_text).replace(" ", "_")
|
||||
device = signal_text.replace(" ","_") + "_" + device.replace(" ","_")
|
||||
|
||||
# Construct JSON
|
||||
payload = {
|
||||
"clientid": str(client_id),
|
||||
"signal": signal,
|
||||
"zone": 999,
|
||||
"cameraLink": "TEXT" + remove_norwegian(device.replace(" ","_"))
|
||||
}
|
||||
else:
|
||||
# Construct JSON
|
||||
subject = remove_norwegian(subject)
|
||||
payload = {
|
||||
"clientid": 9000,
|
||||
"signal": 140,
|
||||
"zone": 999,
|
||||
"cameraLink": "TEXT" + subject.replace(" ", "_")
|
||||
}
|
||||
|
||||
|
||||
logging.info(f"Subject: {subject}")
|
||||
logging.info(f"Body preview: {body_html[:100]}...")
|
||||
|
||||
post_resp = requests.post(POST_URL, json=payload)
|
||||
logging.info(f"📤 Sent to API: {payload}")
|
||||
logging.info(f"📤 Response from API: {post_resp.status_code} - {post_resp.text}")
|
||||
|
||||
# Move the email to Deleted Items folder
|
||||
move_resp = requests.post(
|
||||
f"https://graph.microsoft.com/v1.0/me/messages/{message_id}/move",
|
||||
headers=headers,
|
||||
json={"destinationId": "deleteditems"}
|
||||
)
|
||||
|
||||
if move_resp.status_code == 201:
|
||||
logging.info("🗑️ Email moved to Deleted Items.")
|
||||
else:
|
||||
logging.error(f"Failed to move email: {move_resp.status_code} - {move_resp.text}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
if time.time() - past_timestamp > office365.get("token_ttl", 3600):
|
||||
logging.info("OAuth2 token is expired or is about to expire, refreshing token...")
|
||||
time.sleep(0.5)
|
||||
login_ms365()
|
||||
past_timestamp = time.time()
|
||||
parse_email()
|
||||
try:
|
||||
requests.get("http://watchdog.sikkerhetsservice.no/checkin.php?private_key=YLPRR4uykjZoeQtCtfmjqTPephLf9ZMe")
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.error(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Error: {e}")
|
||||
requests.get(f"https://watchdog.sikkerhetsservice.no/warn.php?sys_name=A24_Safey&warn_type=sys&label=Error&warn_message={e}")
|
||||
traceback.print_exc()
|
||||
time.sleep(office365.get("interval", 30))
|
||||
Loading…
Reference in New Issue