Skip to content

Commit

Permalink
fresh commit for repo to clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
smcalilly committed Sep 26, 2024
0 parents commit a57f8f3
Show file tree
Hide file tree
Showing 16 changed files with 694 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Get parks data

jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Check out this repo
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Notify backcountry subscriptions
run: make notify
env:
GMAIL_APP_PASSWORD: ${{ secrets.GMAIL_APP_PASSWORD }}
- name: Commit and push if it changed
run: |-
git config user.name "Automated"
git config user.email "[email protected]"
git add -A
timestamp=$(date -u)
git commit -m "Latest data: ${timestamp}" || exit 0
git push
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__
.env
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
destination_ids=4675321

.INTERMEDIATE : data/notifications.json data/finished/backcountry_sites.json data/intermediate/backcountry_sites.json

notify : data/notifications.json
python -m backcountry.notify $<
cp $< data/finished/notifications-$(shell date +%s).json

data/notifications.json : data/finished/backcountry_sites.json
python -m backcountry.subscriptions $< > $@

data/finished/backcountry_sites.json : data/intermediate/backcountry_sites.json
python -m backcountry.get_site_availability $< > $@

data/intermediate/backcountry_sites.json :
python backcountry/get_backcountry_sites.py > $@
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# parks

A lightweight data pipeline that sends email notifications to subscribers whenever a reservation opens up at their favorite Glacier National Park backcountry campsite. The pipeline is orchestrated by a Makefile that is setup to run automatically as a Github Action.

### Scripts overview
1. Manually add new subscribers with `backcountry/add_user.py`, passing arguments with their preferred campsites and dates.
2. `backcountry/get_backcountry_sites.py` gets a list of valid campsites and dumps it into a file.
3. `backcountry/get_site_availability.py` downloads the availability of each campsite.
4. `backcountry/subscriptions.py` checks to see if any of the subscribers' chosen campsites have a reservation opening on their preferred dates.
5. `backcountry/notify.py` notifies subscribers if there was an opening.

Thank you robot! Time to go outside!
53 changes: 53 additions & 0 deletions backcountry/add_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from datetime import datetime
import json
import argparse

# Set up the argument parser
parser = argparse.ArgumentParser(
description='Add a user to a list of backcountry sites.'
)
parser.add_argument('--email', type=str, help='The user\'s email address')
parser.add_argument(
'--sites',
type=str,
help='The list of sites the user is registered for, separated by commas',
)
parser.add_argument(
'--dates',
type=str,
help='The dates the user is registered for, separated by commas',
)

# Parse the arguments
args = parser.parse_args()

email = args.email
sites = args.sites.split(',')
dates = [
datetime.strptime(date_str, '%m/%d/%Y').strftime('%Y-%m-%d')
for date_str in args.dates.split(',')
]

with open('data/admin/backcountry_sites.json', 'r') as f:
sites_map = json.load(f)

new_subscription = {email: {'sites': {}}}

for id, site in sites_map.items():

if site['campsite'] in sites:
if id not in new_subscription[email]:
new_subscription[email]['sites'][id] = {'dates': {}}

for date in dates:
if date not in new_subscription[email]['sites'][id]['dates']:
new_subscription[email]['sites'][id]['dates'][date] = {}

subscriptions_file = 'data/subscriptions.json'
with open(subscriptions_file, 'r') as f:
subscriptions = json.load(f)

subscriptions.update(new_subscription)

with open(subscriptions_file, 'w') as f:
json.dump(subscriptions, f, indent=4)
28 changes: 28 additions & 0 deletions backcountry/get_backcountry_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import requests
import sys
import json
import re



with open('data/finished/permit_content.json') as f:
data = json.load(f)['payload']['divisions']

skip_sites = [
'AEF - Elizabeth Lake Foot Administrative Site',
'AGC - Gable Creek Administrative Site',
'AHO - Hole in the Wall (Admin. Site)',
'AFM - Fifty Mountain Administrative Site',
]

campsites = {}

for division_id, division in data.items():
campsite = division['name']

if re.match(r'[A-Z]+\s-', campsite) and campsite not in skip_sites:
campsites.update(
{division_id: {'district': division['district'], 'campsite': campsite}}
)

sys.stdout.write(json.dumps(campsites))
54 changes: 54 additions & 0 deletions backcountry/get_site_availability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sys
import json
import requests

from util.models import Campsite

sites_file = sys.argv[1]

with open(sites_file) as f:
sites = json.load(f)

permit_itenerary_url = 'https://www.recreation.gov/api/permititinerary/4675321/division/{division_id}/availability/month?month={month}&year={year}'
year = 2023

campsites = {}

