Codi del bot @temps@mastodont.cat alliberat

This commit is contained in:
spla 2022-07-11 22:37:14 +02:00
commit 9669469b54
S'han modificat 90 arxius amb 1426 adicions i 0 eliminacions

61
apikey-setup.py Normal file
Veure arxiu

@ -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)

BIN
canviats/1.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 848 B

BIN
canviats/10.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,6 KiB

BIN
canviats/11.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,8 KiB

BIN
canviats/12.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3 KiB

BIN
canviats/13.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3 KiB

BIN
canviats/14.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3 KiB

BIN
canviats/15.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,9 KiB

BIN
canviats/16.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,2 KiB

BIN
canviats/17.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,1 KiB

BIN
canviats/18.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,8 KiB

BIN
canviats/19.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,7 KiB

BIN
canviats/2.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1.004 B

BIN
canviats/20.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,5 KiB

BIN
canviats/21.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,3 KiB

BIN
canviats/22.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,1 KiB

BIN
canviats/23.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,9 KiB

BIN
canviats/24.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,7 KiB

BIN
canviats/25.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,5 KiB

BIN
canviats/26.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,4 KiB

BIN
canviats/27.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,1 KiB

BIN
canviats/28.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 811 B

BIN
canviats/29.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 811 B

BIN
canviats/3.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,3 KiB

BIN
canviats/4.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,4 KiB

BIN
canviats/5.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,6 KiB

BIN
canviats/6.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 1,9 KiB

BIN
canviats/7.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2 KiB

BIN
canviats/8.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,2 KiB

BIN
canviats/9.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,4 KiB

185
db-setup.py Normal file
Veure arxiu

@ -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")

BIN
fonts/DejaVuSerif.ttf Normal file

Archivo binario no mostrado.

BIN
humitat/humedad.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,7 KiB

BIN
images/fons.jpg Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 12 KiB

BIN
images/fonsorig.jpg Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 18 KiB

BIN
images/logo.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,6 KiB

8
requeriments.txt Normal file
Veure arxiu

@ -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

191
setup.py Normal file
Veure arxiu

@ -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

BIN
sol/solsurt.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 2,9 KiB

BIN
temperatura/temperature.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 6,1 KiB

981
temps.py Normal file
Veure arxiu

@ -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('&apos;', "'")
elif pregunta.find("") != -1:
pregunta = pregunta.replace("", "'")
elif pregunta.find("&apos;") != -1:
pregunta = pregunta.replace('&apos;', "'")
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

BIN
wd/C.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 245 B

BIN
wd/E.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 399 B

BIN
wd/ENE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 390 B

BIN
wd/ESE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 386 B

BIN
wd/N.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 421 B

BIN
wd/NE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 390 B

BIN
wd/NNE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 390 B

BIN
wd/NNW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/NO.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/NW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/O.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 399 B

BIN
wd/S.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 419 B

BIN
wd/SE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 386 B

BIN
wd/SO.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/SSE.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 386 B

BIN
wd/SSW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/SW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/VRB.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 486 B

BIN
wd/W.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 399 B

BIN
wd/WNW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wd/WSW.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 384 B

BIN
wi/1.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4 KiB

BIN
wi/11.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,5 KiB

BIN
wi/18.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,9 KiB

BIN
wi/19.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,9 KiB

BIN
wi/1n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,4 KiB

BIN
wi/2.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,4 KiB

BIN
wi/21.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,5 KiB

BIN
wi/21n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,5 KiB

BIN
wi/22.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,5 KiB

BIN
wi/24.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,4 KiB

BIN
wi/25.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,4 KiB

BIN
wi/28.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,7 KiB

BIN
wi/29.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,4 KiB

BIN
wi/2n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,5 KiB

BIN
wi/30.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4 KiB

BIN
wi/33.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 5,2 KiB

BIN
wi/33n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,8 KiB

BIN
wi/4.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,2 KiB

BIN
wi/4n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,6 KiB

BIN
wi/51.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,8 KiB

BIN
wi/51n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,6 KiB

BIN
wi/54.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,9 KiB

BIN
wi/6.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 4,5 KiB

BIN
wi/6n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,8 KiB

BIN
wi/7.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,5 KiB

BIN
wi/9.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,4 KiB

BIN
wi/9n.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,4 KiB

BIN
wi/nd.png Normal file

Archivo binario no mostrado.

Desprès

Amplada:  |  Alçada:  |  Mida: 3,8 KiB