#!/usr/bin/python3
# Get Energy Outages script
import os
import sys
from dataclasses import dataclass
from io import StringIO
from types import SimpleNamespace
from typing import List
import requests
import json
import urllib3
import numpy as np
from numpy.ma.core import append
from config_file import read_config as read_cfg
from datetime import datetime as dt
from lxml import etree
STATUS_ERROR_SEND_TO_TG = 20
STATUS_ERROR_GPV_RESPONSE = 10
STATUS_ERROR_PO_RESPONSE = 30
STATUS_EMPTY_CONFIG = 5
STATUS_ERROR_SEND_TO_MX = 40
SCRIPT_PATH = os.path.dirname(__file__)
CONFIG_FILE_NAME = SCRIPT_PATH + '/get_eo_config.yaml'
LAST_DATAS_FILE_NAME_GPV = SCRIPT_PATH + '/get_eo.last_info'
LAST_DATAS_FILE_NAME_PO = SCRIPT_PATH + '/get_po.last_info'
LAST_DATAS_FORMAT_GPV = '{array} {date} {day}'
LAST_DATAS_FORMAT_PO = '{placed} {star_dt} {start_tm} {stop_dt} {stop_tm} {current_date}'
IS_DEBUG = False
MENTIONS = ""
USE_MATRIX = False
MX_SERVER = ""
MX_ROOM = ""
MX_TOKEN = ""
def read_config():
global MENTIONS, USE_MATRIX, MX_SERVER, MX_ROOM, MX_TOKEN
j = read_cfg(CONFIG_FILE_NAME)
tg_token = j['token']
tg_chat = j['chat_id']
oe_account_number = j['account']
is_debug = j['debug'].lower() == 'true'
settlement = j['settlement']
street = j['street']
house = j['house']
building_part_number = j['building_part_number']
apartment = j['apartment']
USE_MATRIX = j['use_matrix'].lower() == 'true'
MX_SERVER = j['mx_server']
MX_ROOM = j['mx_room_id']
MX_TOKEN = j['mx_access_token']
# i = 1
# for m in j['mentions']:
# MENTIONS += f'user {i}' + '\n'
# i += 1
return tg_token, tg_chat, oe_account_number, is_debug, settlement, street, house, building_part_number, apartment
@dataclass
class POData:
city: str
street: str
house: int
po_type: str
reason: str
placement_date: str
start_date: str
stop_date: str
start_time: str
stop_time: str
_notified_at: dt = None
need_to_send: bool = True
PARSED_PO_DATAS: List[POData]
def send_message_to_tg(msg, token, chat):
url = f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat}&parse_mode=html&text={msg}"
return requests.get(url)
# return requests.post(
# url='https://api.telegram.org/bot{0}/{1}'.format(token, 'sendMessage'),
# data={'chat_id': {chat}, 'text': {msg}, 'parse_mode': 'html'}
# )
def send_matrix_notification(message: str) -> bool:
"""Send notification to Matrix"""
if not USE_MATRIX:
return False
try:
url = f"{MX_SERVER}/_matrix/client/r0/rooms/{MX_ROOM}/send/m.room.message"
headers = {
'Authorization': f'Bearer {MX_TOKEN}',
'Content-Type': 'application/json'
}
payload = {
'msgtype': 'm.text',
'body': message,
'format': 'org.matrix.custom.html',
'formatted_body': message.replace('\n', '
')
}
response = requests.post(url, headers=headers, json=payload)
return response.status_code == 200
except Exception as e:
# logger.error(f"Error sending Matrix notification: {e}")
return False
def get_GPV_response_from_oe(account):
url = 'https://be-svitlo.oe.if.ua/schedule-by-search'
headers = {
"Host": "be-svitlo.oe.if.ua",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"Referer": "https://svitlo.oe.if.ua/",
"Origin": "https://svitlo.oe.if.ua",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"Connection": "keep-alive",
"Priority": "u=0",
}
data = {
"accountNumber": account,
"userSearchChoice": "pob",
"address": ""
}
urllib3.disable_warnings()
return requests.post(url, headers=headers, data=data, verify=False)
def get_PO_response(settlement, street, house, building_part_number='', apartment=''): # Power Outage
utf8 = '✓'
url = 'https://oe.if.ua/uk/shutdowns_table'
params = {
'utf8': utf8,
'settlement': settlement,
'street': street,
'house_number': house,
'building_part_number': building_part_number,
'apartment': apartment,
'commit': 'Пошук'
}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
}
urllib3.disable_warnings()
return requests.get(url=url, headers=headers, params=params)
def has_numbers(input):
return any(char.isdigit() for char in str(input))
def get_utf_chars(number, is_time=False):
digit_chars = ['\U00000030\U000020E3', '\U00000031\U000020E3', '\U00000032\U000020E3', '\U00000033\U000020E3',
'\U00000034\U000020E3', '\U00000035\U000020E3', '\U00000036\U000020E3', '\U00000037\U000020E3',
'\U00000038\U000020E3', '\U00000039\U000020E3']
res = ''
if is_time:
h = number.split(':')[0]
m = number.split(':')[1]
if m != 0:
value = str(h) + ':' + str(m)
else:
value = str(h) + ':00'
else:
value = str(number)
for c in value:
if c.isdigit and c not in ['.', ':']:
res += digit_chars[int(c)]
else:
res += c
return res
def get_digit_message(arr, is_time=False):
first = 0
if len(arr) > 0:
first = arr[0]
last = 0
if len(arr) > 1:
last = arr[1]
msg = 'з {num0} до {num1}'
return msg.format(num0=get_utf_chars(first, is_time), num1=get_utf_chars(last, is_time))
def save_last_datas_GPV(hours_on, hours_off, date, day):
with open(LAST_DATAS_FILE_NAME_GPV, 'w') as f:
f.write(LAST_DATAS_FORMAT_GPV.format(array=str(hours_on) + str(hours_off), date=date, day=day))
def save_last_datas_GPV(datas):
with open(LAST_DATAS_FILE_NAME_GPV, 'w') as f:
f.write(str(datas))
# def save_last_datas_PO(placed='', star_dt='', start_tm='', stop_dt='', stop_tm=''):
# date = dt.today().strftime('%Y-%m-%d')
# with open(LAST_DATAS_FILE_NAME_PO, 'w') as f:
# f.write(LAST_DATAS_FORMAT_PO.format(placed=str(placed), star_dt=str(star_dt), start_tm=str(start_tm),
# stop_dt=str(stop_dt), stop_tm=str(stop_tm),
# current_date=date))
def save_last_datas_PO(po_data: List[POData]):
date = dt.today().strftime('%Y-%m-%d')
with open(LAST_DATAS_FILE_NAME_PO, 'w') as f:
f.write(str(po_data) + ' ' + date)
def load_last_datas(file_name):
if os.path.isfile(file_name):
with open(file_name, 'r') as f:
return f.read()
else:
return ''
def is_old_and_new_datas_equals_GPV(hours_on, hours_off, date, day):
loaded = load_last_datas(LAST_DATAS_FILE_NAME_GPV)
return LAST_DATAS_FORMAT_GPV.format(array=str(hours_on) + str(hours_off), date=date, day=day) == loaded
def is_old_and_new_datas_equals_GPV(datas):
loaded = load_last_datas(LAST_DATAS_FILE_NAME_GPV)
return str(datas) == str(loaded)
def is_old_and_new_datas_equals_PO(placed='', star_dt='', start_tm='', stop_dt='', stop_tm=''):
date = dt.today().strftime('%Y-%m-%d')
loaded = load_last_datas(LAST_DATAS_FILE_NAME_PO)
return LAST_DATAS_FORMAT_PO.format(placed=str(placed), star_dt=str(star_dt), start_tm=str(start_tm),
stop_dt=str(stop_dt),
stop_tm=str(stop_tm), current_date=date) == loaded
def is_old_and_new_datas_equals_PO(po_datas: List[POData]):
date = dt.today().strftime('%Y-%m-%d')
loaded = load_last_datas(LAST_DATAS_FILE_NAME_PO)
return str(po_datas)+ ' ' + date == loaded
def is_need_toSend(approved_from, event_date, hours_off, hours_on):
need_to_send = not is_old_and_new_datas_equals_GPV(hours_off, hours_on, approved_from, event_date)
return need_to_send
def get_GPV_message(approved_from, event_date, hours_off, hours_on, outage_queue):
queue = get_utf_chars(outage_queue)
message = f'\U000026A1 ГПВ на {event_date} для {queue} черги'
if len(hours_off) > 0:
message += '\nВимкнення:'
for i in range(len(hours_off)):
arr = [hours_off[i], hours_on[i]]
msg = get_digit_message(arr, True)
message += '\n' + msg
# message += (f'\n{MENTIONS}')
else:
message += '\nВимкнень немає \U0001F44C \U0001F483'
message += (f'\n{approved_from}')
return message
def get_address(city, street, house):
return f'{city}, {street}, {house}'
def is_outage_date_before_or_same(stop_date):
parsed_date = dt.strptime(stop_date, "%Y.%m.%d").date()
current_date = dt.now().date()
return current_date <= parsed_date
def parse_po_data(root) -> List[POData]:
res: List[POData] = []
for i in range(len(root.findall(".//div[@class='table-row flex']"))):
po_data = POData(city=root.xpath("//div[@class='table-row flex']/div[@class='city']/text()")[i],
street=root.xpath("//div[@class='table-row flex']/div[@class='street']/text()")[i],
house=root.xpath("//div[@class='table-row flex']/div[@class='house_number']/text()")[i],
po_type=root.xpath("//div[@class='table-row flex']/div[@class='type-shutdown']/text()")[i],
reason=root.xpath("//div[@class='table-row flex']/div[@class='reason-shutdown reason-text-container']/div[@class='reason-text-hide']/text()")[i],
placement_date=root.xpath("//div[@class='table-row flex']/div[@class='placement_date']/div[@class='date']/text()")[i], start_date=
root.xpath("//div[@class='table-row flex']/div[@class='shutdown_date']/div[@class='date']/text()")[i],
stop_date=root.xpath("//div[@class='table-row flex']/div[@class='turn_on_date']/div[@class='date']/text()")[i],
start_time=root.xpath("//div[@class='table-row flex']/div[@class='shutdown_date']/div[@class='time']/text()")[i],
stop_time=root.xpath("//div[@class='table-row flex']/div[@class='turn_on_date']/div[@class='time']/text()")[i]
)
# po_data.need_to_send = not is_old_and_new_datas_equals_PO(placed=po_data.placement_date, star_dt=po_data.start_date,
# start_tm=po_data.start_time, stop_dt=po_data.stop_date,
# stop_tm=po_data.stop_time)
if is_outage_date_before_or_same(po_data.stop_date):
po_data.need_to_send = True
if po_data not in res:
res.append(po_data)
res.sort(key=lambda pd: pd.start_date)
return res
def get_message_with_PO(root, html): # city, street, house, placement_date, PO_type, reason, start_date, stop_date, start_time, stop_time
message = ''
need_to_send = False
global PARSED_PO_DATAS
PARSED_PO_DATAS = parse_po_data(root)
if "