125 lines
5.7 KiB
Python
125 lines
5.7 KiB
Python
import threading
|
|
|
|
import requests
|
|
from bs4 import BeautifulSoup
|
|
from tkinter import Frame, Label, PhotoImage, Tk
|
|
from app.widgets.abc import ThreadedTab
|
|
import cairosvg
|
|
from io import BytesIO
|
|
|
|
class WeatherTab(ThreadedTab):
|
|
|
|
def __init__(self, root: Frame | Tk, city: str, **kwargs):
|
|
self.city = city
|
|
self.weather_info = {}
|
|
self.weather_image = None
|
|
self.weather_frame = None
|
|
self.weather_label = None
|
|
self.weather_image_label = None
|
|
self.city_label = None
|
|
self.real_feel_label = None
|
|
self.wind_label = None
|
|
self.wind_gusts_label = None
|
|
self.air_quality_label = None
|
|
super().__init__(root, **kwargs)
|
|
|
|
def build(self):
|
|
self.weather_frame = Frame(self)
|
|
self.weather_frame.pack(fill="both", expand=True)
|
|
|
|
self.city_label = Label(self.weather_frame, text=f"Weather in {self.city}", font=("Helvetica", 16))
|
|
self.city_label.grid(row=0, column=0, columnspan=2, pady=10, sticky="ew")
|
|
|
|
self.weather_image_label = Label(self.weather_frame)
|
|
self.weather_image_label.grid(row=1, column=0, padx=10, sticky="ew")
|
|
|
|
self.weather_label = Label(self.weather_frame, text="", font=("Helvetica", 14))
|
|
self.weather_label.grid(row=1, column=1, padx=10, sticky="ew")
|
|
|
|
self.real_feel_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
|
self.real_feel_label.grid(row=2, column=1, padx=10, sticky="ew")
|
|
|
|
self.wind_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
|
self.wind_label.grid(row=3, column=1, padx=10, sticky="ew")
|
|
|
|
self.wind_gusts_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
|
self.wind_gusts_label.grid(row=4, column=1, padx=10, sticky="ew")
|
|
|
|
self.air_quality_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
|
self.air_quality_label.grid(row=5, column=1, padx=10, sticky="ew")
|
|
|
|
self.weather_frame.columnconfigure(0, weight=1)
|
|
self.weather_frame.columnconfigure(1, weight=1)
|
|
|
|
# Ensure the frame fills the entire parent space
|
|
self.grid(row=0, column=0, sticky="nsew")
|
|
self.master.rowconfigure(0, weight=1)
|
|
self.master.columnconfigure(0, weight=1)
|
|
|
|
def task(self, *args):
|
|
self.fetch_weather_data()
|
|
self.update_ui()
|
|
|
|
def fetch_weather_data(self):
|
|
try:
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
|
}
|
|
search_url = f"https://www.accuweather.com/en/search-locations?query={self.city}"
|
|
search_response = requests.get(search_url, headers=headers)
|
|
search_soup = BeautifulSoup(search_response.text, 'html.parser')
|
|
location_list = search_soup.find('div', class_='locations-list content-module')
|
|
if not location_list:
|
|
print("Location list not found")
|
|
return
|
|
|
|
location_link = location_list.find('a')['href']
|
|
weather_url = f"https://www.accuweather.com{location_link}"
|
|
weather_response = requests.get(weather_url, headers=headers)
|
|
weather_soup = BeautifulSoup(weather_response.text, 'html.parser')
|
|
|
|
weather_icon_path = weather_soup.find('svg', class_='weather-icon')['data-src']
|
|
weather_icon_url = f"https://www.accuweather.com{weather_icon_path}"
|
|
weather_icon_response = requests.get(weather_icon_url, headers=headers)
|
|
weather_icon_svg = weather_icon_response.content
|
|
|
|
# Convert SVG to PNG and resize to 50x50
|
|
weather_icon_png = cairosvg.svg2png(bytestring=weather_icon_svg, output_width=50, output_height=50)
|
|
weather_icon_image = PhotoImage(data=BytesIO(weather_icon_png).getvalue())
|
|
|
|
temperature = weather_soup.find('div', class_='temp').text
|
|
real_feel = weather_soup.find('div', class_='real-feel').text.strip().replace('RealFeel®', '').strip()
|
|
|
|
details_container = weather_soup.find('div', class_='details-container')
|
|
wind = details_container.find('span', text='Wind').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
|
wind_gusts = details_container.find('span', text='Wind Gusts').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
|
air_quality = details_container.find('span', text='Air Quality').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
|
|
|
self.weather_info = {
|
|
'icon': weather_icon_image,
|
|
'temperature': temperature,
|
|
'real_feel': real_feel,
|
|
'wind': wind,
|
|
'wind_gusts': wind_gusts,
|
|
'air_quality': air_quality
|
|
}
|
|
except Exception as e:
|
|
print(f"Error fetching weather data: {e}")
|
|
|
|
def update_ui(self):
|
|
if self.weather_info:
|
|
weather_text = f"{self.weather_info['temperature']}"
|
|
self.weather_label.config(text=weather_text)
|
|
|
|
self.weather_image_label.config(image=self.weather_info['icon'])
|
|
self.weather_image_label.image = self.weather_info['icon']
|
|
|
|
self.real_feel_label.config(text=f"RealFeel: {self.weather_info['real_feel']}")
|
|
self.wind_label.config(text=f"Wind: {self.weather_info['wind']}")
|
|
self.wind_gusts_label.config(text=f"Wind Gusts: {self.weather_info['wind_gusts']}")
|
|
self.air_quality_label.config(text=f"Air Quality: {self.weather_info['air_quality']}")
|
|
|
|
def changeCity(self, city):
|
|
self.city = city
|
|
self.city_label.config(text=f"Weather in {self.city}")
|
|
threading.Thread(target=self.task).start() |