Compare commits
4 commits
f577a8b8ca
...
eaa32c6987
Author | SHA1 | Date | |
---|---|---|---|
eaa32c6987 | |||
03f05fe44c | |||
d4a4d5a18c | |||
3ea6abd7f9 |
4 changed files with 125 additions and 23 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -166,3 +166,5 @@ cython_debug/
|
||||||
*.secret
|
*.secret
|
||||||
*.db
|
*.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
config.yml
|
9
config-sample.yml
Normal file
9
config-sample.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
stadiamaps:
|
||||||
|
api_key:
|
||||||
|
nominatim:
|
||||||
|
api_base_url:
|
||||||
|
mastodon:
|
||||||
|
client_id:
|
||||||
|
client_secret:
|
||||||
|
access_token:
|
||||||
|
api_base_url:
|
|
@ -1,4 +1,3 @@
|
||||||
beautifulsoup4==4.12.2
|
|
||||||
blurhash==1.1.4
|
blurhash==1.1.4
|
||||||
certifi==2023.11.17
|
certifi==2023.11.17
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.3.2
|
||||||
|
@ -9,9 +8,9 @@ Mastodon.py==1.8.1
|
||||||
pillow==10.2.0
|
pillow==10.2.0
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
|
PyYAML==6.0.1
|
||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
soupsieve==2.5
|
|
||||||
SQLAlchemy==2.0.25
|
SQLAlchemy==2.0.25
|
||||||
staticmap==0.5.7
|
staticmap==0.5.7
|
||||||
typing_extensions==4.9.0
|
typing_extensions==4.9.0
|
||||||
|
|
134
scl.py
134
scl.py
|
@ -1,8 +1,10 @@
|
||||||
import io
|
import io
|
||||||
|
import math
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import yaml
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
from sqlalchemy import create_engine, select
|
from sqlalchemy import create_engine, select
|
||||||
|
@ -20,11 +22,15 @@ except requests.JSONDecodeError:
|
||||||
print("JSON could not be loaded from SCL API")
|
print("JSON could not be loaded from SCL API")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
mastodon = Mastodon(access_token="scl_bot_mastodon.secret")
|
config = yaml.safe_load(open("config.yml"))
|
||||||
|
stadiamaps_api_key = config["stadiamaps"]["api_key"]
|
||||||
with open("stadiamaps_api_key.secret", "r+") as stadiamaps_api_key_file:
|
nominatim_url = config["nominatim"]["api_base_url"]
|
||||||
# Reading from a file
|
mastodon = Mastodon(
|
||||||
stadiamaps_api_key = stadiamaps_api_key_file.read()
|
client_id=config["mastodon"]["client_id"],
|
||||||
|
client_secret=config["mastodon"]["client_secret"],
|
||||||
|
access_token=config["mastodon"]["access_token"],
|
||||||
|
api_base_url=config["mastodon"]["api_base_url"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AttribStaticMap(StaticMap, object):
|
class AttribStaticMap(StaticMap, object):
|
||||||
|
@ -181,22 +187,7 @@ with Session(engine) as session:
|
||||||
# If the outage becomes medium/large, it'll then be posted as a new outage on the next run
|
# If the outage becomes medium/large, it'll then be posted as a new outage on the next run
|
||||||
print("Outage is small, will not post")
|
print("Outage is small, will not post")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print("Existing record not found")
|
print("Existing record not found")
|
||||||
post_text = """Seattle City Light is reporting a {} outage in {}.
|
|
||||||
|
|
||||||
Start Date: {}
|
|
||||||
Est. Restoration: {}
|
|
||||||
Cause: {}
|
|
||||||
|
|
||||||
{}""".format(
|
|
||||||
outage_size.lower(),
|
|
||||||
event["city"],
|
|
||||||
start_time.strftime(post_datetime_format),
|
|
||||||
estimated_restoration_time.strftime(post_datetime_format),
|
|
||||||
event["cause"],
|
|
||||||
hashtag_string,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
map = AttribStaticMap(
|
map = AttribStaticMap(
|
||||||
|
@ -212,10 +203,92 @@ Cause: {}
|
||||||
)
|
)
|
||||||
map.add_polygon(polygon)
|
map.add_polygon(polygon)
|
||||||
map_image = map.render()
|
map_image = map.render()
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
def num2deg(xtile, ytile, zoom):
|
||||||
|
n = 1 << zoom
|
||||||
|
lon_deg = xtile / n * 360.0 - 180.0
|
||||||
|
lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
|
||||||
|
lat_deg = math.degrees(lat_rad)
|
||||||
|
return lat_deg, lon_deg
|
||||||
|
|
||||||
|
center_lat_lon = num2deg(map.x_center, map.y_center, map.zoom)
|
||||||
|
|
||||||
|
# Zoom level 17 ensures that we won't get any building/POI names, just street names
|
||||||
|
geocode_url = "{nominatim_url}/reverse?lat={lat}&lon={lon}&format=geocodejson&zoom=17".format(
|
||||||
|
nominatim_url=nominatim_url,
|
||||||
|
lat=center_lat_lon[0],
|
||||||
|
lon=center_lat_lon[1],
|
||||||
|
)
|
||||||
|
geocode_headers = {"User-Agent": "seattlecitylight-mastodon-bot"}
|
||||||
|
geocode_response = requests.get(
|
||||||
|
geocode_url, headers=geocode_headers
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
geocode = geocode_response.json()
|
||||||
|
except requests.JSONDecodeError:
|
||||||
|
print("JSON could not be loaded from nominatim API")
|
||||||
|
raise
|
||||||
|
|
||||||
|
if (
|
||||||
|
geocode["features"][0]["properties"]["geocoding"]["city"]
|
||||||
|
!= "Seattle"
|
||||||
|
):
|
||||||
|
city_not_seattle_text = " of {}".format(
|
||||||
|
geocode["features"][0]["properties"]["geocoding"]["city"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
city_not_seattle_text = ""
|
||||||
|
|
||||||
|
street = geocode["features"][0]["properties"]["geocoding"]["name"]
|
||||||
|
|
||||||
|
if "locality" in geocode["features"][0]["properties"]["geocoding"]:
|
||||||
|
locality = geocode["features"][0]["properties"]["geocoding"]
|
||||||
|
if locality == "Uptown":
|
||||||
|
locality = "Lower Queen Anne"
|
||||||
|
|
||||||
|
alt_text = "A map showing the location of the outage, centered around {} in the {} area{}.".format(
|
||||||
|
street,
|
||||||
|
locality,
|
||||||
|
city_not_seattle_text,
|
||||||
|
)
|
||||||
|
area_text = "the {} area{}".format(
|
||||||
|
locality, city_not_seattle_text
|
||||||
|
)
|
||||||
|
elif (
|
||||||
|
"district" in geocode["features"][0]["properties"]["geocoding"]
|
||||||
|
):
|
||||||
|
alt_text = "A map showing the location of the outage, centered around {} in the {} area{}.".format(
|
||||||
|
street,
|
||||||
|
geocode["features"][0]["properties"]["geocoding"][
|
||||||
|
"district"
|
||||||
|
],
|
||||||
|
city_not_seattle_text,
|
||||||
|
)
|
||||||
|
area_text = "the {} area{}".format(
|
||||||
|
geocode["features"][0]["properties"]["geocoding"][
|
||||||
|
"district"
|
||||||
|
],
|
||||||
|
city_not_seattle_text,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
alt_text = "A map showing the location of the outage, centered around {} in {}.".format(
|
||||||
|
street,
|
||||||
|
geocode["features"][0]["properties"]["geocoding"]["city"],
|
||||||
|
)
|
||||||
|
area_text = geocode["features"][0]["properties"]["geocoding"][
|
||||||
|
"city"
|
||||||
|
]
|
||||||
|
except Exception:
|
||||||
|
alt_text = "A map showing the location of the outage."
|
||||||
|
|
||||||
with io.BytesIO() as map_image_file:
|
with io.BytesIO() as map_image_file:
|
||||||
map_image.save(map_image_file, format="PNG", optimize=True)
|
map_image.save(map_image_file, format="PNG", optimize=True)
|
||||||
map_media_post = mastodon.media_post(
|
map_media_post = mastodon.media_post(
|
||||||
map_image_file.getvalue(), mime_type="image/png"
|
map_image_file.getvalue(),
|
||||||
|
mime_type="image/png",
|
||||||
|
description=alt_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -225,6 +298,25 @@ Cause: {}
|
||||||
)
|
)
|
||||||
map_media_post = None
|
map_media_post = None
|
||||||
|
|
||||||
|
# Fallback location from the SCL API in case one couldn't be reverse geocoded
|
||||||
|
if not area_text:
|
||||||
|
area_text = event["city"]
|
||||||
|
|
||||||
|
post_text = """Seattle City Light is reporting a {} outage in {}.
|
||||||
|
|
||||||
|
Start Date: {}
|
||||||
|
Est. Restoration: {}
|
||||||
|
Cause: {}
|
||||||
|
|
||||||
|
{}""".format(
|
||||||
|
outage_size.lower(),
|
||||||
|
area_text,
|
||||||
|
start_time.strftime(post_datetime_format),
|
||||||
|
estimated_restoration_time.strftime(post_datetime_format),
|
||||||
|
event["cause"],
|
||||||
|
hashtag_string,
|
||||||
|
)
|
||||||
|
|
||||||
mastodon_post_result = mastodon.status_post(
|
mastodon_post_result = mastodon.status_post(
|
||||||
status=post_text,
|
status=post_text,
|
||||||
media_ids=map_media_post,
|
media_ids=map_media_post,
|
||||||
|
|
Loading…
Reference in a new issue