aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/i3blocks/.local/bin
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-10-07 13:30:57 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-10-07 13:30:57 -0500
commite842d34c9ae3a48a78bbcaa5b9fc58216e1d49a6 (patch)
tree9b3b9c8883f5536fddf736a93c29e3dc1dab9446 /i3blocks/.local/bin
parent59dcb091288d60e8c2d2e352711b72f4beeb1ddf (diff)
refactor(i3blocks): move i3blocks to bin dir
Diffstat (limited to 'i3blocks/.local/bin')
-rwxr-xr-xi3blocks/.local/bin/i3blocks-cpu42
-rwxr-xr-xi3blocks/.local/bin/i3blocks-disk28
-rwxr-xr-xi3blocks/.local/bin/i3blocks-dunst146
-rwxr-xr-xi3blocks/.local/bin/i3blocks-gpu30
-rwxr-xr-xi3blocks/.local/bin/i3blocks-memory30
-rwxr-xr-xi3blocks/.local/bin/i3blocks-miniflux39
-rwxr-xr-xi3blocks/.local/bin/i3blocks-net54
-rwxr-xr-xi3blocks/.local/bin/i3blocks-notmuch33
-rwxr-xr-xi3blocks/.local/bin/i3blocks-title17
-rwxr-xr-xi3blocks/.local/bin/i3blocks-volume128
-rwxr-xr-xi3blocks/.local/bin/i3blocks-wttr152
11 files changed, 699 insertions, 0 deletions
diff --git a/i3blocks/.local/bin/i3blocks-cpu b/i3blocks/.local/bin/i3blocks-cpu
new file mode 100755
index 0000000..ddb1d68
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-cpu
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+import os
+import json
+
+ICON = "\U000f0ee0" # 󰻠
+BLACK = f"#{os.environ.get("BASE16_COLOR_00_HEX")}"
+RED = f"#{os.environ.get("BASE16_COLOR_08_HEX")}"
+YELLOW = f"#{os.environ.get("BASE16_COLOR_0A_HEX")}"
+AQUA = f"#{os.environ.get("BASE16_COLOR_0C_HEX")}"
+
+
+def main():
+ off = json.loads(os.environ.get("values", json.dumps([0 for i in range(10)])))
+
+ with open("/proc/stat", "r") as f:
+ val = [int(x) for x in f.readline().strip().split(" ")[2:]]
+
+ if elapsed := sum(val + off):
+ usage = sum(val[0:3] + val[5:] + off[0:3] + off[5:]) / elapsed
+ else:
+ usage = 0
+
+ output = {
+ "full_text": " {} {:.0%} ".format(ICON, usage),
+ "values": [-x for x in val],
+ }
+ if usage > 0.9:
+ output["color"] = BLACK
+ output["background"] = RED
+ elif usage > 0.6:
+ output["color"] = BLACK
+ output["background"] = YELLOW
+ elif usage > 0.3:
+ output["color"] = BLACK
+ output["background"] = AQUA
+
+ print(json.dumps(output, ensure_ascii=False), flush=True)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-disk b/i3blocks/.local/bin/i3blocks-disk
new file mode 100755
index 0000000..a98a40c
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-disk
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+import os
+
+ICON = "\U000f02ca" # 󰋊
+GB = 1073741824
+
+
+def main():
+ statvfs = os.statvfs("/")
+ avail = bytes = statvfs.f_bavail * statvfs.f_frsize
+
+ for unit in ["B", "KB", "MB", "GB", "TB", "PB"]:
+ if avail < 1024.0 or unit == "PB":
+ break
+ avail /= 1024.0
+
+ print(f" {ICON} {round(avail)}{unit} \n")
+ if bytes < (20 * GB):
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}")
+ elif bytes < (10 * GB):
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-dunst b/i3blocks/.local/bin/i3blocks-dunst
new file mode 100755
index 0000000..df3d9d4
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-dunst
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+
+import asyncio
+import os
+import sys
+import json
+import pprint
+
+from dbus_next.aio import MessageBus
+import i3ipc
+from i3ipc.aio import Connection
+
+
+BUS_NAME = "org.freedesktop.Notifications"
+OBJECT_PATH = "/org/freedesktop/Notifications"
+DUNST_INTERFACE = "org.dunstproject.cmd0"
+PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"
+I3_EVENTS = [
+ i3ipc.Event.WINDOW_FOCUS,
+ i3ipc.Event.WINDOW_FULLSCREEN_MODE,
+ i3ipc.Event.WINDOW_CLOSE,
+ i3ipc.Event.WORKSPACE_FOCUS,
+]
+
+
+class Dunst:
+ @classmethod
+ async def connect(cls):
+ self = cls()
+ self.bus = await MessageBus().connect()
+
+ self.obj = self.bus.get_proxy_object(
+ BUS_NAME,
+ OBJECT_PATH,
+ await self.bus.introspect(BUS_NAME, OBJECT_PATH),
+ )
+
+ self.dunst = self.obj.get_interface(DUNST_INTERFACE)
+ self.paused = await self.dunst.get_paused()
+ self.displayed = await self.dunst.get_displayed_length()
+ self.waiting = await self.dunst.get_waiting_length()
+
+ self.i3 = await Connection().connect()
+
+ for event in I3_EVENTS:
+ self.i3.on(event, self.handle_i3_event)
+
+ return self
+
+ async def handle_i3_event(self, conn, e):
+ if e.change == "close":
+ enable = 0
+ elif isinstance(e, i3ipc.WorkspaceEvent):
+ enable = e.current.fullscreen_mode
+ else:
+ enable = e.container.fullscreen_mode
+
+ await self.dunst.call_rule_enable("transient_skip", enable)
+
+
+ def print_status(self):
+ if self.paused:
+ icon = "\U000f009b" # 󰂛
+ else:
+ icon = "\U000f009a" # 󰂚
+
+ if self.waiting > 0:
+ count = f" {self.waiting}"
+ else:
+ count = ""
+
+ output = {"full_text": f" {icon}{count} "}
+
+ if self.displayed + self.waiting > 0:
+ output["color"] = f"#{os.environ.get("BASE16_COLOR_00_HEX")}"
+ output["background"] = f"#{os.environ.get("BASE16_COLOR_0C_HEX")}"
+ elif self.paused:
+ output["color"] = f"#{os.environ.get("BASE16_COLOR_00_HEX")}"
+ output["background"] = f"#{os.environ.get("BASE16_COLOR_0A_HEX")}"
+
+ print(json.dumps(output, ensure_ascii=False), flush=True)
+
+ def on_properties_changed(self, _name, property, invalidated):
+ update = False
+ for property, variant in property.items():
+ match property:
+ case "paused":
+ self.paused = variant.value
+ case "displayedLength":
+ self.displayed = variant.value
+ case "waitingLength":
+ self.waiting = variant.value
+ case _:
+ continue
+ update = True
+
+ if update:
+ self.print_status()
+
+ async def listener(self):
+ properties = self.obj.get_interface(PROPERTIES_INTERFACE)
+ properties.on_properties_changed(self.on_properties_changed)
+
+ await self.bus.wait_for_disconnect()
+
+ async def button_handler(self):
+ loop = asyncio.get_event_loop()
+ reader = asyncio.StreamReader()
+ protocol = asyncio.StreamReaderProtocol(reader)
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
+
+ while True:
+ line = await reader.readline()
+
+ if not line:
+ await asyncio.sleep(1)
+ continue
+
+ match json.loads(line):
+ case {"button": 1}:
+ await self.dunst.call_notification_show()
+ case {"button": 2}:
+ pass
+ case {"button": 3}:
+ await self.dunst.set_paused(not self.paused)
+ case {"button": 4}:
+ pass
+ case {"button": 5}:
+ pass
+
+
+async def main():
+ dunst = await Dunst.connect()
+ dunst.print_status()
+
+ try:
+ async with asyncio.TaskGroup() as task_group:
+ task_group.create_task(dunst.i3.main())
+ task_group.create_task(dunst.listener())
+ task_group.create_task(dunst.button_handler())
+ except asyncio.CancelledError:
+ return
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/i3blocks/.local/bin/i3blocks-gpu b/i3blocks/.local/bin/i3blocks-gpu
new file mode 100755
index 0000000..df96d6c
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-gpu
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import os
+import glob
+
+ICON = "\U000f0379" # 󰍹
+
+
+def main():
+ files = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
+ if not files:
+ return
+
+ with open(files[0]) as f:
+ load = int(f.readline())
+
+ print(f" {ICON} {load}% \n")
+ if load > 90:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}")
+ elif load > 60:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}")
+ elif load > 30:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0C_HEX")}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-memory b/i3blocks/.local/bin/i3blocks-memory
new file mode 100755
index 0000000..f62d5c7
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-memory
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import os
+import linecache
+
+ICON = "\U000f035b" # 󰍛
+
+
+def main():
+ linecache.getline("/proc/meminfo", 0)
+ linecache.getline("/proc/meminfo", 2)
+
+ with open("/proc/meminfo", "r") as f:
+ meminfo = dict(
+ (i.split()[0].rstrip(":"), int(i.split()[1])) for i in f.readlines()
+ )
+
+ used = 100 - round(100 * meminfo["MemAvailable"] / meminfo["MemTotal"])
+
+ print(f" {ICON} {used}% \n")
+ if used > 90:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}")
+ elif used > 75:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-miniflux b/i3blocks/.local/bin/i3blocks-miniflux
new file mode 100755
index 0000000..f87f1d3
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-miniflux
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+import os
+import subprocess
+
+import requests
+
+ICON = "\U000f046b" # 󰑫
+
+
+def get_count():
+ url = "https://miniflux.tobyvin.dev/v1/feeds/counters"
+ headers = {"X-Auth-Token": os.environ.get("MINIFLUX_TOKEN")}
+ resp = requests.get(url, headers=headers)
+ return sum(resp.json().get("unreads").values())
+
+
+def main():
+ match int(os.environ.get("BLOCK_BUTTON", "0")):
+ case 1:
+ subprocess.run(["xdg-open", "https://miniflux.tobyvin.dev"])
+
+ try:
+ count = get_count()
+ except requests.exceptions.ConnectionError:
+ count = "x"
+
+ print(f" {ICON} {count} \n")
+ match count:
+ case str(c) | int(c) if c > 10:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}")
+ case int(c) if c > 0:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-net b/i3blocks/.local/bin/i3blocks-net
new file mode 100755
index 0000000..2faa071
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-net
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+import glob
+import os
+from enum import Enum
+
+
+class IFaceType(Enum):
+ ETHERNET = "\U000f0200"
+ WIRELESS = "\U000f05a9"
+ TUNNEL = "\U000f0582"
+ UNKNOWN = ""
+
+ @staticmethod
+ def from_path(path: str):
+ try:
+ with open(os.path.join(path, "type"), "r") as f:
+ match int(f.readline()):
+ case 1 if os.path.isdir(
+ os.path.join(path, "wireless")
+ ) or os.path.islink(os.path.join(path, "phy80211")):
+ return IFaceType.WIRELESS
+ case 1:
+ return IFaceType.ETHERNET
+ case 65534:
+ return IFaceType.TUNNEL
+ case _:
+ raise ValueError("Invalid interface type")
+ except Exception:
+ return IFaceType.UNKNOWN
+
+
+class IFace:
+ def __init__(self, path: str):
+ self.path = path
+ self.name = os.path.basename(path)
+ self._type = IFaceType.from_path(path)
+
+ def __str__(self):
+ return self._type.value
+
+
+def main():
+ ifaces = []
+ for net in glob.glob("/sys/class/net/*"):
+ iface = IFace(net)
+ if iface.name != "lo" and iface._type != IFaceType.UNKNOWN:
+ ifaces.append(str(iface))
+
+ print(f" {" ".join(ifaces)} \n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-notmuch b/i3blocks/.local/bin/i3blocks-notmuch
new file mode 100755
index 0000000..a81fae6
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-notmuch
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+
+import notmuch
+import os
+
+ICON = "\U000f01ee" # 󰇮
+
+
+def main():
+ # Workaround for bug in the notmuch module's default config resolution
+ if os.environ.get("NOTMUCH_CONFIG") is None:
+ os.environ["NOTMUCH_CONFIG"] = os.path.join(
+ os.environ.get(
+ "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")
+ ),
+ "notmuch",
+ os.environ.get("NOTMUCH_PROFILE", "default"),
+ "config",
+ )
+
+ count = notmuch.Database().create_query("tag:unread").count_messages()
+
+ print(f" {ICON} {count} \n")
+ if count > 10:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_08_HEX")}")
+ elif count > 0:
+ print(f"#{os.environ.get("BASE16_COLOR_00_HEX")}")
+ print(f"#{os.environ.get("BASE16_COLOR_0A_HEX")}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/i3blocks/.local/bin/i3blocks-title b/i3blocks/.local/bin/i3blocks-title
new file mode 100755
index 0000000..c014adc
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-title
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+into_block='{
+ "full_text": (.name[0:50] // ""),
+ "short_text": ([.app_id, .window_title, .name] | map(select(. != null and . != "")) | (first // "") | .[0:25]),
+}'
+
+swaymsg -t get_tree | jq -c '.. | select(.focused? == true and .type == "con") | '"$into_block"
+
+swaymsg --monitor -t subscribe '["window", "workspace"]' | jq -c --unbuffered '
+ select((.change | test("close|focus|title")) and (.current.type == "workspace" or .container.focused)) |
+ if .current.type == "workspace" or .change == "close" then
+ { "full_text": "" }
+ else
+ .container | '"$into_block"'
+ end
+'
diff --git a/i3blocks/.local/bin/i3blocks-volume b/i3blocks/.local/bin/i3blocks-volume
new file mode 100755
index 0000000..40854e7
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-volume
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+
+import asyncio
+import json
+import os
+import subprocess
+import sys
+
+
+ICONS = {
+ "mute": "\U000f075f", # 󰝟
+ "low": "\U000f057f", # 󰕿
+ "medium": "\U000f0580", # 󰖀
+ "high": "\U000f057e", # 󰕾
+}
+
+
+def is_muted():
+ return (
+ subprocess.run(
+ ["pactl", "get-sink-mute", "@DEFAULT_SINK@"],
+ capture_output=True,
+ encoding="UTF-8",
+ )
+ .stdout.removeprefix("Mute: ")
+ .strip()
+ == "yes"
+ )
+
+
+def get_volume():
+ stdout = subprocess.run(
+ ["pactl", "get-sink-volume", "@DEFAULT_SINK@"],
+ capture_output=True,
+ encoding="UTF-8",
+ ).stdout.strip()
+
+ for s in stdout.removeprefix("Volume: ").split():
+ if s.endswith("%"):
+ return int(s.rstrip("%"))
+
+
+def print_status():
+ match get_volume():
+ case None:
+ output = {}
+ case v if is_muted():
+ output = {
+ "full_text": f" {ICONS["mute"]} {v}% ",
+ "color": f"#{os.environ.get("BASE16_COLOR_00_HEX")}",
+ "background": f"#{os.environ.get("BASE16_COLOR_0A_HEX")}",
+ }
+ case v if v > 66:
+ output = {"full_text": f" {ICONS["high"]} {v}% "}
+ case v if v > 33:
+ output = {"full_text": f" {ICONS["medium"]} {v}% "}
+ case v:
+ output = {"full_text": f" {ICONS["low"]} {v}% "}
+
+ print(json.dumps(output, ensure_ascii=False), flush=True)
+
+
+async def listener():
+ process = await asyncio.create_subprocess_exec(
+ "pactl",
+ "--format=json",
+ "subscribe",
+ stdout=asyncio.subprocess.PIPE,
+ )
+
+ while True:
+ line = await process.stdout.readline()
+
+ if not line:
+ await asyncio.sleep(1)
+ continue
+
+ match json.loads(line.decode("UTF-8")):
+ case {"on": "sink"} | {"on": "source-output"}:
+ print_status()
+
+
+async def button_handler():
+ loop = asyncio.get_event_loop()
+ reader = asyncio.StreamReader()
+ protocol = asyncio.StreamReaderProtocol(reader)
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
+ proc: subprocess.Popen = None
+
+ while True:
+ line = await reader.readline()
+
+ if not line:
+ await asyncio.sleep(1)
+ continue
+
+ match json.loads(line):
+ case {"button": 1} if proc:
+ proc.terminate()
+ proc = None
+ case {"button": 1, "gui": cmd}:
+ proc = subprocess.Popen(cmd, shell=True)
+ case {"button": 2}:
+ pass
+ case {"button": 3}:
+ subprocess.run(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"])
+ case {"button": 4}:
+ subprocess.run(
+ ["wpctl", "set-volume", "-l", "1.5", "@DEFAULT_AUDIO_SINK@", "5%+"]
+ )
+ case {"button": 5}:
+ subprocess.run(
+ ["wpctl", "set-volume", "-l", "1.5", "@DEFAULT_AUDIO_SINK@", "5%-"]
+ )
+
+
+async def main():
+ print_status()
+ try:
+ async with asyncio.TaskGroup() as task_group:
+ task_group.create_task(listener())
+ task_group.create_task(button_handler())
+ except asyncio.CancelledError:
+ return
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/i3blocks/.local/bin/i3blocks-wttr b/i3blocks/.local/bin/i3blocks-wttr
new file mode 100755
index 0000000..8ae4857
--- /dev/null
+++ b/i3blocks/.local/bin/i3blocks-wttr
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+
+import time
+
+import requests
+
+ICON = "\U000f046b" # 󰑫
+
+WWO_CODE = {
+ "113": "Sunny",
+ "116": "PartlyCloudy",
+ "119": "Cloudy",
+ "122": "VeryCloudy",
+ "143": "Fog",
+ "176": "LightShowers",
+ "179": "LightSleetShowers",
+ "182": "LightSleet",
+ "185": "LightSleet",
+ "200": "ThunderyShowers",
+ "227": "LightSnow",
+ "230": "HeavySnow",
+ "248": "Fog",
+ "260": "Fog",
+ "263": "LightShowers",
+ "266": "LightRain",
+ "281": "LightSleet",
+ "284": "LightSleet",
+ "293": "LightRain",
+ "296": "LightRain",
+ "299": "HeavyShowers",
+ "302": "HeavyRain",
+ "305": "HeavyShowers",
+ "308": "HeavyRain",
+ "311": "LightSleet",
+ "314": "LightSleet",
+ "317": "LightSleet",
+ "320": "LightSnow",
+ "323": "LightSnowShowers",
+ "326": "LightSnowShowers",
+ "329": "HeavySnow",
+ "332": "HeavySnow",
+ "335": "HeavySnowShowers",
+ "338": "HeavySnow",
+ "350": "LightSleet",
+ "353": "LightShowers",
+ "356": "HeavyShowers",
+ "359": "HeavyRain",
+ "362": "LightSleetShowers",
+ "365": "LightSleetShowers",
+ "368": "LightSnowShowers",
+ "371": "HeavySnowShowers",
+ "374": "LightSleetShowers",
+ "377": "LightSleet",
+ "386": "ThunderyShowers",
+ "389": "ThunderyHeavyRain",
+ "392": "ThunderySnowShowers",
+ "395": "HeavySnowShowers",
+}
+
+WEATHER_SYMBOL_WI_DAY = {
+ "Unknown": "",
+ "Cloudy": "",
+ "Fog": "",
+ "HeavyRain": "",
+ "HeavyShowers": "",
+ "HeavySnow": "",
+ "HeavySnowShowers": "",
+ "LightRain": "",
+ "LightShowers": "",
+ "LightSleet": "",
+ "LightSleetShowers": "",
+ "LightSnow": "",
+ "LightSnowShowers": "",
+ "PartlyCloudy": "",
+ "Sunny": "",
+ "ThunderyHeavyRain": "",
+ "ThunderyShowers": "",
+ "ThunderySnowShowers": "",
+ "VeryCloudy": "",
+}
+
+WEATHER_SYMBOL_WI_NIGHT = {
+ "Unknown": "",
+ "Cloudy": "",
+ "Fog": "",
+ "HeavyRain": "",
+ "HeavyShowers": "",
+ "HeavySnow": "",
+ "HeavySnowShowers": "",
+ "LightRain": "",
+ "LightShowers": "",
+ "LightSleet": "",
+ "LightSleetShowers": "",
+ "LightSnow": "",
+ "LightSnowShowers": "",
+ "PartlyCloudy": "",
+ "Sunny": "",
+ "ThunderyHeavyRain": "",
+ "ThunderyShowers": "",
+ "ThunderySnowShowers": "",
+ "VeryCloudy": "",
+}
+
+
+def parse_data() -> str:
+ data = requests.get("https://wttr.in/?format=j1").json()
+
+ current = next(iter(data.get("current_condition", [])), None)
+
+ if current is None:
+ return ""
+
+ code = current.get("weatherCode", "0")
+ key = WWO_CODE.get(code, "Unknown")
+
+ local_obs_date, local_obs_time = current.get("localObsDateTime").split(maxsplit=1)
+ astronomy = next(
+ iter(
+ next(
+ w.get("astronomy")
+ for w in data.get("weather", [])
+ if w.get("date", None) == local_obs_date and w.get("astronomy", None)
+ )
+ ),
+ None,
+ )
+
+ if astronomy is None:
+ return None
+
+ local = time.strptime(local_obs_time, "%I:%M %p")
+ sunrise = time.strptime(astronomy.get("sunrise"), "%I:%M %p")
+ sunset = time.strptime(astronomy.get("sunset"), "%I:%M %p")
+
+ if local > sunrise and local < sunset:
+ icon = WEATHER_SYMBOL_WI_DAY.get(key)
+ else:
+ icon = WEATHER_SYMBOL_WI_NIGHT.get(key)
+
+ if temp := current.get("temp_C"):
+ return " {} {}°C ".format(icon, temp)
+
+
+def main():
+ try:
+ print(parse_data())
+ except Exception:
+ print()
+
+
+if __name__ == "__main__":
+ main()