for site_id, site in sites.items():
campsite = Campsite(
name=site['campsite'],
district=site['district'],
division_id=site_id,
)

for month in []:

url = permit_itenerary_url.format(
division_id=campsite.division_id, month=month, year=year
)
response = requests.get(
url,
headers={
'Accept-Language': 'en-US',
'Host': 'www.recreation.gov',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:109.0) Gecko/20100101 Firefox/110.0',
},
)
days = response.json()['payload']['quota_type_maps'].get(
'ConstantQuotaUsageDaily'
)

d = {}

for date, status in days.items():
d.update(
{date: {'remaining': status['remaining'], 'total': status['total']}}
)

campsite.dates.update(d)

campsites.update({campsite.division_id: campsite.to_dict()})

sys.stdout.write(json.dumps(campsites))
100 changes: 100 additions & 0 deletions backcountry/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# import sendgrid

# from sendgrid.helpers.mail import Mail, Email, To, Content

# my_sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))

# # Change to your verified sender
# from_email = Email("[email protected]")

# # Change to your recipient
# to_email = To("[email protected]")

# subject = "Lorem ipsum dolor sit amet"
# content = Content("text/plain", "consectetur adipiscing elit")

# mail = Mail(from_email, to_email, subject, content)

# # Get a JSON-ready representation of the Mail object
# mail_json = mail.get()

# # Send an HTTP POST request to /mail/send
# response = my_sg.client.mail.send.post(request_body=mail_json)

import os
import json
import sys

try:
from dotenv import load_dotenv

load_dotenv('.env')
except ModuleNotFoundError:
pass

import smtplib
from email.mime.text import MIMEText


def make_message(email, text):
return {
"from_email": "[email protected]",
"from_name": "Sam McAlilly",
"subject": "Your campsite status alerts",
"text": text,
"to": [{"email": email, "type": "to"}],
}


def send_email(subject, body, sender, recipients, password):
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ', '.join(recipients)
smtp_server = smtplib.SMTP_SSL('smtp.gmail.com', 465)
smtp_server.login(sender, password)
smtp_server.sendmail(sender, recipients, msg.as_string())
smtp_server.quit()


# def send_email(message):
# key = os.getenv('MAILCHIMP_API_KEY')
# try:
# mailchimp = MailchimpTransactional.Client(api_key=key)
# response = mailchimp.messages.send({"message": message})
# except ApiClientError as error:
# print("An exception occurred: {}".format(error.text))


target_file = sys.argv[1]
with open(target_file) as f:
notifications = json.load(f)

print('notifications', notifications)

if notifications:
for email, subscription in notifications.items():
sites = {}
for site_name, dates in subscription.items():
sites[site_name] = []
for date, status in dates.items():
sites[site_name].append((date, status['remaining']))

text = f""

for site_name, details in sites.items():
for d in details:
text += f"{site_name} has {d[1]} available for {d[0]}\n"

text += "\nU better hurry up and book ur campsite. Visit https://www.recreation.gov/permits/4675321/registration/detailed-availability/ & grab that permit.\n\nO yea here is a map: https://www.nps.gov/glac/planyourvisit/upload/Wilderness-Campground-Map-2023.pdf\n\n"

send_email(
"Your tracked campsite(s) in Glacier changed",
text,
'[email protected]',
[email],
os.getenv('GMAIL_APP_PASSWORD'),
)
print('sent email to', email)
else:
print('no notifications to send')
49 changes: 49 additions & 0 deletions backcountry/subscriptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import json
import sys

with open('data/subscriptions.json') as f:
subscriptions = json.load(f)

with open('data/finished/backcountry_sites.json') as f:
sites = json.load(f)

notify = {}


for email, subscription in subscriptions.items():
# subscription = Subscription(email=email, dates=s['dates'], campsite=sites.get(s['site_id'])
notify.update({email: {}})

for id, subscription_dates in subscription['sites'].items():
site = sites.get(id)

for date, status in subscription_dates['dates'].items():
current = site['dates'].get(date)

sub_date = subscription_dates['dates'].get(date)

if not current:
continue

if not sub_date:
if current['remaining'] > 0:
try:
notify[email][site['name']].update({date: current})
except KeyError:
notify[email].update({site['name']: {date: current}})
elif sub_date.get('remaining') != current['remaining']:
if current['remaining'] > 0:
try:
notify[email][site['name']].update({date: current})
except KeyError:
notify[email].update({site['name']: {date: current}})

subscription['sites'][id]['dates'].update({date: current})

if notify[email] == {}:
del notify[email]

with open('data/subscriptions.json', 'w') as f:
json.dump(subscriptions, f)

sys.stdout.write(json.dumps(notify))
Loading

0 comments on commit a57f8f3

Please sign in to comment.