Skip to content

Commit

Permalink
Code Refactor eruv_alerts.py
Browse files Browse the repository at this point in the history
Changed camelCase to snake_case. Added shorten_message function.
Fixed bugs.
  • Loading branch information
Sliicy committed Apr 17, 2020
1 parent 1563e16 commit cee40e3
Showing 1 changed file with 86 additions and 60 deletions.
146 changes: 86 additions & 60 deletions eruv_alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@

# Imports:
from twilio.rest import Client
import gspread, argparse
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import urllib.request, json, argparse

# Add random time delay to prevent being flagged.
# Add random time delay to prevent being flagged:
from random import randint
from time import sleep
import random

# This function, being data-agnostic, extracts all key-values found in any multilevel JSON file, without knowing the order/hierarchy of the data:
# Obtained from: https://hackersandslackers.com/extract-data-from-complex-json-python/
def extract_values(obj, key):
"""Pull all values of specified key from nested JSON."""
arr = []
Expand All @@ -33,79 +34,99 @@ def extract(obj, arr, key):
results = extract(obj, arr, key)
return results

def shorten_message(message):
"""This function recursively tries to shorten a message
to under 160 characters"""
if len(message) =< 160:
return message
else:
if '50 min' in message:
return shorten_message(message.replace(' (50 min)', ''))
else:
# Warn if message still exceeds 160 characters:
print('Message for ' + city + ' exceeds 160 character limit!\nMessage: ' + message)
quit()

# Define a list of random greetings to reduce spam detection and add variety:
greetings = ['a great', 'a wonderful', 'an amazing', 'a good']

# Initialize argument interpretation:
parser = argparse.ArgumentParser(description='This script sends SMS messages via Twilio to subscribers on a Google Sheet.')
parser.add_argument('--delayed', help='Slowly send out each SMS between 0 - 2 seconds.', action='store_true')
parser.add_argument('--donate', help='Append reminder to donate for select cities.', action='store_true')
parser.add_argument('--no-candlelighting', help='Skip appending candle-lighting times.', action='store_true')
parser.add_argument('--no-havdalah', help='Skip appending havdalah times.', action='store_true')
parser.add_argument('--test', help='Test run without actually sending.', action='store_true')
parser.add_argument('-v', '--verbose', help='Helpful for debugging.', action='store_true')
arguments = parser.parse_args()


# Google Authentication from external JSON file:
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('keys/google-auth.json', scope)
creds = ServiceAccountCredentials.from_json_keyfile_name('keys/google_auth.json', scope)
gclient = gspread.authorize(creds)
if arguments.verbose: print('Google Authenticated successfully.\n')
if arguments.verbose:
print('Google Authenticated successfully.\n')


# Twilio Authentication from external JSON file:
with open('keys/twilio_auth.json') as file:
twilio_file = json.load(file)
client = Client(twilio_file['account-sid'], twilio_file['password'])
if arguments.verbose: print('Twilio Authenticated successfully.\n')
if arguments.verbose:
print('Twilio Authenticated successfully.\n')

# Load Open Weather Map Authentication from external JSON file:
with open('keys/open_weather_map.json') as file:
open_weather_map = json.load(file)


# Load lists from sheets:
subscriberSheet = gclient.open('Eruv List').worksheet('Subscribers')
rabbiSheet = gclient.open('Eruv List').worksheet('Rabbis')
statusSheet = gclient.open('Eruv List').worksheet('Status')
if arguments.verbose: print('Google Sheets loaded successfully.\n')
subscriber_sheet = gclient.open('Eruv List').worksheet('Subscribers')
rabbi_sheet = gclient.open('Eruv List').worksheet('Rabbis')
status_sheet = gclient.open('Eruv List').worksheet('Status')
if arguments.verbose:
print('Google Sheets loaded successfully.\n')


