Compare commits

..

No commits in common. "eaa32c6987eac17e0ff0c95a5fe1299f21df286c" and "f577a8b8ca6f784f00fc767539fc289cd4eb7017" have entirely different histories.

4 changed files with 23 additions and 125 deletions

2
.gitignore vendored
View file

@ -166,5 +166,3 @@ cython_debug/
*.secret *.secret
*.db *.db
.DS_Store .DS_Store
config.yml

View file

@ -1,9 +0,0 @@
stadiamaps:
api_key:
nominatim:
api_base_url:
mastodon:
client_id:
client_secret:
access_token:
api_base_url:

View file

@ -1,3 +1,4 @@
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
@ -8,9 +9,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
View file

@ -1,10 +1,8 @@
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
@ -22,15 +20,11 @@ except requests.JSONDecodeError:
print("JSON could not be loaded from SCL API") print("JSON could not be loaded from SCL API")
raise raise
config = yaml.safe_load(open("config.yml")) mastodon = Mastodon(access_token="scl_bot_mastodon.secret")
stadiamaps_api_key = config["stadiamaps"]["api_key"]
nominatim_url = config["nominatim"]["api_base_url"] with open("stadiamaps_api_key.secret", "r+") as stadiamaps_api_key_file:
mastodon = Mastodon( # Reading from a file
client_id=config["mastodon"]["client_id"], stadiamaps_api_key = stadiamaps_api_key_file.read()
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):
@ -187,7 +181,22 @@ 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(
@ -203,92 +212,10 @@ with Session(engine) as session:
) )
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(), map_image_file.getvalue(), mime_type="image/png"
mime_type="image/png",
description=alt_text,
) )
except Exception as e: except Exception as e:
@ -298,25 +225,6 @@ with Session(engine) as session:
) )
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,