diff --git a/Rain_alert/.env.example b/Rain_alert/.env.example new file mode 100644 index 00000000..a092f2e0 --- /dev/null +++ b/Rain_alert/.env.example @@ -0,0 +1,5 @@ +OPENWEATHER_API_KEY=Open-Weather-API +TWILIO_ACCOUNT_SID=Your-Account-SID +TWILIO_AUTH_TOKEN=Your-Twilio-Auth-Token +TWILIO_NUMBER=Your-Twilio-Phone-Number +USER_NUMBER=User-Number \ No newline at end of file diff --git a/Rain_alert/.gitignore b/Rain_alert/.gitignore new file mode 100644 index 00000000..7c8c1516 --- /dev/null +++ b/Rain_alert/.gitignore @@ -0,0 +1,5 @@ +venv/ +__pycache__/ +.vscode/ +creds.py +.env \ No newline at end of file diff --git a/Rain_alert/README.md b/Rain_alert/README.md index 047fe46a..256cc1bf 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -33,3 +33,53 @@ Before you begin, ensure you have the following installed: 4. **OpenWeather API Key** – [Generate your key here](https://openweathermap.org/api) --- + +## 📥 Installation +To install the program and run the program +- Go to the shell + ```shell + $ git clone https://github.com/sakhileln/Code-Crunch-Marathon.git + ``` +- Change directory to the project + ```shell + $ cd Code-Crunch-Marathon/Rain_alert/ + ``` +- Install the required packages + ```shell + $ pip install -r requirements.txt + ``` +- Run the project + ```shell + $ python3 main.py + ``` +--- + +## 🛠️ Usage +If you are interested to know if there is expected rain in your vicinity. +1. Create a .env file at the root of the project. +2. Get your OpenWeather API Key and Twilio Authorasation Token and Account SID, and a Twilio Number. +3. Get the number you like to receive the SMS forecast on. +4. Add the items mentioned above to .env file you just created. +5. Use .env.sample file as a guide for adding items on .env file +6. Run the project and you will receive a SMS message with the forecast. + +--- + +## 👨‍💻 Contributing +Contributions are welcome! If you have suggestions for improvements or new features, please fork the repository and create a pull request. +1. Fork the repository +2. Create your feature branch: + ```bash + git checkout -b feature/YourFeature + ``` +3. Commit your changes: + ```bash + git commit -m "Add some feature" + ``` +4. Push to the branch: + ```bash + git push origin feature/YourFeature + ``` +5. Open a pull request. + +--- \ No newline at end of file diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py new file mode 100644 index 00000000..47b9db41 --- /dev/null +++ b/Rain_alert/helper.py @@ -0,0 +1,137 @@ +""" +A module for helper functions. +""" + +from dotenv import load_dotenv +from os import getenv +from requests import get +from requests.exceptions import HTTPError +from samples import wx_sample_response +from twilio.rest import Client + + +load_dotenv() # Take environment variables from .env + +def get_weather_data(lat: float, lon: float, exclude: str) -> dict: + """ + Get current weather from the given city's latitude and longitude. + + Parameters: + lat (float): Angular distance of place north/south of equater + lonv(float): Angular distance of place east/west of greenwich meridian + Return: + weather_data (dict): All weather information about the city + """ + + URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={getenv('OPENWEATHER_API')}" + test_url = "https://api.github.com" + + #f"api.openweathermap.org/data/2.5/weather?q=London,uk&APPID={api_key}" + + # Catch any errors that may arise trying to GET the Weather data + try: + response = get(URL) + response.raise_for_status() + except HTTPError as http_err: + print(f"HTTP Error occured: {http_err}") + except Exception as e: + print(f"Other error occured: {e}") + else: + return response.json() # If request was a success + + +def get_user_location() -> tuple[str, str]: + """ + Get current user location and return coordinates (lat, lon) + + Parameters: + None + Return: + location (tuple): Location of user in latitude and longitude + """ + + url = "https://ipinfo.io/" + try: + response = get(url) + response.raise_for_status() + except HTTPError as http_err: + print(f"HTTP Error occured: {http_err}") + except Exception as e: + print(f"Other error occured: {e}") + else: + json_format = response.json() # Make response into json for referencing + location_str = tuple(json_format['loc'].split(",")) # Get lat, long, make them a tuple + # Convert the str location to a float and give it back + return tuple(map(float, location_str)) + + +def process_weather(weather_data: dict) -> str: + """ + Process weather from given location, give rain warning if needed + + Parameters: + weather_data (dict): Weather information from a given location + Return: + weather_forecast (str): Warning to user if there is rain + """ + warning_words = [ + "rain", + "drizzle", + "precipitation", + "thunderstorms", + "showers", + "hail", + "sleet", + "snow", + "snowflake" + ] + + # Get weather data from the dictionary at 'data' key element 0 + dict_wx_data = weather_data['data'][0] + # Get dictionary with weather data we are interested on + weather_dict = dict_wx_data['weather'][0] + # Issue the warning + for item in weather_dict: + for warning in warning_words: + if type(weather_dict[item]) == int: # Skip integeer items + continue + else: + if warning in weather_dict[item].lower(): + return "Rain is expected in you area (Time Expected: 1 hour), get to shelter." + return "Clear blue sky, go to the swimming pool" + + +def send_sms(message: str) -> dict: + """ + Send SMS to verified number from predefined number. + + Parameters: + message (str): Message we want to send to the user + Return: + response (dict): Response from the attempt to send the message + """ + + client = Client(str(getenv('TWILIO_ACCOUNT_SID')), str(getenv('TWILIO_AUTH_TOKEN'))) + + message = client.messages.create( + from_= str(getenv('TWILIO_NUMBER')), + body = message, + to = str(getenv('USER_NUMBER')) + ) + + return message.sid + + +if __name__ == "__main__": + # Test cases + # Test data + # Johannesburg (lat, lon) + # lat = -26.204103 + # lon = 28.047304 + # exclude = "hourly" + # print(f"Hardcoded: ({lat}, {lon})") + # print(f"Retrieval: {get_user_location()}") + # print(get_weather_data(lat, lon, exclude)) + # message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento." + # print(send_sms(message)) + print(process_weather(wx_sample_response)) \ No newline at end of file diff --git a/Rain_alert/main.py b/Rain_alert/main.py index e69de29b..8eeeb45c 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -0,0 +1,37 @@ +""" +A module to check rain forecast and arn the user through Twilio SMS +""" + +from helper import ( + get_user_location, + get_weather_data, + send_sms, + process_weather +) + + +def main() -> None: + """ + Main driver function for the program. + + Parameters + None + Return: + None + """ + # Get user location + location = get_user_location() + # Uunpack tuple to latitude and longitude + lat, lon = location + # Get hourly weather data + exclude = 'hourly' + hourly_forcast = get_weather_data(lat, lon, exclude) + # Process that data and formulate warning for the user + warning = process_weather(hourly_forcast) + # Send the user a warning SMS + send_sms(warning) + + +if __name__ == "__main__": + # Start the program + main() diff --git a/Rain_alert/requirements.txt b/Rain_alert/requirements.txt new file mode 100644 index 00000000..b93584b2 --- /dev/null +++ b/Rain_alert/requirements.txt @@ -0,0 +1,25 @@ +aiohappyeyeballs==2.4.3 +aiohttp==3.10.10 +aiohttp-retry==2.9.1 +aiosignal==1.3.1 +async-timeout==4.0.3 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +decorator==5.1.1 +frozenlist==1.5.0 +future==1.0.0 +geocoder==1.38.1 +idna==3.10 +multidict==6.1.0 +propcache==0.2.0 +PyJWT==2.9.0 +python-dotenv==1.0.1 +ratelim==0.1.6 +requests==2.32.3 +six==1.16.0 +twilio==9.3.6 +typing_extensions==4.12.2 +urllib3==2.2.3 +yarl==1.17.1 diff --git a/Rain_alert/samples.py b/Rain_alert/samples.py new file mode 100644 index 00000000..08709cea --- /dev/null +++ b/Rain_alert/samples.py @@ -0,0 +1,65 @@ +# Sample response from Openweather API +wx_sample_response = { + "lat": 52.2297, + "lon": 21.0122, + "timezone": "Sakhile/Mars", + "timezone_offset": 3600, + "data": [ + { + "dt": 1645888976, + "sunrise": 1645853361, + "sunset": 1645891727, + "temp": 279.13, + "feels_like": 276.44, + "pressure": 1029, + "humidity": 64, + "dew_point": 272.88, + "uvi": 0.06, + "clouds": 0, + "visibility": 10000, + "wind_speed": 3.6, + "wind_deg": 340, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "clear sky", + "icon": "01d" + } + ] + } + ] +} + +sample_dict_wx_data = { + "dt": 1645888976, + "sunrise": 1645853361, + "sunset": 1645891727, + "temp": 279.13, + "feels_like": 276.44, + "pressure": 1029, + "humidity": 64, + "dew_point": 272.88, + "uvi": 0.06, + "clouds": 0, + "visibility": 10000, + "wind_speed": 3.6, + "wind_deg": 340, + "weather": [ + { + "id": 800, + "main": "Clear", + "description": "clear sky", + "icon": "01d" + } + ] +} + +# Sample response from Twilio +# 400 - BAD REQUEST - The data given in the POST or PUT failed validation. Inspect the response body for details. +res = { + "code": 21608, + "message": "The number +27082581XXXX is unverified. Trial accounts cannot send messages to unverified numbers; verify +27082581XXXX at twilio.com/user/account/phone-numbers/verified, or purchase a Twilio number to send messages to unverified numbers", + "more_info": "https://www.twilio.com/docs/errors/21608", + "status": 400 +} \ No newline at end of file diff --git a/birthday_wisher/.gitignore b/birthday_wisher/.gitignore new file mode 100644 index 00000000..76317214 --- /dev/null +++ b/birthday_wisher/.gitignore @@ -0,0 +1,3 @@ +.venv/ +__pycache__/ +.vscode/ diff --git a/birthday_wisher/helper.py b/birthday_wisher/helper.py new file mode 100644 index 00000000..bc224866 --- /dev/null +++ b/birthday_wisher/helper.py @@ -0,0 +1,44 @@ +""" +A helper module for helper funcions. +""" + +import gspread +from oauth2client.service_account import ServiceAccountCredentials + + +def read_spreadsheet() -> list[list]: + """ + Authenticat and read a Google Sheet using gspread + + Parameters: + None + Return: + values (list): Values in a spreadsheet + """ + # Define the scope for the Sheets API and Google Drive API + SCOPES = [ + "https://www.googleapis.com/auth/spreadsheets", + "https://www.googleapis.com/auth/drive.file", + ] + # Use the credentials file from Google Cloud Console + credentials = ServiceAccountCredentials.from_json_keyfile_name( + "credentials.json", SCOPES + ) + + # Authenticate using credentials and create client + gc = gspread.authorize(credentials) + spreadsheet = gc.open("Birthday Spreadsheet") + + worksheet = spreadsheet.get_worksheet(0) # Get first sheet + + # Read values from sheet + values = worksheet.get_all_values() + + return values + + +if __name__ == "__main__": + # Test cases + values = read_spreadsheet() + for row in values: + print(row) diff --git a/birthday_wisher/requirements.txt b/birthday_wisher/requirements.txt new file mode 100644 index 00000000..22d19de4 --- /dev/null +++ b/birthday_wisher/requirements.txt @@ -0,0 +1,18 @@ +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +google-auth==2.36.0 +google-auth-oauthlib==1.2.1 +gspread==6.1.4 +httplib2==0.22.0 +idna==3.10 +oauth2client==4.1.3 +oauthlib==3.2.2 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 +pyparsing==3.2.0 +requests==2.32.3 +requests-oauthlib==2.0.0 +rsa==4.9 +six==1.16.0 +urllib3==2.2.3