Skip to content

Instantly share code, notes, and snippets.

@Surendrajat
Last active April 8, 2025 03:23
Show Gist options
  • Save Surendrajat/ff3876fd2166dd86fb71180f4e9342d7 to your computer and use it in GitHub Desktop.
Save Surendrajat/ff3876fd2166dd86fb71180f4e9342d7 to your computer and use it in GitHub Desktop.
A Clean & Customizable Weather module for Waybar
"custom/weather": {
"exec": "python ~/.config/waybar/scripts/weather.py",
"restart-interval": 300,
"return-type": "json",
"on-click": "xdg-open https://weather.com/en-IN/weather/today/l/$(location_id)"
// "format-alt": "{alt}",
},
#custom-weather.severe {
color: #eb937d;
}
#custom-weather.sunnyDay {
color: #c2ca76;
}
#custom-weather.clearNight {
color: #2b2b2a;
}
#custom-weather.cloudyFoggyDay, #custom-weather.cloudyFoggyNight {
color: #c2ddda;
}
#custom-weather.rainyDay, #custom-weather.rainyNight {
color: #5aaca5;
}
#custom-weather.snowyIcyDay, #custom-weather.snowyIcyNight {
color: #d6e7e5;
}
#custom-weather.default {
color: #dbd9d8;
}
#!/usr/bin/env python3
from pyquery import PyQuery # install using `pip install pyquery`
import json
################################### CONFIGURATION ###################################
# set your location_id
# to get your location_id, go to https://weather.com & search for your location.
# once you choose your location, you can see the location_id in the URL(64 chars long hex string)
# like this: https://weather.com/en-IN/weather/today/l/c3e96d6cc4965fc54f88296b54449571c4107c73b9638c16aafc83575b4ddf2e
# once you get the location_id, you can replace the below location_id with your own location_id
location_id = "c3e96d6cc4965fc54f88296b54449571c4107c73b9638c16aafc83575b4ddf2e" # TODO
# celcius or fahrenheit
unit = "metric" # metric or imperial
# forcase type
forecast_type = "Daily" # Hourly or Daily
########################################## MAIN ##################################
# weather icons
weather_icons = {
"sunnyDay": "滛",
"clearNight": "望",
"cloudyFoggyDay": "",
"cloudyFoggyNight": "",
"rainyDay": "",
"rainyNight": "",
"snowyIcyDay": "",
"snowyIcyNight": "",
"severe": "",
"default": "",
}
# get html page
_l = "en-IN" if unit == "metric" else "en-US"
url = f"https://weather.com/{_l}/weather/today/l/{location_id}"
# get html data
html_data = PyQuery(url=url)
# current temperature
temp = html_data("span[data-testid='TemperatureValue']").eq(0).text()
# current status phrase
status = html_data("div[data-testid='wxPhrase']").text()
status = f"{status[:16]}.." if len(status) > 17 else status
# status code
status_code_class = html_data("#regionHeader").attr("class")
status_code = str(status_code_class).split(" ")[2].split("-")[2]
# status icon
icon = (
weather_icons[status_code]
if status_code in weather_icons
else weather_icons["default"]
)
# temperature feels like
temp_feel = html_data(
"div[data-testid='FeelsLikeSection'] > span > span[data-testid='TemperatureValue']"
).text()
temp_feel_text = f"Feels like {temp_feel}{'c' if unit == 'metric' else 'f'}"
# min-max temperature
temp_min = (
html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
.eq(1)
.text()
)
temp_max = (
html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
.eq(0)
.text()
)
temp_min_max = f" {temp_min}\t\t{temp_max}"
# wind speed
wind_speed = str(html_data("span[data-testid='Wind']").text())
wind_text = f"煮 {wind_speed}"
# humidity
humidity = html_data("span[data-testid='PercentageValue']").text()
humidity_text = f" {humidity}"
# visibility
visbility = html_data("span[data-testid='VisibilityValue']").text()
visbility_text = f" {visbility}"
# air quality index
air_quality_index = html_data("text[data-testid='DonutChartValue']").text()
# rain prediction
r_prediction_text = html_data(f"section[aria-label='{forecast_type} Forecast']")(
"div[data-testid='SegmentPrecipPercentage'] > span"
).text()
r_prediction = str(r_prediction_text).replace("Chance of Rain", "")
r_prediction = f"  ({forecast_type}) {r_prediction}" if len(r_prediction) > 0 else r_prediction
# temperature prediction
t_prediction_text = html_data(f"section[aria-label='{forecast_type} Forecast']")(
"div[data-testid='SegmentHighTemp'] > span"
).text()
t_prediction = str(t_prediction_text).replace(" /", "/")
t_prediction = f" 滛 ({forecast_type}) {t_prediction}" if len(t_prediction) > 0 else t_prediction
#pretty print all data
# print(f"temp: {temp}\nstatus: {status}\nstatus_code: {status_code}\nicon: {icon}\
# \ntemp_feel_text: {temp_feel_text}\ntemp_min_max: {temp_min_max}\nwind_text: {wind_text}\
# \nhumidity_text: {humidity_text}\nvisbility_text: {visbility_text}\nair_quality_index: {air_quality_index}\
# \nprediction: \n{r_prediction}\n{t_prediction}")
# tooltip text
tooltip_text = str.format(
"\t\t{}\t\t\n{}\n{}\n{}\n\n{}\n{}\n{}\n\n{}\n{}",
f'<span size="xx-large">{temp}</span>',
f"<big>{icon}</big>",
f"<big>{status}</big>",
f"<small>{temp_feel_text}</small>",
f"<big>{temp_min_max}</big>",
f"{wind_text}\t{humidity_text}",
f"{visbility_text}\tAQI {air_quality_index}",
f"<i>{r_prediction}</i>",
f"<i>{t_prediction}</i>"
)
# print waybar module data
out_data = {
"text": f"{icon} {temp}",
"alt": status,
"tooltip": tooltip_text,
"class": status_code,
}
print(json.dumps(out_data))
@Surendrajat
Copy link
Author

