commit 9669469b54730273f2c0d243b165045145bdf089 Author: spla Date: Mon Jul 11 22:37:14 2022 +0200 Codi del bot @temps@mastodont.cat alliberat diff --git a/apikey-setup.py b/apikey-setup.py new file mode 100644 index 0000000..2762502 --- /dev/null +++ b/apikey-setup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pdb +import getpass +import fileinput,re +import os +import sys + +def create_dir(): + if not os.path.exists('secrets'): + os.makedirs('secrets') + +def create_file(): + if not os.path.exists('secrets/apikey.txt'): + with open('secrets/apikey.txt', 'w'): pass + print(secrets_filepath + " created!") + +def write_params(): + with open(secrets_filepath, 'a') as the_file: + print("Writing api key parameter to " + secrets_filepath) + the_file.write('api_key: \n') + +def modify_file(file_name,pattern,value=""): + fh=fileinput.input(file_name,inplace=True) + for line in fh: + replacement=pattern + value + line=re.sub(pattern,replacement,line) + sys.stdout.write(line) + fh.close() + +def get_parameter( parameter, file_path ): + + # Check if secrets file exists + if not os.path.isfile(file_path): + print("File %s not found, creating it."%file_path) + create_dir() + create_file() + write_params() + api_key = input('Enter your API key: ') + modify_file(file_path, "api_key: ", value=api_key) + + # Find parameter in file + with open( file_path ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(file_path + " Missing parameter %s "%parameter) + sys.exit(0) + +# main + +if __name__ == '__main__': + + + # Load secrets from secrets file + secrets_filepath = "secrets/apikey.txt" + api_key = get_parameter("api_key", secrets_filepath) + diff --git a/canviats/1.png b/canviats/1.png new file mode 100644 index 0000000..0fa15e9 Binary files /dev/null and b/canviats/1.png differ diff --git a/canviats/10.png b/canviats/10.png new file mode 100644 index 0000000..5b6793a Binary files /dev/null and b/canviats/10.png differ diff --git a/canviats/11.png b/canviats/11.png new file mode 100644 index 0000000..ce7da71 Binary files /dev/null and b/canviats/11.png differ diff --git a/canviats/12.png b/canviats/12.png new file mode 100644 index 0000000..933fd4f Binary files /dev/null and b/canviats/12.png differ diff --git a/canviats/13.png b/canviats/13.png new file mode 100644 index 0000000..5dde486 Binary files /dev/null and b/canviats/13.png differ diff --git a/canviats/14.png b/canviats/14.png new file mode 100644 index 0000000..51876b5 Binary files /dev/null and b/canviats/14.png differ diff --git a/canviats/15.png b/canviats/15.png new file mode 100644 index 0000000..e997296 Binary files /dev/null and b/canviats/15.png differ diff --git a/canviats/16.png b/canviats/16.png new file mode 100644 index 0000000..aaec044 Binary files /dev/null and b/canviats/16.png differ diff --git a/canviats/17.png b/canviats/17.png new file mode 100644 index 0000000..0bac0c5 Binary files /dev/null and b/canviats/17.png differ diff --git a/canviats/18.png b/canviats/18.png new file mode 100644 index 0000000..e99094f Binary files /dev/null and b/canviats/18.png differ diff --git a/canviats/19.png b/canviats/19.png new file mode 100644 index 0000000..deaa815 Binary files /dev/null and b/canviats/19.png differ diff --git a/canviats/2.png b/canviats/2.png new file mode 100644 index 0000000..58998f7 Binary files /dev/null and b/canviats/2.png differ diff --git a/canviats/20.png b/canviats/20.png new file mode 100644 index 0000000..02c4349 Binary files /dev/null and b/canviats/20.png differ diff --git a/canviats/21.png b/canviats/21.png new file mode 100644 index 0000000..48bf998 Binary files /dev/null and b/canviats/21.png differ diff --git a/canviats/22.png b/canviats/22.png new file mode 100644 index 0000000..7ba7f72 Binary files /dev/null and b/canviats/22.png differ diff --git a/canviats/23.png b/canviats/23.png new file mode 100644 index 0000000..93c7e83 Binary files /dev/null and b/canviats/23.png differ diff --git a/canviats/24.png b/canviats/24.png new file mode 100644 index 0000000..cc6510e Binary files /dev/null and b/canviats/24.png differ diff --git a/canviats/25.png b/canviats/25.png new file mode 100644 index 0000000..27cea26 Binary files /dev/null and b/canviats/25.png differ diff --git a/canviats/26.png b/canviats/26.png new file mode 100644 index 0000000..fd8f213 Binary files /dev/null and b/canviats/26.png differ diff --git a/canviats/27.png b/canviats/27.png new file mode 100644 index 0000000..26c2100 Binary files /dev/null and b/canviats/27.png differ diff --git a/canviats/28.png b/canviats/28.png new file mode 100644 index 0000000..1657d0d Binary files /dev/null and b/canviats/28.png differ diff --git a/canviats/29.png b/canviats/29.png new file mode 100644 index 0000000..d472a09 Binary files /dev/null and b/canviats/29.png differ diff --git a/canviats/3.png b/canviats/3.png new file mode 100644 index 0000000..a43e827 Binary files /dev/null and b/canviats/3.png differ diff --git a/canviats/4.png b/canviats/4.png new file mode 100644 index 0000000..fcd1162 Binary files /dev/null and b/canviats/4.png differ diff --git a/canviats/5.png b/canviats/5.png new file mode 100644 index 0000000..a570755 Binary files /dev/null and b/canviats/5.png differ diff --git a/canviats/6.png b/canviats/6.png new file mode 100644 index 0000000..21be377 Binary files /dev/null and b/canviats/6.png differ diff --git a/canviats/7.png b/canviats/7.png new file mode 100644 index 0000000..a66e1eb Binary files /dev/null and b/canviats/7.png differ diff --git a/canviats/8.png b/canviats/8.png new file mode 100644 index 0000000..fc3c4a3 Binary files /dev/null and b/canviats/8.png differ diff --git a/canviats/9.png b/canviats/9.png new file mode 100644 index 0000000..fceed3c Binary files /dev/null and b/canviats/9.png differ diff --git a/db-setup.py b/db-setup.py new file mode 100644 index 0000000..c95cc31 --- /dev/null +++ b/db-setup.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import getpass +import os +import sys +from mastodon import Mastodon +from mastodon.Mastodon import MastodonMalformedEventError, MastodonNetworkError, MastodonReadTimeout, MastodonAPIError +import psycopg2 +from psycopg2 import sql +from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + +# Returns the parameter from the specified file +def get_parameter( parameter, file_path ): + # Check if secrets file exists + if not os.path.isfile(file_path): + print("File %s not found, asking."%file_path) + write_parameter( parameter, file_path ) + #sys.exit(0) + + # Find parameter in file + with open( file_path ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(file_path + " Missing parameter %s "%parameter) + sys.exit(0) + +def write_parameter( parameter, file_path ): + if not os.path.exists('config'): + os.makedirs('config') + print("Setting up finances parameters...") + print("\n") + alarm_db = input("alarm db name: ") + alarm_db_user = input("alarm db user: ") + + with open(file_path, "w") as text_file: + print("alarm_db: {}".format(alarm_db), file=text_file) + print("alarm_db_user: {}".format(alarm_db_user), file=text_file) + +def create_table(db, db_user, table, sql): + + conn = None + + try: + + conn = psycopg2.connect(database = db, user = db_user, password = "", host = "/var/run/postgresql", port = "5432") + cur = conn.cursor() + + print("Creating table.. "+table) + # Create the table in PostgreSQL database + cur.execute(sql) + + conn.commit() + print("Table "+table+" created!") + print("\n") + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + +def create_index(db, db_user, table, index, sql_index): + + conn = None + try: + + conn = psycopg2.connect(database = db, user = db_user, password = "", host = "/var/run/postgresql", port = "5432") + cur = conn.cursor() + + print(f"Creating index...{index}") + # Create the table in PostgreSQL database + cur.execute(sql_index) + + conn.commit() + + print(f"Index {index} created!\n") + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + +############################################################################### +# main + +if __name__ == '__main__': + + # Load configuration from config file + config_filepath = "config/db_config.txt" + alarm_db = get_parameter("alarm_db", config_filepath) + alarm_db_user = get_parameter("alarm_db_user", config_filepath) + + ############################################################ + # create database + ############################################################ + + conn = None + + try: + + conn = psycopg2.connect(dbname='postgres', + user=alarm_db_user, host='', + password='') + + conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + + cur = conn.cursor() + + print("Creating database " + alarm_db + ". Please wait...") + + cur.execute(sql.SQL("CREATE DATABASE {}").format( + sql.Identifier(alarm_db)) + ) + print("Database " + alarm_db + " created!") + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + ############################################################ + + try: + + conn = None + + conn = psycopg2.connect(database = alarm_db, user =alarm_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + # Load configuration from config file + os.remove("config/db_config.txt") + + print("Exiting. Run db-setup again with right parameters") + sys.exit(0) + + finally: + + if conn is not None: + + conn.close() + + print("\n") + print("alarm parameters saved to db-config.txt!") + print("\n") + + ############################################################ + # Create needed tables + ############################################################ + + db = alarm_db + db_user = alarm_db_user + table = 'alarms' + sql = "create table " + table + " (username varchar(30), visibility varchar(8), time time, city varchar(30))" + create_table(db, db_user, table, sql) + + index = "alarm_pkey" + sql_index = f'CREATE UNIQUE INDEX {index} ON {table} (username, city)' + create_index(db, db_user, table, index, sql_index) + + ############################################################ + + print("Done!") + print("Now you can run setup.py!") + print("\n") diff --git a/fonts/DejaVuSerif.ttf b/fonts/DejaVuSerif.ttf new file mode 100644 index 0000000..3cdea31 Binary files /dev/null and b/fonts/DejaVuSerif.ttf differ diff --git a/humitat/humedad.png b/humitat/humedad.png new file mode 100644 index 0000000..14555e8 Binary files /dev/null and b/humitat/humedad.png differ diff --git a/images/fons.jpg b/images/fons.jpg new file mode 100644 index 0000000..921d586 Binary files /dev/null and b/images/fons.jpg differ diff --git a/images/fonsorig.jpg b/images/fonsorig.jpg new file mode 100644 index 0000000..e690a7f Binary files /dev/null and b/images/fonsorig.jpg differ diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 0000000..244bd3b Binary files /dev/null and b/images/logo.png differ diff --git a/requeriments.txt b/requeriments.txt new file mode 100644 index 0000000..e39f175 --- /dev/null +++ b/requeriments.txt @@ -0,0 +1,8 @@ +Mastodon.py>=1.5.1 +urllib3-1.26.4->urllib3-1.26.7 +unidecode>=1.2.0 +psycopg2>=2.8.6 +geopy>=2.1.0 -> 2.2.0 +pillow>=8.2.0 +geotiler>=0.14.4 +geocoder>=1.38.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b4b3bf6 --- /dev/null +++ b/setup.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pdb +import getpass +from mastodon import Mastodon +from mastodon.Mastodon import MastodonMalformedEventError, MastodonNetworkError, MastodonReadTimeout, MastodonAPIError, MastodonIllegalArgumentError +import fileinput,re +import os +import sys + +def create_dir(): + if not os.path.exists('secrets'): + os.makedirs('secrets') + +def create_file(): + if not os.path.exists('secrets/secrets.txt'): + with open('secrets/secrets.txt', 'w'): pass + print(secrets_filepath + " created!") + +def create_config(): + if not os.path.exists(config_filepath): + print(config_filepath + " created!") + with open('config.txt', 'w'): pass + +def write_params(): + with open(secrets_filepath, 'a') as the_file: + print("Writing secrets parameter names to " + secrets_filepath) + the_file.write('uc_client_id: \n'+'uc_client_secret: \n'+'uc_access_token: \n') + +def write_config(): + with open(config_filepath, 'a') as the_file: + the_file.write('mastodon_hostname: \n') + print("adding parameter name 'mastodon_hostname' to "+ config_filepath) + +def read_client_lines(self): + client_path = 'app_clientcred.txt' + with open(client_path) as fp: + line = fp.readline() + cnt = 1 + while line: + if cnt == 1: + print("Writing client id to " + secrets_filepath) + modify_file(secrets_filepath, "uc_client_id: ", value=line.rstrip()) + elif cnt == 2: + print("Writing client secret to " + secrets_filepath) + modify_file(secrets_filepath, "uc_client_secret: ", value=line.rstrip()) + line = fp.readline() + cnt += 1 + +def read_token_line(self): + token_path = 'app_usercred.txt' + with open(token_path) as fp: + line = fp.readline() + print("Writing access token to " + secrets_filepath) + modify_file(secrets_filepath, "uc_access_token: ", value=line.rstrip()) + +def read_config_line(): + with open(config_filepath) as fp: + line = fp.readline() + modify_file(config_filepath, "mastodon_hostname: ", value=hostname) + +def log_in(): + error = 0 + try: + global hostname + hostname = input("Enter Mastodon hostname: ") + user_name = input("User name, ex. user@" + hostname +"? ") + user_password = getpass.getpass("User password? ") + app_name = input("This app name? ") + Mastodon.create_app(app_name, scopes=["read","write"], + to_file="app_clientcred.txt", api_base_url=hostname) + mastodon = Mastodon(client_id = "app_clientcred.txt", api_base_url = hostname) + mastodon.log_in( + user_name, + user_password, + scopes = ["read", "write"], + to_file = "app_usercred.txt" + ) + except MastodonIllegalArgumentError as i_error: + error = 1 + if os.path.exists("secrets/secrets.txt"): + print("Removing secrets/secrets.txt file..") + os.remove("secrets/secrets.txt") + if os.path.exists("app_clientcred.txt"): + print("Removing app_clientcred.txt file..") + os.remove("app_clientcred.txt") + sys.exit(i_error) + except MastodonNetworkError as n_error: + error = 1 + if os.path.exists("secrets/secrets.txt"): + print("Removing secrets/secrets.txt file..") + os.remove("secrets/secrets.txt") + if os.path.exists("app_clientcred.txt"): + print("Removing app_clientcred.txt file..") + os.remove("app_clientcred.txt") + sys.exit(n_error) + except MastodonReadTimeout as r_error: + error = 1 + if os.path.exists("secrets/secrets.txt"): + print("Removing secrets/secrets.txt file..") + os.remove("secrets/secrets.txt") + if os.path.exists("app_clientcred.txt"): + print("Removing app_clientcred.txt file..") + os.remove("app_clientcred.txt") + sys.exit(r_error) + except MastodonAPIError as a_error: + error = 1 + if os.path.exists("secrets/secrets.txt"): + print("Removing secrets/secrets.txt file..") + os.remove("secrets/secrets.txt") + if os.path.exists("app_clientcred.txt"): + print("Removing app_clientcred.txt file..") + os.remove("app_clientcred.txt") + sys.exit(a_error) + finally: + if error == 0: + + create_dir() + create_file() + #Your client id and secret are the two lines in `app_clientcred.txt`, your access + #token is the line in `app_usercred.txt`. + write_params() + client_path = 'app_clientcred.txt' + read_client_lines(client_path) + token_path = 'app_usercred.txt' + read_token_line(token_path) + if os.path.exists("app_clientcred.txt"): + print("Removing app_clientcred.txt temp file..") + os.remove("app_clientcred.txt") + if os.path.exists("app_usercred.txt"): + print("Removing app_usercred.txt temp file..") + os.remove("app_usercred.txt") + print("Secrets setup done!\n") + print("\n") + print("Now you can run versions.py!") + +def modify_file(file_name,pattern,value=""): + fh=fileinput.input(file_name,inplace=True) + for line in fh: + replacement=pattern + value + line=re.sub(pattern,replacement,line) + sys.stdout.write(line) + fh.close() + +# Returns the parameter from the specified file +def get_parameter( parameter, file_path ): + # Check if secrets file exists + if not os.path.isfile(file_path): + print("File %s not found, creating it."%file_path) + log_in() + + # Find parameter in file + with open( file_path ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(file_path + " Missing parameter %s "%parameter) + sys.exit(0) + +# Returns the parameter from the specified file +def get_hostname( parameter, config_filepath ): + # Check if secrets file exists + if not os.path.isfile(config_filepath): + print("File %s not found, creating it."%config_filepath) + create_config() + + # Find parameter in file + with open( config_filepath ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(config_filepath + " Missing parameter %s "%parameter) + write_config() + read_config_line() + print("hostname setup done!") + sys.exit(0) + +# Load secrets from secrets file +secrets_filepath = "secrets/secrets.txt" +uc_client_id = get_parameter("uc_client_id", secrets_filepath) +uc_client_secret = get_parameter("uc_client_secret", secrets_filepath) +uc_access_token = get_parameter("uc_access_token", secrets_filepath) + +# Load configuration from config file +config_filepath = "config.txt" +mastodon_hostname = get_hostname("mastodon_hostname", config_filepath) # E.g., mastodon.social diff --git a/sol/solsurt.png b/sol/solsurt.png new file mode 100644 index 0000000..b63fe22 Binary files /dev/null and b/sol/solsurt.png differ diff --git a/temperatura/temperature.png b/temperatura/temperature.png new file mode 100644 index 0000000..30653fc Binary files /dev/null and b/temperatura/temperature.png differ diff --git a/temps.py b/temps.py new file mode 100644 index 0000000..04dbe66 --- /dev/null +++ b/temps.py @@ -0,0 +1,981 @@ +# https://medium.com/nexttech/how-to-use-the-openweathermap-api-with-python-c84cc7075cfc + +import urllib3 +import datetime +import time +from mastodon import Mastodon +import psycopg2 +import unidecode +import time +import re +import os +import json +import sys +import os.path # For checking whether secrets file exists +import requests # For doing the web stuff, dummy! +from datetime import timedelta, datetime +import urllib.request +import geopy +from geopy.geocoders import Nominatim +from PIL import Image, ImageFont, ImageDraw +import geotiler +import geocoder +import ssl +import pdb + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +def cleanhtml(raw_html): + + cleanr = re.compile('<.*?>') + cleantext = re.sub(cleanr, '', raw_html) + return cleantext + +def translate(i, detall, dir_vent): + + if detall == 'Parcialmente nuboso': + detall = 'Parcialment ennuvolat' + if detall == 'Parcialmente nuboso con niebla': + detall = 'Parcialment ennuvolat amb boira' + if detall == 'Parcialmente nuboso con lluvias': + detall = 'Parcialment ennuvolat amb aiguats' + if detall == 'Nubes dispersas': + detall = 'Núvols dispersos' + if detall == 'Muy nuboso con nevadas': + detall = 'Molt ennuvolat amb nevades' + if detall == 'Muy nuboso con lluvia': + detall = 'Molt ennuvolat amb pluja' + if detall == 'Muy nuboso con niebla': + detall = 'Molt ennuvolat amb boira' + if detall == 'Muy nuboso con lluvias': + detall = 'Molt ennuvolat amb pluges' + if detall == 'Cubierto': + detall = 'Tapat' + if detall == 'Cubierto con lluvia': + detall = 'Tapat amb pluja' + if detall == 'Cubierto con lluvias': + detall = 'Tapat amb pluges' + if detall == 'Cubierto con probabilidad de lluvia': + detall = 'Tapat amb possible pluja' + if detall == 'Cubierto con nevadas': + detall = 'Tapat amb nevades' + if detall == 'Muy nuboso': + detall = 'Molt ennuvolat' + if detall == 'Despejado': + detall = 'Clar' + + if dir_vent == 'Suroeste': + dir_vent = 'Sud-oest' + if dir_vent == 'Noroeste': + dir_vent = 'Nord-oest' + if dir_vent == 'Nordeste': + dir_vent = 'Nord-est' + if dir_vent == 'Sureste': + dir_vent = 'Sud-est' + if dir_vent == 'Norte': + dir_vent = 'Nord' + if dir_vent == 'Este': + dir_vent = 'Est' + if dir_vent == 'Sur': + dir_vent = 'Sud' + if dir_vent == 'Oeste': + dir_vent = 'Oest' + + return i, detall, dir_vent + +def createtilehours(data, pregunta): + + i = 1 + x = 10 + y = 10 + + while i < 13: + + hour_data = data['hour_hour']['hour'+str(i)]['hour_data'] + temperature = data['hour_hour']['hour'+str(i)]['temperature'] + detall = data['hour_hour']['hour'+str(i)]['text'] + icona = data['hour_hour']['hour'+str(i)]['icon'] + dir_vent = data['hour_hour']['hour'+str(i)]['wind_direction'] + + i, detall, dir_vent = translate(i, detall, dir_vent) + + if i == 1: + fons = Image.open('images/fonsorig.jpg') + else: + fons = Image.open('images/tempsperhores'+str(i-1)+'.png') + + if i == 1: + + logo_img = Image.open('images/logo.png') + fons.paste(logo_img, (470, 16), logo_img) + + # hi afegim l'icona del temps + icona_path = 'wi/'+icona+'.png' + temps_img = Image.open(icona_path) + + fons.paste(temps_img, (x,y+30), temps_img) + + fons.save('images/temphour'+str(i)+'.png',"PNG") + + base = Image.open('images/temphour'+str(i)+'.png').convert('RGBA') + txt = Image.new('RGBA', base.size, (255,255,255,0)) + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 25, layout_engine=ImageFont.LAYOUT_BASIC) + # get a drawing context + draw = ImageDraw.Draw(txt) + + if i == 1: + + draw.text((10,10), pregunta + ', 12 hores', font=fnt, fill=(255,255,255,255)) ## half opacity + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 18, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((x+50,y+45), str(temperature)+'\u00b0'+ ' ' +hour_data+' '+detall, font=fnt, fill=(255,255,255,200)) ## half opacity + + if i == 1: + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 12, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((380,490), 'temps@mastodont.cat', font=fnt, fill=(255,255,255,200)) #fill=(255,255,255,255)) ## full opacity + draw.text((400,510), 'API: tutiempo.net', font=fnt, fill=(155,0,200,200)) #fill=(255,255,255,255)) ## full opacity + + out = Image.alpha_composite(base, txt) + out.save('images/tempsperhores'+str(i)+'.png') + + y = y + 40 + + i += 1 + +def createtiletoday(people, long, lat, data, days): + + i = 1 + while i < days+1: + + temperatura_max = data['day'+str(i)]['temperature_max'] + temperatura_min = data['day'+str(i)]['temperature_min'] + icona = data['day'+str(i)]['icon'] + detall = data['day'+str(i)]['text'] + vent = data['day'+str(i)]['wind'] + icona_vent = data['day'+str(i)]['icon_wind'] + dir_vent = data['day'+str(i)]['wind_direction'] + humitat = data['day'+str(i)]['humidity'] + solsurt = data['day'+str(i)]['sunrise'] + solamaga = data['day'+str(i)]['sunset'] + llunasurt = data['day'+str(i)]['moonrise'] + llunaamaga = data['day'+str(i)]['moonset'] + icona_lluna = data['day'+str(i)]['moon_phases_icon'] + + i, detall, dir_vent = translate(i, detall, dir_vent) + + if people > 1000000: + zoom = 10 + elif people > 100000 and people < 999999: + zoom = 11 + elif people > 50000 and people < 99999: + zoom = 12 + elif people < 49999: + zoom = 15 + + map = geotiler.Map(center=(long, lat), zoom=zoom, size=(256, 256)) + image = geotiler.render_map(map) + image.save('images/map.png') + + fons = Image.open("images/fons.jpg") + + #hi afegim el mapa de la ciutat + map_img = Image.open('images/map.png') + fons.paste(map_img, (390,65), map_img) + + if i == 1: + + logo_img = Image.open('images/logo.png') + fons.paste(logo_img, (310, 320), logo_img) + + # hi afegim l'icona del temps + icona_path = 'wi/'+icona+'.png' + temps_img = Image.open(icona_path) + + fons.paste(temps_img, (10,55), temps_img) + + # hi afegim l'icona del termometre + icona_temperature_path = 'temperatura/temperature.png' + temperature_img = Image.open(icona_temperature_path) + + fons.paste(temperature_img, (10,115), temperature_img) + + # hi afegim l'icona del vent + icona_vent_path = 'wd/'+icona_vent+'.png' + vent_img = Image.open(icona_vent_path) + + fons.paste(vent_img, (22,180), vent_img) + + # hi afegim l'icona d'humitat + icona_humitat_path = 'humitat/humedad.png' + hum_img = Image.open(icona_humitat_path) + + fons.paste(hum_img, (8,210), hum_img) + + # hi afegim l'icona del sol sortint + icona_sun_path = 'sol/solsurt.png' + sun_img = Image.open(icona_sun_path) + + fons.paste(sun_img, (10,270), sun_img) + + # hi afegim l'icona de la lluna + icona_lluna_path = 'canviats/'+icona_lluna+'.png' + lluna_img = Image.open(icona_lluna_path) + + fons.paste(lluna_img, (12,315), lluna_img) + fons.save('images/temporal.png',"PNG") + + base = Image.open('images/temporal.png').convert('RGBA') + txt = Image.new('RGBA', base.size, (255,255,255,0)) + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 35, layout_engine=ImageFont.LAYOUT_BASIC) + # get a drawing context + draw = ImageDraw.Draw(txt) + + draw.text((10,10), pregunta, font=fnt, fill=(255,255,255,255)) + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 30, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((70,60), detall, font=fnt, fill=(255,255,255,200)) ## half opacity + draw.text((70,120), str(temperatura_max)+'\u00b0 / '+str(temperatura_min)+'\u00b0', font=fnt, fill=(255,255,255,200)) #fill=(255,255,255,255)) ## full opacity + draw.text((70,170), str(dir_vent)+', '+str(vent)+'km/h', font=fnt, fill=(255,255,255,200)) + draw.text((70,220), str(humitat)+'%', font=fnt, fill=(255,255,255,200)) + draw.text((70,270), str(solsurt)+', '+str(solamaga), font=fnt, fill=(255,255,255,200)) + draw.text((70,320), str(llunasurt)+', '+str(llunaamaga), font=fnt, fill=(255,255,255,200)) + + if i == 1: + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 12, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((380,350), 'temps@mastodont.cat', font=fnt, fill=(255,255,255,200)) #fill=(255,255,255,255)) ## full opacity + draw.text((540,350), 'API: tutiempo.net', font=fnt, fill=(155,0,200,200)) #fill=(255,255,255,255)) ## full opacity + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 10, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((395,300),"© Col·laboradors d'OpenStreetMap, CC-BY-SA", font=fnt, fill=(155,0,200,200)) #fill=(255,255,255,255)) ## full opacity + + out = Image.alpha_composite(base, txt) + out.save('images/temps.png') + + i += 1 + + return detall + +def createtileweek(data, days): + + week_days= ['Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte','Diumenge'] + + x = 10 + y = 10 + + i = 1 + while i < days+1: + + weather_date = data['day'+str(i)]['date'] + temperatura_max = data['day'+str(i)]['temperature_max'] + temperatura_min = data['day'+str(i)]['temperature_min'] + icona = data['day'+str(i)]['icon'] + detall = data['day'+str(i)]['text'] + vent = data['day'+str(i)]['wind'] + icona_vent = data['day'+str(i)]['icon_wind'] + dir_vent = data['day'+str(i)]['wind_direction'] + humitat = data['day'+str(i)]['humidity'] + solsurt = data['day'+str(i)]['sunrise'] + solamaga = data['day'+str(i)]['sunset'] + llunasurt = data['day'+str(i)]['moonrise'] + llunaamaga = data['day'+str(i)]['moonset'] + icona_lluna = data['day'+str(i)]['moon_phases_icon'] + + i, detall, dir_vent = translate(i, detall, dir_vent) + + date_list = list(map(str, weather_date. split('-'))) + day=datetime(int(date_list[0]),int(date_list[1]),int(date_list[2])).weekday() + + if i == 1: + fons = Image.open('images/fons.jpg') + else: + fons = Image.open('images/tempsweek'+str(i-1)+'.png') + + # hi afegim l'icona del temps + icona_path = 'wi/'+icona+'.png' + temps_img = Image.open(icona_path) + + fons.paste(temps_img, (y+10, x+80), temps_img) + + if i == 1: + logo_img = Image.open('images/logo.png') + fons.paste(logo_img, (15, 320), logo_img) + + fons.save('images/tempweek'+str(i)+'.png',"PNG") + + base = Image.open('images/tempweek'+str(i)+'.png').convert('RGBA') + txt = Image.new('RGBA', base.size, (255,255,255,0)) + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 35, layout_engine=ImageFont.LAYOUT_BASIC) + # get a drawing context + draw = ImageDraw.Draw(txt) + + if i == 1: + + draw.text((15,10), pregunta+', 7 dies', font=fnt, fill=(255,255,255,255)) + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 16, layout_engine=ImageFont.LAYOUT_BASIC) + + if i == 1: + draw.text((y+10,x+50), "Avui", font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + elif week_days[day] == 'Dimarts': + draw.text((y+4,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + elif week_days[day] == 'Dimecres': + draw.text((y-5,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + elif week_days[day] == 'Dijous': + draw.text((y+6,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + elif week_days[day] == 'Divendres': + draw.text((y-6,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + elif week_days[day] == 'Dissabte': + draw.text((y,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + else: + draw.text((y+4,x+50), str(week_days[day]), font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 24, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((y+18,x+140), str(temperatura_max)+'\u00b0', font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + draw.text((y+20,x+190), str(temperatura_min)+'\u00b0', font=fnt, fill=(255,255,255,220)) #fill=(255,255,255,255)) ## full opacity + + if i == 1: + + fnt = ImageFont.truetype('fonts/DejaVuSerif.ttf', 15, layout_engine=ImageFont.LAYOUT_BASIC) + draw.text((60,330), 'temps@mastodont.cat - 2020', font=fnt, fill=(255,255,255,200)) #fill=(255,255,255,255)) ## full opacity + draw.text((520,330), 'API: tutiempo.net', font=fnt, fill=(155,0,200,200)) #fill=(255,255,255,255)) ## full opacity + + out = Image.alpha_composite(base, txt) + out.save('images/tempsweek'+str(i)+'.png') + + y += 95 + i += 1 + + return detall + +def format_query(pregunta): + + if pregunta.find("'") != -1: + + pregunta = pregunta.replace(''', "'") + + elif pregunta.find("’") != -1: + + pregunta = pregunta.replace("’", "'") + + elif pregunta.find("'") != -1: + + pregunta = pregunta.replace(''', "'") + + return pregunta + +def get_weather_data(pregunta): + + nom = Nominatim(user_agent="temps@mastodont.cat") + coords = nom.geocode(pregunta) + + if coords != None: + + long = coords.longitude + lat = coords.latitude + + headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} + + g = geocoder.geonames(pregunta, key='spla') + + people = g.population if g.ok else 0 + + url = 'https://api.tutiempo.net/json/?lan=es&apid={}&ll={},{}'.format(api_key, lat, long) + res = requests.get(url, headers=headers, verify=False) + + data = res.json() + + else: + + data = None + people = None + long = None + lat = None + + return (data, people, long, lat) + +def get_query(contingut): + + inici = contingut.index("@") + + final = contingut.index(" ") + + if len(contingut) > final: + + contingut = contingut[0: inici:] + contingut[final +1::] + + neteja = contingut.count('@') + + i = 0 + while i < neteja : + + inici = contingut.rfind("@") + final = len(contingut) + contingut = contingut[0: inici:] + contingut[final +1::] + i += 1 + + pregunta = contingut.lstrip(" ") + + return pregunta + +def get_country(pregunta): + + if pregunta.find(',') != -1: + + pais = pregunta.rsplit(',', 1)[1] + pais = pais.replace(' ', '') + pais = pais.upper() + + else: + + pais = 'ES' + + return pais + +def set_alarm(username, visibility, alarm_time, alarm_city): + + insert_sql = "INSERT INTO alarms(username, visibility, time, city) VALUES(%s,%s,%s,%s) ON CONFLICT DO NOTHING" + + conn = None + + try: + + conn = psycopg2.connect(database = alarm_db, user = alarm_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute(insert_sql, (username, visibility, alarm_time, alarm_city)) + + conn.commit() + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + +def get_alarms(): + + alarm_user = [] + + alarm_visibility = [] + + alarm_time = [] + + alarm_city = [] + + conn = None + + try: + + conn = psycopg2.connect(database = alarm_db, user = alarm_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute('select username, visibility, time, city from alarms') + + rows = cur.fetchall() + + for row in rows: + + alarm_user.append(row[0]) + + alarm_visibility.append(row[1]) + + alarm_time.append(row[2]) + + alarm_city.append(row[3]) + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + return (alarm_user, alarm_visibility, alarm_time, alarm_city) + +def check_alarm(username, city): + + sql_query = 'select username, city from alarms where username=(%s) and city=(%s)' + + alarm_exists = False + + conn = None + + try: + + conn = psycopg2.connect(database = alarm_db, user = alarm_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute(sql_query, (username, city)) + + row = cur.fetchone() + + if row != None: + + alarm_exists = True + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + return alarm_exists + +def delete_alarm(username, city): + + delete_sql = 'delete from alarms where username=(%s) and city=(%s)' + + conn = None + + try: + + conn = psycopg2.connect(database = alarm_db, user = alarm_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute(delete_sql, (username, city)) + + conn.commit() + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + +def alarm_post(username, visibility, city): + + data, people, long, lat = get_weather_data(city) + + if data != None and 'error' not in data: + + days = 1 + + detall = createtilehours(data, city) + + toot_text = f"@{username} \n" + + toot_text += f"El temps per hores a {city} \n" + + image_id = mastodon.media_post('images/tempsperhores12.png', "image/png", description='temps').id + + mastodon.status_post(toot_text, in_reply_to_id=None, visibility=visibility, media_ids={image_id}) + + else: + + toot_text = f"@{username} \n" + + toot_text += f"No trobo {city} :-(\n" + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + +def get_parameter( parameter, file_path ): + # Check if secrets file exists + if not os.path.isfile(file_path): + print("File %s not found, exiting."%file_path) + sys.exit(0) + + # Find parameter in file + with open( file_path ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(file_path + " Missing parameter %s "%parameter) + sys.exit(0) + +def create_dir(): + + if not os.path.exists('images'): + + os.makedirs('images') + +def mastodon(): + + # Load secrets from secrets file + secrets_filepath = "secrets/secrets.txt" + uc_client_id = get_parameter("uc_client_id", secrets_filepath) + uc_client_secret = get_parameter("uc_client_secret", secrets_filepath) + uc_access_token = get_parameter("uc_access_token", secrets_filepath) + + # Load configuration from config file + config_filepath = "config/config.txt" + mastodon_hostname = get_parameter("mastodon_hostname", config_filepath) + + # Initialise Mastodon API + mastodon = Mastodon( + client_id=uc_client_id, + client_secret=uc_client_secret, + access_token=uc_access_token, + api_base_url='https://' + mastodon_hostname, + ) + + # Initialise access headers + headers = {'Authorization': 'Bearer %s'%uc_access_token} + + return (mastodon, mastodon_hostname) + +def dbconfig(): + + # Load configuration from config file + config_filepath = "config/db_config.txt" + alarm_db = get_parameter("alarm_db", config_filepath) + alarm_db_user = get_parameter("alarm_db_user", config_filepath) + + return (alarm_db, alarm_db_user) + +def read_apikey(): + + # Load secrets from secrets file + secrets_filepath = "secrets/apikey.txt" + api_key = get_parameter("api_key", secrets_filepath) + + return api_key + +# main + +if __name__ == '__main__': + + t = time.localtime() + + current_time = time.strftime("%H:%M", t) + + create_dir() + + mastodon, mastodon_hostname = mastodon() + + api_key = read_apikey() + + alarm_db, alarm_db_user = dbconfig() + + peticions_restants = mastodon.ratelimit_remaining + print("Peticions al API restants: " + str(peticions_restants)) + + proxim_reset = mastodon.ratelimit_reset + proxim_reset = datetime.fromtimestamp(proxim_reset) + proxim_reset = proxim_reset.strftime("%d/%m/%Y, %H:%M:%S") + print(f"Pròxim reset: {str(proxim_reset)}") + + alarm_user, alarm_visibility, alarm_time, alarm_city = get_alarms() + + i = 0 + + while i < len (alarm_user): + + print(current_time) + + alarmtime = alarm_time[i].strftime("%H:%M") + + print(alarmtime) + + if alarmtime == current_time: + + username = alarm_user[i] + + visibility = alarm_visibility[i] + + city = alarm_city[i] + + alarm_post(username, visibility, city) + + i += 1 + + #################################################################### + # get notifications + + notifications = mastodon.notifications() + + if len(notifications) == 0: + + print('No mentions') + + sys.exit(0) + + i = 0 + + while i < len(notifications): + + notification_id = notifications[i].id + + if notifications[i].type != 'mention': + + i += 1 + + print(f'dismissing notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + continue + + account_id = notifications[i] + + username = notifications[i].account.acct + + status_id = notifications[i].status.id + + text = notifications[i].status.content + + visibility = notifications[i].status.visibility + + contingut = cleanhtml(text) + + pregunta = get_query(contingut) + + if unidecode.unidecode(pregunta)[0:9] == "consulta:": + + pais = get_country(pregunta) + + inici = 0 + + final = unidecode.unidecode(pregunta).index('consulta:',0) + + pregunta = pregunta.split(',')[0] + + if len(pregunta) > final : + + pregunta = pregunta[0: inici:] + pregunta[final +10::] + + try: + + pregunta = format_query(pregunta) + + data, people, long, lat = get_weather_data(pregunta) + + if data != None and 'error' not in data: + + days = 1 + + detall = createtiletoday(people, long, lat, data, days) + + toot_text = f'@{username} \n' + + toot_text += f'El temps a {pregunta} és:\n' + + toot_text += f'{detall}\n\n' + + image_id = mastodon.media_post('images/temps.png', "image/png", description='temps').id + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility, media_ids={image_id}) + + else: + + toot_text = f'@{username} \n' + + toot_text += f'No trobo {pregunta} :-(\n' + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + except ValueError as verror: + + country_error() + + elif unidecode.unidecode(pregunta)[0:5] == "avui:": + + pais = get_country(pregunta) + + inici = 0 + + final = unidecode.unidecode(pregunta).index('avui:',0) + + pregunta = pregunta.split(',')[0] + + if len(pregunta) > final : + + pregunta = pregunta[0: inici:] + pregunta[final +6::] + + try: + + pregunta = format_query(pregunta) + + data, people, long, lat = get_weather_data(pregunta) + + if data != None and 'error' not in data: + + days = 1 + + detall = createtilehours(data, pregunta) + + toot_text = f"@{username} \n" + + toot_text += f"El temps per hores a {pregunta} \n" + + image_id = mastodon.media_post('images/tempsperhores12.png', "image/png", description='temps').id + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility, media_ids={image_id}) + + else: + + toot_text = f"@{username} \n" + + toot_text += f"No trobo {pregunta} :-(\n" + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + except ValueError as verror: + + country_error() + + elif unidecode.unidecode(pregunta)[0:8] == "setmana:": + + pais = get_country(pregunta) + + inici = 0 + + final = unidecode.unidecode(pregunta).index('setmana:',0) + + pregunta = pregunta.split(',')[0] + + if len(pregunta) > final : + + pregunta = pregunta[0: inici:] + pregunta[final +9::] + + try: + + pregunta = format_query(pregunta) + + data, people, long, lat = get_weather_data(pregunta) + + if data != None: + + days = 7 + + detall = createtileweek(data, days) + + toot_text = f"@{username} \n" + + toot_text += f"Previsió del temps de 7 dies a {pregunta} \n" + + image_id = mastodon.media_post('images/tempsweek7.png', "image/png", description='temps').id + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility, media_ids={image_id}) + + else: + + toot_text = f"@{username} \n" + + toot_text += f"No trobo {pregunta} :-(\n" + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + except ValueError as verror: + + country_error() + + elif unidecode.unidecode(pregunta)[0:7] == "alarma:": + + inici = 0 + + final = unidecode.unidecode(pregunta).index('alarma:',0) + + pregunta = pregunta.split(',')[0] + + if len(pregunta) > final : + + pregunta = pregunta[0: inici:] + pregunta[final +8::] + + alarm_time = pregunta.split(' ')[0] + + alarm_city = pregunta[6:] + + if alarm_city != '': + + set_alarm(username, visibility, alarm_time, alarm_city) + + toot_text = f"@{username} \n" + + toot_text += f"Alarma per a {alarm_city} fixada a les {alarm_time}\n" + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + else: + + toot_text = f"@{username} \n" + + toot_text += f"Per a configurar una alarma cal fer-ho així:\n@temps alarma: 08:00 Ciutat" + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + elif unidecode.unidecode(pregunta)[0:8] == "esborra:": + + inici = 0 + + final = unidecode.unidecode(pregunta).index('esborra:',0) + + pregunta = pregunta.split(',')[0] + + if len(pregunta) > final : + + pregunta = pregunta[0: inici:] + pregunta[final +9::] + + alarm_exists = check_alarm(username, pregunta) + + if alarm_exists: + + delete_alarm(username, pregunta) + + toot_text = f"@{username} \n" + + toot_text += f"{pregunta}, alarma esborrada." + + mastodon.status_post(toot_text, in_reply_to_id=status_id, visibility=visibility) + + print(f'Replied notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + else: + + print(f'Dismissing notification {notification_id}') + + mastodon.notifications_dismiss(notification_id) + + i += 1 + diff --git a/wd/C.png b/wd/C.png new file mode 100644 index 0000000..95492f7 Binary files /dev/null and b/wd/C.png differ diff --git a/wd/E.png b/wd/E.png new file mode 100644 index 0000000..94147c2 Binary files /dev/null and b/wd/E.png differ diff --git a/wd/ENE.png b/wd/ENE.png new file mode 100644 index 0000000..292cc29 Binary files /dev/null and b/wd/ENE.png differ diff --git a/wd/ESE.png b/wd/ESE.png new file mode 100644 index 0000000..1520adf Binary files /dev/null and b/wd/ESE.png differ diff --git a/wd/N.png b/wd/N.png new file mode 100644 index 0000000..929dbe8 Binary files /dev/null and b/wd/N.png differ diff --git a/wd/NE.png b/wd/NE.png new file mode 100644 index 0000000..292cc29 Binary files /dev/null and b/wd/NE.png differ diff --git a/wd/NNE.png b/wd/NNE.png new file mode 100644 index 0000000..292cc29 Binary files /dev/null and b/wd/NNE.png differ diff --git a/wd/NNW.png b/wd/NNW.png new file mode 100644 index 0000000..809c409 Binary files /dev/null and b/wd/NNW.png differ diff --git a/wd/NO.png b/wd/NO.png new file mode 100644 index 0000000..809c409 Binary files /dev/null and b/wd/NO.png differ diff --git a/wd/NW.png b/wd/NW.png new file mode 100644 index 0000000..809c409 Binary files /dev/null and b/wd/NW.png differ diff --git a/wd/O.png b/wd/O.png new file mode 100644 index 0000000..31399c8 Binary files /dev/null and b/wd/O.png differ diff --git a/wd/S.png b/wd/S.png new file mode 100644 index 0000000..c0e45c5 Binary files /dev/null and b/wd/S.png differ diff --git a/wd/SE.png b/wd/SE.png new file mode 100644 index 0000000..1520adf Binary files /dev/null and b/wd/SE.png differ diff --git a/wd/SO.png b/wd/SO.png new file mode 100644 index 0000000..7fef307 Binary files /dev/null and b/wd/SO.png differ diff --git a/wd/SSE.png b/wd/SSE.png new file mode 100644 index 0000000..1520adf Binary files /dev/null and b/wd/SSE.png differ diff --git a/wd/SSW.png b/wd/SSW.png new file mode 100644 index 0000000..7fef307 Binary files /dev/null and b/wd/SSW.png differ diff --git a/wd/SW.png b/wd/SW.png new file mode 100644 index 0000000..7fef307 Binary files /dev/null and b/wd/SW.png differ diff --git a/wd/VRB.png b/wd/VRB.png new file mode 100644 index 0000000..b9b092b Binary files /dev/null and b/wd/VRB.png differ diff --git a/wd/W.png b/wd/W.png new file mode 100644 index 0000000..31399c8 Binary files /dev/null and b/wd/W.png differ diff --git a/wd/WNW.png b/wd/WNW.png new file mode 100644 index 0000000..809c409 Binary files /dev/null and b/wd/WNW.png differ diff --git a/wd/WSW.png b/wd/WSW.png new file mode 100644 index 0000000..7fef307 Binary files /dev/null and b/wd/WSW.png differ diff --git a/wi/1.png b/wi/1.png new file mode 100644 index 0000000..ea802a9 Binary files /dev/null and b/wi/1.png differ diff --git a/wi/11.png b/wi/11.png new file mode 100644 index 0000000..479369f Binary files /dev/null and b/wi/11.png differ diff --git a/wi/18.png b/wi/18.png new file mode 100644 index 0000000..f03f41e Binary files /dev/null and b/wi/18.png differ diff --git a/wi/19.png b/wi/19.png new file mode 100644 index 0000000..f03f41e Binary files /dev/null and b/wi/19.png differ diff --git a/wi/1n.png b/wi/1n.png new file mode 100644 index 0000000..08bd141 Binary files /dev/null and b/wi/1n.png differ diff --git a/wi/2.png b/wi/2.png new file mode 100644 index 0000000..8d857d6 Binary files /dev/null and b/wi/2.png differ diff --git a/wi/21.png b/wi/21.png new file mode 100644 index 0000000..6194369 Binary files /dev/null and b/wi/21.png differ diff --git a/wi/21n.png b/wi/21n.png new file mode 100644 index 0000000..6194369 Binary files /dev/null and b/wi/21n.png differ diff --git a/wi/22.png b/wi/22.png new file mode 100644 index 0000000..6194369 Binary files /dev/null and b/wi/22.png differ diff --git a/wi/24.png b/wi/24.png new file mode 100644 index 0000000..91e9437 Binary files /dev/null and b/wi/24.png differ diff --git a/wi/25.png b/wi/25.png new file mode 100644 index 0000000..f5d9d35 Binary files /dev/null and b/wi/25.png differ diff --git a/wi/28.png b/wi/28.png new file mode 100644 index 0000000..f923b18 Binary files /dev/null and b/wi/28.png differ diff --git a/wi/29.png b/wi/29.png new file mode 100644 index 0000000..2d97de6 Binary files /dev/null and b/wi/29.png differ diff --git a/wi/2n.png b/wi/2n.png new file mode 100644 index 0000000..a2140fc Binary files /dev/null and b/wi/2n.png differ diff --git a/wi/30.png b/wi/30.png new file mode 100644 index 0000000..048cb15 Binary files /dev/null and b/wi/30.png differ diff --git a/wi/33.png b/wi/33.png new file mode 100644 index 0000000..25ac4d2 Binary files /dev/null and b/wi/33.png differ diff --git a/wi/33n.png b/wi/33n.png new file mode 100644 index 0000000..542ce88 Binary files /dev/null and b/wi/33n.png differ diff --git a/wi/4.png b/wi/4.png new file mode 100644 index 0000000..2da6977 Binary files /dev/null and b/wi/4.png differ diff --git a/wi/4n.png b/wi/4n.png new file mode 100644 index 0000000..334baf9 Binary files /dev/null and b/wi/4n.png differ diff --git a/wi/51.png b/wi/51.png new file mode 100644 index 0000000..2afa97f Binary files /dev/null and b/wi/51.png differ diff --git a/wi/51n.png b/wi/51n.png new file mode 100644 index 0000000..732e9b0 Binary files /dev/null and b/wi/51n.png differ diff --git a/wi/54.png b/wi/54.png new file mode 100644 index 0000000..bce0d9b Binary files /dev/null and b/wi/54.png differ diff --git a/wi/6.png b/wi/6.png new file mode 100644 index 0000000..039052f Binary files /dev/null and b/wi/6.png differ diff --git a/wi/6n.png b/wi/6n.png new file mode 100644 index 0000000..98a75e9 Binary files /dev/null and b/wi/6n.png differ diff --git a/wi/7.png b/wi/7.png new file mode 100644 index 0000000..805b5c9 Binary files /dev/null and b/wi/7.png differ diff --git a/wi/9.png b/wi/9.png new file mode 100644 index 0000000..21fb579 Binary files /dev/null and b/wi/9.png differ diff --git a/wi/9n.png b/wi/9n.png new file mode 100644 index 0000000..21fb579 Binary files /dev/null and b/wi/9n.png differ diff --git a/wi/nd.png b/wi/nd.png new file mode 100644 index 0000000..f059c5a Binary files /dev/null and b/wi/nd.png differ