# Create arrays of all elements from the sheets. Skip top row (Timestamp, Phone Number, City, Rabbi's City, Zip Code):
allNumbers = subscriberSheet.col_values(2)[1:]
allUserCities = subscriberSheet.col_values(3)[1:]
allRabbiCities = rabbiSheet.col_values(3)[1:]
allRabbiZips = rabbiSheet.col_values(4)[1:]
allCities = statusSheet.col_values(1)
cityStatuses = statusSheet.col_values(2)
if arguments.verbose: print('Cities loaded successfully.\n')

# Define a list of random greetings to reduce spam detection and add variety:
greetings = ['a great', 'a wonderful', 'an amazing', 'a good']
all_numbers = subscriber_sheet.col_values(2)[1:]
all_user_cities = subscriber_sheet.col_values(3)[1:]
all_rabbi_cities = rabbi_sheet.col_values(3)[1:]
all_rabbi_zipcodes = rabbi_sheet.col_values(4)[1:]
all_cities = status_sheet.col_values(1)
city_statuses = status_sheet.col_values(2)
if arguments.verbose:
print('Cities loaded successfully.\n')

# For each city in Status Sheet:
cityIndex = 0
for city in allCities:
city_index = 0
for city in all_cities:

# Skip if the city status is Pending:
if cityStatuses[cityIndex] == 'Pending':
cityIndex += 1
if city_statuses[city_index] == 'Pending':
city_index += 1
continue

# Get zipcode of city:
zipIndex = 0
zip_index = 0
zipcode = 0
for rabbiCity in allRabbiCities:
if rabbiCity == city:
zipcode = allRabbiZips[zipIndex]
for rabbi_city in all_rabbi_cities:
if rabbi_city == city:
zipcode = all_rabbi_zipcodes[zip_index]
break
zipIndex += 1
zip_index += 1

# Catch invalid Zip Code:
if zipcode == 0:
print('\nInvalid zipcode detected for ' + city + '!\n')
quit()

# Get Candle-lighting, Havdalah, and Parsha/Chag from hebcal.com:
hebcalURL = 'https://www.hebcal.com/shabbat/?cfg=json&zip=' + str(zipcode) + '&m=50&a=on'
response = json.loads(urllib.request.urlopen(hebcalURL, timeout=15).read().decode('utf-8'))
hebcal_URL = 'https://www.hebcal.com/shabbat/?cfg=json&zip=' + str(zipcode) + '&m=50&a=on'
response = json.loads(urllib.request.urlopen(hebcal_URL, timeout=15).read().decode('utf-8'))

# Find first occurrence of Candle-lighting from JSON:
candleLighting = [i for i in extract_values(response, 'title') if 'Candle' in i][0]
candle_lighting = [i for i in extract_values(response, 'title') if 'Candle' in i][0]

# Find first occurrence of Havdalah from JSON only if Havdalah exists:
havdalah = ''
Expand All @@ -114,9 +135,10 @@ def extract(obj, arr, key):
if len([i for i in extract_values(response, 'title') if 'Havdalah' in i]) > 0:
havdalah = [i for i in extract_values(response, 'title') if 'Havdalah' in i][0]
havdalah = havdalah + '. '
if len(havdalah) == 0: print('No Havdalah time detected!')
if len(havdalah) == 0:
print('No Havdalah time detected!')

# No holiday by default:
# Store if holiday:
holiday = ''

# Check if any Parsha is listed in JSON:
Expand All @@ -132,53 +154,57 @@ def extract(obj, arr, key):
holiday = ' and Yom Tov'