Looks like they've added one more <span> wrapper around the temp value. Anyway I've fixed it for now. Cheers.

@chrisco23
Copy link

Is it possible to get Fahrenheit? I notice there is no separate URL for Fahrenheit on the website, guess some JS handling or something.

Possible to get 5-day?

When I click the popup/dropdown thing, goes to 404 page: https://weather.com/en-IN/weather/today/l/

ie. missing location_id at that point.

@alzalia1
Copy link

Hi, it seems they must have changed the min/max temp, because it's now reversed.

By changing :

# min-max temperature
temp_min = (
    html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
    .eq(0)
    .text()
)
temp_max = (
    html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
    .eq(1)
    .text()
)

To

# min-max temperature
temp_min = (
    html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
    .eq(1) # <- Changed here
    .text()
)
temp_max = (
    html_data("div[data-testid='wxData'] > span[data-testid='TemperatureValue']")
    .eq(0) # <- Changed here
    .text()
)

You get the right values again.

Here are some screenshot to display what I'm talking about :

What it is right now
before

What it is with the change
after

The values on the website
right_info

If this is a problem on my side, I apologize for the disturbance !

@DoYouSeeAmin
Copy link

which font u use for weather icon

@Riddmaker
Copy link

I got an update on the fetching logic, also discussed further above.
The correct synthax is now

# get html page
url = "https://weather.com/en-IN/weather/today/l/" + location_id
html_data = PyQuery(url=url)

Updates on what args PyQuery takes can be found here: https://pyquery.readthedocs.io/en/latest/scrap.html (as also posted further above by @cn-danieldev.)

As of date of this comment I can confirm that this script still works!

@SeanEParsons
Copy link

Is it possible to get Fahrenheit? I notice there is no separate URL for Fahrenheit on the website, guess some JS handling or something.

Possible to get 5-day?

When I click the popup/dropdown thing, goes to 404 page: https://weather.com/en-IN/weather/today/l/

ie. missing location_id at that point.

To get English/Imperial units append '?units=e' to the end of your location_id. My locations looks like this:
location_id = "ff02642ddd763393bf9158589c1b4804cc84c808136cc42554efce2a92fc47fb?unit=e" # appending '?unit=e' changes everything to English/Imperial units
Here is what my screen looks like, yes I made a few other minor mods to the file since I don't know/understand any Asian characters.
20241226_11h18m47s_grim
I hope you, and others, find this helpful.

