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))
@StayPirate
Copy link

I already set my location from there (in weather.py), but the hash value is not available in ~/.config/waybar/config. Does it work for you?

@Surendrajat
Copy link
Author

Surendrajat commented Jun 17, 2022

Yeah that won't work unless you replace it with actual location_id or export a shell variable location_id as I do.

@StayPirate
Copy link

StayPirate commented Oct 28, 2022

I noticed today that something may have changed in the https://weather.com/en-IN/weather/today/l/... webpage and the html_data filters may now be broken.

I initially realized it because of the following error:

[2022-10-28 10:40:55.065] [error] weather stopped unexpectedly, is it endless?
Traceback (most recent call last):
  File "/home/crazybyte/.config/waybar/modules/weather", line 92, in <module>
    wind_speed = html_data(
IndexError: list index out of range

The CSS item is now class .Wind--windWrapper--3Ly7c instead of .Wind--windWrapper--3aqXJ, but fixing it is not enough to make the script works properly again.

@StayPirate
Copy link

May I ask you why are you scraping data from the web-page instead of querying some API endpoints?

@Surendrajat
Copy link
Author

Surendrajat commented Oct 29, 2022

@StayPirate Thanks for letting me know. I'm not using sway right now, but I've tried to update the script for now and hopefully made it less fragile. (Probably you can fork and maintain it if you're interested)

May I ask you why are you scraping data from the web-page instead of querying some API endpoints?

Yeah that'd be simpler, but I actively try to avoid signing up for services that are otherwise accessible.

@ShabirK21
Copy link

I get his error when i try to run, i've changed it my own location

Traceback (most recent call last):
File "/home/shabirk/.config/waybar/modules/weather.py", line 46, in
status_code = html_data("#regionHeader").attr("class").split(" ")[2].split("-")[2]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'split'

@k1tyoodev
Copy link

k1tyoodev commented Feb 2, 2023

Traceback (most recent call last):
File "/home/shabirk/.config/waybar/modules/weather.py", line 46, in
status_code = html_data("#regionHeader").attr("class").split(" ")[2].split("-")[2]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'split'

The new document of pyquery module shows the method below will no longer fetch the contents of the URL.

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

The right way

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

https://pyquery.readthedocs.io/en/latest/scrap.html

@Surendrajat
Copy link
Author

Surendrajat commented Feb 2, 2023

Thanks @k1tyoo for tracking it down. I've updated the script to work with pyquery 1 & 2 now.

Copy link

ghost commented Feb 6, 2023

What are the default icons, as a lot of them are not showing for me, other than that I appreciate the work you have done and it's a great module :)

@Surendrajat
Copy link
Author

@CerealKiller-John thanks. I've mostly used font-awesome icons and few icons from nerd-fonts. This is my setup:

https://github.com/Surendrajat/dotfiles/blob/f3b2deef20fac21285dcefe52ed8eccdcb13fa73/.config/waybar/style.css#L48

Copy link

ghost commented Feb 7, 2023

@CerealKiller-John thanks. I've mostly used font-awesome icons and few icons from nerd-fonts. This is my setup:

https://github.com/Surendrajat/dotfiles/blob/f3b2deef20fac21285dcefe52ed8eccdcb13fa73/.config/waybar/style.css#L48

Thank you, I also was wondering is there a way to incorporate current location into this? As I am using it on a laptop, I wouldn't really need to know my home forecast if I am not at home. I know I could update the key, but I was wondering if there was an easier way?

@Surendrajat
Copy link
Author

I also was wondering is there a way to incorporate current location into this?

I guess you can get your lat-long from laptop's GPS and submit it to weather.com's HTML form (using a similar script) and get location id from redirection URL. It'll a bit complicated and will depend on the accuracy of your GPS. Anyway, you might be better off with some API from weather providers at that point.

@putridpete
Copy link

Hello there! Thanks for this wonderful module. In the past few days, I've realized that the "Feels like" section inside the tooltip does not print out any value. I'm not sure why, as the values seem to align with the website its scrapes from. Any ideas about how to fix?

@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