# If there's a thunderstorm or tornado, warn users to be vigilant:
weatherResponse = json.loads(urllib.request.urlopen('https://api.openweathermap.org/data/2.5/weather?zip=' + str(zipcode) + ',us&appid=' + open_weather_map['api-key'], timeout=15).read().decode('utf-8'))
temperature = 'Temperature: ' + str(int(1.8 * (weatherResponse['main']['temp'] - 273.15) + 32)) + 'F'
humidity = str(weatherResponse['main']['humidity']) + '% humid'
weather_response = json.loads(urllib.request.urlopen('https://api.openweathermap.org/data/2.5/weather?zip=' + str(zipcode) + ',us&appid=' + open_weather_map['api-key'], timeout=15).read().decode('utf-8'))
temperature = 'Temperature: ' + str(int(1.8 * (weather_response['main']['temp'] - 273.15) + 32)) + 'F'
humidity = str(weather_response['main']['humidity']) + '% humid'

# Prequel & sequel change if there's a storm:
prequel = ' The '
sequel = ''

if [i for i in extract_values(weatherResponse, 'description') if 'thunderstorm' in i or 'tornado' in i]:
if [i for i in extract_values(weather_response, 'description') if 'thunderstorm' in i or 'tornado' in i]:
prequel = ' As of now, the '
sequel = 'If winds exceed 35 mph, consider the Eruv down. '

# Loop through all users from city and send:
userIndex = 0
userCount = 0
for cityFound in allUserCities:
for cityItem in [x.strip() for x in cityFound.split(',')]:
if city == cityItem:
user_index = 0
population = 0
for city_found in all_user_cities:
for city_item in [x.strip() for x in city_found.split(',')]:
if city == city_item:

if arguments.no_candlelighting:
candle_lighting = ''

if arguments.no_havdalah:
havdalah = ''

# Final message:
havdalah = ''
message = parsha + prequel + city + ' Eruv is ' + cityStatuses[cityIndex] + '. ' + sequel + candleLighting + '. ' + havdalah + ('Have ' + random.choice(greetings) + ' Shabbos' + holiday + '!' if sequel == '' else '.')
message = parsha + prequel + city + ' Eruv is ' + city_statuses[city_index] + '. ' + sequel + candle_lighting + '. ' + havdalah + ('Have ' + random.choice(greetings) + ' Shabbos' + holiday + '!' if sequel == '' else '.')

# Try to shorten the message if necessary:
if len(message) > 160: message = message.replace(' (50 min)', '')

# Warn if message still exceeds 160 characrters:
if len(message) > 160:
print('Message for ' + city + ' exceeds 160 character limit!\nMessage: ' + message)
quit()
message = shorten_message(message)

# Add optional parameters to select cities: (links may be flagged as spam)
#if city == 'North Miami Beach': message = message + ' Please visit bit.ly/nmberuv to cover the costs.'
if arguments.donate and city == 'North Miami Beach':
message = message + ' Please visit bit.ly/nmberuv to cover the costs.'

# Sanitize the phone number from special characters:
cleanNumber = '+1' + str(allNumbers[userIndex]).replace("-", "").replace(" ", "").replace("(", "").replace(")", "").replace(".", "").replace("_", "")
clean_number = '+1' + str(all_numbers[user_index]).replace("-", "").replace(" ", "").replace("(", "").replace(")", "").replace(".", "").replace("_", "")

# Display and send:
if arguments.verbose: print(cleanNumber + ' > ' + message)
if arguments.verbose:
print(clean_number + ' > ' + message)

# Send if no testing argument:
if not arguments.test: twilio_message = client.messages.create(to=cleanNumber, from_=twilio_file['phone'], body=message)
if not arguments.test:
twilio_message = client.messages.create(to=clean_number, from_=twilio_file['phone'], body=message)

# Wait a random amount of seconds between sending (0 - 2 seconds):
#sleep(randint(0,2))
if arguments.delayed:
sleep(randint(0,2))

userCount += 1
userIndex += 1
print('\n' + str(userCount) + ' users notified in ' + city + '.\n')
cityIndex += 1
population += 1
user_index += 1
print('\n' + str(population) + ' users ' + ('would have been ' if arguments.test else '') + 'notified in ' + city + '.\n')
city_index += 1

0 comments on commit cee40e3

Please sign in to comment.