@SeanEParsons
Copy link

Hello, while I did write my own CSS for this, I did notice a typo in your CSS. In your CSS you have:
#custom-weather.showyIcyDay, #custom-weather.snowyIcyNight { color: #d6e7e5; }
It should say:
#custom-weather.snowyIcyDay, #custom-weather.snowyIcyNight { color: #d6e7e5; }
I noticed it when I started with your CSS sheet and wanted to have different appearances for Day and Night. By the way, I love the script and the lack of need to sign up for any additional APIs. It is simple, looks great, and it works.
weather

@XenBuddha
Copy link

Can someone please demonstrate how to get the wind direction?

@tdtooke
Copy link

tdtooke commented Jan 28, 2025

First, I really like this module. One question. On mine I find a slight misalignment just under "feels like". One measurement seems to be too far over to the right. Is there a way I can correct this in the py file?

Screenshot_28-Jan_13-46-29_26388

edit: Nevermind, I fixed it. Turns out just removing the big tag around min-max fixes that. Also the min and max temps were reversed. Fixed that too. I have no idea what font you were using but I had to change all the weather icons to work with Jet Brains NF.

@tdtooke
Copy link

tdtooke commented Jan 28, 2025

Screenshot_28-Jan_13-59-13_5170

@tdtooke
Copy link

tdtooke commented Jan 30, 2025

Triple posting rebel I am. I just noticed that the max temperature now no longer works. After looking at the source code on weather.com I did find it again just one entry after the current temp. I adjusted my min and max settings. I'm no longer looking at what division it's in, just following the order I saw the different temps being mentioned:

# min-max temperature
temp_min = html_data("span[data-testid='TemperatureValue']").eq(2).text()
temp_max = html_data("span[data-testid='TemperatureValue']").eq(1).text()

@Surendrajat
Copy link
Author

Surendrajat commented Jan 30, 2025

Thanks everyone for keeping it alive while I was in jail(jk.. was def not..). Anyway I've updated the script with suggested fixes and a configuration section for common requests.

Thanks @alzalia1 @SeanEParsons @tdtooke for looking into it.

Is it possible to get Fahrenheit?... Possible to get 5-day?

@chrisco23 both possible now. Please check the config section.

which font u use for weather icon

@DoYouSeeAmin I use JetBrainsMono Nerd Font

Can someone please demonstrate how to get the wind direction?

@XenBuddha you can look into Wind--windWrapper class (specifically the highlighted property). It will give you rotation in degrees.

Screenshot 2025-01-30 at 12 39 43

I'm currently using macOS for work so haven't tested end-to-end. Let me know if you find any issues.

@XenBuddha
Copy link

Can someone please demonstrate how to get the wind direction?

@XenBuddha you can look into Wind--windWrapper class (specifically the highlighted property). It will give you rotation in degrees.

Screenshot 2025-01-30 at 12 39 43 I'm currently using macOS for work so haven't tested end-to-end. Let me know if you find any issues.

Thanks for your response. While I appreciate your response, this code isn't helpful to me. Any chance you could add the wind direction? The pointing arrow is preferable as it is the most concise. It seems to be the only major element missing from the script. Kr

@g-lpm
Copy link

g-lpm commented Mar 29, 2025

When trying to run the script, I get the following message:

Traceback (most recent call last):
File "/home/glmachado/.config/waybar/weather.py", line 79, in
wind_speed = html_data("span[data-testid='Wind']").text().split("\n")[1]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

It used to work just fine until yesterday.

@apraile
Copy link

apraile commented Mar 29, 2025

Hi @g-lpm , try to delete .split("\n")[1] from 83 line, such that it looks like this:

wind_speed = str(html_data("span[data-testid='Wind']").text())

@Surendrajat
Copy link
Author

Thanks @apraile. @g-lpm I've updated the code.

@Auretus
Copy link

Auretus commented Apr 8, 2025

The rain prediction is coming out weird, instead of showing the raindrop symbol like it should, it's coming out like:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment