fix again

This commit is contained in:
2025-12-07 12:42:43 +00:00
parent a765aa23f4
commit d24587c321
13 changed files with 21 additions and 185 deletions

254
providers/gwt_rpc.py Normal file
View File

@@ -0,0 +1,254 @@
import logging
import json
from datetime import datetime, timezone, timedelta
from urllib.parse import urlparse
from pyproj import Transformer
import get_rpc_config_auto
from providers.base import BaseProvider, BaseCountyProvider
logger = logging.getLogger(__name__)
class GwtRpcBaseProvider:
"""Base class for GWT-RPC providers to share common logic like auto-repair."""
def __init__(self, config, session):
self.config = config
self.session = session
self.name = config.get('name', 'Unknown')
self.map_url = config.get('map_url')
self.state_filter = config.get('state_filter')
self.AUTO_UPDATE_COOLDOWN_HOURS = 4
# Set up session headers and cookies from config
self.session.headers.update({
'User-Agent': config.get('user_agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'),
'Accept': '*/*',
'Sec-Fetch-Site': 'same-origin'
})
if config.get('cookies'):
for cookie in config['cookies']:
self.session.cookies.set(cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'])
def attempt_auto_repair(self):
if not self.map_url: return False
last_update = self.config.get('last_auto_update')
if last_update:
try:
last_dt = datetime.fromisoformat(last_update)
if last_dt.tzinfo is None: last_dt = last_dt.replace(tzinfo=timezone.utc)
if datetime.now(timezone.utc) - last_dt < timedelta(hours=self.AUTO_UPDATE_COOLDOWN_HOURS):
logger.info(f"Skipping auto-repair for {self.name} (Cooldown active).")
return False
except ValueError: pass
logger.info(f"Attempting Auto-Repair for {self.name}...")
try:
# This function needs to be defined in the main script context to save config.
# We import it here, inside the method, to avoid circular import errors at startup.
if isinstance(self, GwtRpcCountyProvider):
from newpower import update_provider_config
else:
from newpower2 import update_provider_config
_, valid_headers, valid_cookies, valid_body = get_rpc_config_auto.fetch_live_data(self.map_url)
if valid_headers and valid_body:
logger.info(f"Repair successful! Updating {self.name}.")
excluded = {'content-length', 'host', 'connection', 'cookie', 'accept-encoding', 'sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform', 'origin'}
clean_headers = {k: v for k, v in valid_headers.items() if k.lower() not in excluded}
clean_headers['Referer'] = self.map_url
new_settings = {
'headers': clean_headers, 'body': valid_body, 'cookies': valid_cookies,
'user_agent': valid_headers.get('user-agent'),
}
# Update in-memory config for the current run
self.config.update(new_settings)
self.config['last_auto_update'] = datetime.now(timezone.utc).isoformat()
# Update session for the current run
self.session.cookies.clear()
for cookie in valid_cookies:
self.session.cookies.set(cookie['name'], cookie['value'], domain=cookie['domain'], path=cookie['path'])
# Save to disk for next time
update_provider_config(self.name, self.config)
return True
except Exception as e:
logger.error(f"Auto-repair failed: {e}")
return False
def _fetch_rpc_data(self, is_retry=False):
url = self.config.get('url')
headers = self.config.get('headers', {})
body = self.config.get('body')
if not url or not body: return None
parsed_url = urlparse(url)
origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
correct_referer = headers.get('Referer') or headers.get('x-gwt-module-base') or origin
req_headers = headers.copy()
req_headers['Content-Type'] = 'text/x-gwt-rpc; charset=UTF-8'
req_headers['Referer'] = correct_referer
resp = self.session.post(url, headers=req_headers, data=body, verify=False)
if "//EX" in resp.text or resp.status_code == 500:
logger.error(f"GWT Failure for {self.name}.")
if is_retry: return None
if self.attempt_auto_repair():
logger.info("Retrying fetch with new settings...")
return self._fetch_rpc_data(is_retry=True)
return None
if not resp.ok: return None
return json.loads(resp.text.replace('//OK', ''))
class GwtRpcCountyProvider(GwtRpcBaseProvider, BaseCountyProvider):
def fetch(self):
try:
data = self._fetch_rpc_data()
if data:
return self._extract_county_summary(data)
return []
except Exception as e:
logger.error(f"County fetch error for {self.name}: {e}")
return []
def _extract_county_summary(self, data_list):
try:
string_table = next((item for item in data_list if isinstance(item, list)), None)
if not string_table: return []
stream_raw = [item for item in data_list if not isinstance(item, list)]
stream = [int(token) for token in stream_raw if isinstance(token, (int, float, str)) and str(token).replace('.','',1).isdigit()]
REGION_SIG = "cc.nisc.oms.clientandserver.v2.pojo.Region/3192921568"
INTEGER_SIG = "java.lang.Integer/3438268394"
CATEGORY_KEY = "County"
def get_index(val):
try: return string_table.index(val) + 1
except ValueError: return 0
region_type_id = get_index(REGION_SIG)
integer_type_id = get_index(INTEGER_SIG)
county_type_id = get_index(CATEGORY_KEY)
if region_type_id == 0: return []
results = []
i = 0
while i < len(stream):
if stream[i] == region_type_id:
try:
p = i + 1
served = stream[p] if stream[p+1] == integer_type_id else 0
p += 2 if served > 0 else 1
out = stream[p] if stream[p+1] == integer_type_id else 0
p += 2 if out > 0 else 1
name_idx, cat_idx = stream[p], stream[p+1]
if cat_idx == county_type_id:
name = string_table[name_idx - 1] if 0 < name_idx <= len(string_table) else "Unknown"
results.append({'county': name, 'state': self.state_filter, 'company': self.name, 'outages': out, 'served': served})
except IndexError: pass
i += 1
return results
except Exception as e:
logger.error(f"Could not parse county summary for {self.name}: {e}")
return []
class GwtRpcProvider(GwtRpcBaseProvider, BaseProvider):
def __init__(self, config, session):
super().__init__(config, session)
self.transformer = None
self.STATE_BOUNDS = {
'WV': {'lat_min': 37.0, 'lat_max': 40.7, 'lon_min': -82.7, 'lon_max': -77.7},
'OH': {'lat_min': 38.4, 'lat_max': 42.0, 'lon_min': -84.9, 'lon_max': -80.5},
'KY': {'lat_min': 36.4, 'lat_max': 39.2, 'lon_min': -89.6, 'lon_max': -81.9},
'IA': {'lat_min': 40.3, 'lat_max': 43.6, 'lon_min': -96.7, 'lon_max': -90.1}
}
if config.get('epsg'):
try:
self.transformer = Transformer.from_crs(f"EPSG:{config['epsg']}", "EPSG:4326", always_xy=True)
except: logger.error(f"EPSG Error for {self.name}")
def fetch(self):
try:
data = self._fetch_rpc_data()
if data:
return self._extract_outages(data)
return []
except Exception as e:
logger.error(f"Fetch error {self.name}: {e}")
return []
def _extract_outages(self, data_list):
results = []
try:
string_table = next((item for item in data_list if isinstance(item, list)), None)
if not string_table: return []
stream_raw = [item for item in data_list if not isinstance(item, list)]
stream = [int(token) for token in stream_raw if isinstance(token, (int, float))]
OUTAGE_SIG_KEYWORD = ".pojo.Outage/"
outage_sig_full = next((s for s in string_table if OUTAGE_SIG_KEYWORD in s), None)
if not outage_sig_full: return []
outage_type_id = string_table.index(outage_sig_full) + 1
i = 0
while i < len(stream):
if stream[i] == outage_type_id:
try:
p = i + 1
outagen = stream[p]; p += 1
crew_status_idx = stream[p]; p += 1
cause_idx = stream[p]; p += 1
etr_high = stream[p]; p += 1
etr_low = stream[p]; p += 1; p += 1
start_high = stream[p]; p += 1
start_low = stream[p]; p += 1; p += 1
coord_x = stream[p]; p += 1
coord_y = stream[p]; p += 1
lat, lon = None, None
if self.transformer and coord_x and coord_y:
try:
lon, lat = self.transformer.transform(coord_x, coord_y)
if not self._is_valid(lat, lon): lat, lon = None, None
except: pass
if lat and lon:
start_ms = (start_high << 32) | start_low
etr_ms = (etr_high << 32) | etr_low
start_time = datetime.fromtimestamp(start_ms / 1000, tz=timezone.utc) if start_ms > 0 else None
etr_time = datetime.fromtimestamp(etr_ms / 1000, tz=timezone.utc) if etr_ms > 0 else None
cause = string_table[cause_idx - 1].strip() if 0 < cause_idx <= len(string_table) else "Unknown"
crew_status = string_table[crew_status_idx - 1].strip() if 0 < crew_status_idx <= len(string_table) else "Unknown"
results.append({
'incidentid': f"{self.name}-{lat:.5f}-{lon:.5f}", 'utility': self.name,
'lat': lat, 'lon': lon, 'pointgeom': f"{lat:.5f},{lon:.5f}",
'start': start_time, 'etr': etr_time, 'outagen': outagen,
'cause': cause, 'crew_status': crew_status,
'last_change': datetime.now(timezone.utc)
})
except (IndexError, TypeError):
pass
i += 1
return results
except Exception as e:
logger.error(f"Could not parse point outages for {self.name}: {e}")
return []
def _is_valid(self, lat, lon):
if not self.state_filter: return True
b = self.STATE_BOUNDS.get(self.state_filter)
if not b: return True
return b['lat_min'] <= lat <= b['lat_max'] and b['lon_min'] <= lon <= b['lon_max']