A quick and dirty plugin to show countdowns to events -- in this case, F1 events
raceweek.py
import weechat
import os
from datetime import datetime as dt_class, timezone
try:
from zoneinfo import ZoneInfo
except ImportError:
from backports.zoneinfo import ZoneInfo
SCRIPT_NAME = "countdown"
SCRIPT_AUTHOR = "you"
SCRIPT_VERSION = "2.0"
SCRIPT_LICENSE = "GPL3"
SCRIPT_DESC = "Countdown to next F1 session from raceweek.txt"
_cache = []
_timer_hook = None
def parse_dt(date_str, time_str, tz):
date_parts = date_str.split("-")
time_parts = time_str.split(":")
return dt_class(
int(date_parts[0]), int(date_parts[1]), int(date_parts[2]),
int(time_parts[0]), int(time_parts[1]),
tzinfo=tz
)
def format_countdown(target):
now = dt_class.now(timezone.utc)
diff = target - now
total_seconds = int(diff.total_seconds())
weeks = total_seconds // (7 * 86400)
remainder = total_seconds % (7 * 86400)
days = remainder // 86400
remainder %= 86400
hours = remainder // 3600
remainder %= 3600
minutes = remainder // 60
seconds = remainder % 60
if total_seconds <= 300:
units = [("w", weeks), ("d", days), ("h", hours), ("m", minutes), ("s", f"{seconds:02d}")]
else:
units = [("w", weeks), ("d", days), ("h", hours), ("m", minutes)]
first = next((i for i, (_, v) in enumerate(units) if v), None)
last = max((i for i, (_, v) in enumerate(units) if v), default=None)
if first is None:
return "0s"
parts = [f"{v}{u}" for u, v in units[first:last+1]]
return " ".join(parts)
def get_interval_ms():
now = dt_class.now(timezone.utc)
for label, dt in _cache:
if dt > now:
diff = (dt - now).total_seconds()
if diff <= 300:
return 1000
break
interval = int(weechat.config_get_plugin("interval") or 60)
return interval * 60 * 1000
def load_cache():
global _cache
filepath = weechat.config_get_plugin("file") or os.path.expanduser("~/.weechat/raceweek.txt")
tz_str = weechat.config_get_plugin("file_timezone") or "UTC"
try:
tz = ZoneInfo(tz_str)
except Exception:
weechat.prnt("", f"[countdown] unknown timezone: {tz_str}, falling back to UTC")
tz = ZoneInfo("UTC")
if not os.path.exists(filepath):
weechat.prnt("", f"[countdown] file not found: {filepath}")
_cache = []
return
now = dt_class.now(timezone.utc)
future_events = []
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
try:
parts = line.split(" ", 2)
dt_aware = parse_dt(parts[0], parts[1], tz)
label = parts[2] if len(parts) > 2 else parts[0]
if dt_aware.astimezone(timezone.utc) > now:
future_events.append((label, dt_aware.astimezone(timezone.utc)))
except (ValueError, IndexError) as e:
weechat.prnt("", f"[countdown] couldn't parse line: {line} ({e})")
future_events.sort(key=lambda x: x[1])
_cache = future_events[:5]
def get_next_event():
now = dt_class.now(timezone.utc)
for label, dt in _cache:
if dt > now:
return label, dt
return None, None
def bar_item_cb(data, item, window):
label, target = get_next_event()
if target:
return f"{label}: {format_countdown(target)}"
return "no events"
def tick_cb(data, remaining_calls):
global _timer_hook
weechat.bar_item_update("countdown")
new_interval = get_interval_ms()
current_interval = getattr(tick_cb, '_last_interval', None)
if current_interval != new_interval:
weechat.unhook(_timer_hook)
_timer_hook = weechat.hook_timer(new_interval, 0, -1, "tick_cb", "")
tick_cb._last_interval = new_interval
return weechat.WEECHAT_RC_OK
def config_changed_cb(data, option, value):
global _timer_hook
if "interval" in option:
if _timer_hook:
weechat.unhook(_timer_hook)
interval_ms = (int(value) or 60) * 60 * 1000
_timer_hook = weechat.hook_timer(interval_ms, 0, -1, "tick_cb", "")
weechat.bar_item_update("countdown")
return weechat.WEECHAT_RC_OK
def main():
global _timer_hook
weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
SCRIPT_LICENSE, SCRIPT_DESC, "", "")
if not weechat.config_is_set_plugin("file"):
weechat.config_set_plugin("file", os.path.expanduser("~/.weechat/raceweek.txt"))
weechat.config_set_desc_plugin("file",
"Path to your raceweek.txt file")
if not weechat.config_is_set_plugin("file_timezone"):
weechat.config_set_plugin("file_timezone", "UTC")
weechat.config_set_desc_plugin("file_timezone",
"Timezone of the dates in the file (e.g. UTC, America/Vancouver, America/New_York)")
if not weechat.config_is_set_plugin("interval"):
weechat.config_set_plugin("interval", "60")
weechat.config_set_desc_plugin("interval",
"How often to update the bar item, in minutes (default: 60)")
weechat.hook_config("plugins.var.python.countdown.interval", "config_changed_cb", "")
weechat.bar_item_new("countdown", "bar_item_cb", "")
load_cache()
_timer_hook = weechat.hook_timer(get_interval_ms(), 0, -1, "tick_cb", "")
tick_cb._last_interval = get_interval_ms()
weechat.bar_item_update("countdown")
main()raceweek.txt
2026-03-21 08:10 test test
2026-03-27 02:30 ๐ฏ๐ต Japan: FP1
2026-03-27 06:00 ๐ฏ๐ต Japan: FP2
2026-03-28 02:30 ๐ฏ๐ต Japan: FP3
2026-03-28 06:00 ๐ฏ๐ต Japan: Qualy
2026-03-29 06:00 ๐ฏ๐ต Japan: Grand Prix
2026-05-01 17:30 ๐บ๐ธ Miami: FP1
2026-05-01 21:30 ๐บ๐ธ Miami: Sprint Qualy
2026-05-02 17:00 ๐บ๐ธ Miami: Sprint
2026-05-02 21:00 ๐บ๐ธ Miami: Qualy
2026-05-03 21:00 ๐บ๐ธ Miami: Grand Prix
2026-05-22 17:30 ๐จ๐ฆ Canada: FP1
2026-05-22 21:30 ๐จ๐ฆ Canada: Sprint Qualy
2026-05-23 17:00 ๐จ๐ฆ Canada: Sprint
2026-05-23 21:00 ๐จ๐ฆ Canada: Qualy
2026-05-24 21:00 ๐จ๐ฆ Canada: Grand Prix
2026-06-05 12:30 ๐ฒ๐จ Monaco: FP1
2026-06-05 16:00 ๐ฒ๐จ Monaco: FP2
2026-06-06 11:30 ๐ฒ๐จ Monaco: FP3
2026-06-06 15:00 ๐ฒ๐จ Monaco: Qualy
2026-06-07 14:00 ๐ฒ๐จ Monaco: Grand Prix
2026-06-26 12:30 ๐ฆ๐น Austria: FP1
2026-06-26 16:00 ๐ฆ๐น Austria: FP2
2026-06-27 11:30 ๐ฆ๐น Austria: FP3
2026-06-27 15:00 ๐ฆ๐น Austria: Qualy
2026-06-28 14:00 ๐ฆ๐น Austria: Grand Prix
2026-07-03 12:30 ๐ฌ๐ง Britain: FP1
2026-07-03 16:30 ๐ฌ๐ง Britain: Sprint Qualy
2026-07-04 12:00 ๐ฌ๐ง Britain: Sprint
2026-07-04 16:00 ๐ฌ๐ง Britain: Qualy
2026-07-05 15:00 ๐ฌ๐ง Britain: Grand Prix
2026-07-17 12:30 ๐ง๐ช Belgium: FP1
2026-07-17 16:00 ๐ง๐ช Belgium: FP2
2026-07-18 11:30 ๐ง๐ช Belgium: FP3
2026-07-18 15:00 ๐ง๐ช Belgium: Qualy
2026-07-19 14:00 ๐ง๐ช Belgium: Grand Prix
2026-07-24 12:30 ๐ญ๐บ Hungary: FP1
2026-07-24 16:00 ๐ญ๐บ Hungary: FP2
2026-07-25 11:30 ๐ญ๐บ Hungary: FP3
2026-07-25 15:00 ๐ญ๐บ Hungary: Qualy
2026-07-26 14:00 ๐ญ๐บ Hungary: Grand Prix
2026-08-21 11:30 ๐ณ๐ฑ Netherlands: FP1
2026-08-21 15:30 ๐ณ๐ฑ Netherlands: Sprint Qualy
2026-08-22 11:00 ๐ณ๐ฑ Netherlands: Sprint
2026-08-22 15:00 ๐ณ๐ฑ Netherlands: Qualy
2026-08-23 14:00 ๐ณ๐ฑ Netherlands: Grand Prix
2026-09-04 11:30 ๐ฎ๐น Italy: FP1
2026-09-04 15:00 ๐ฎ๐น Italy: FP2
2026-09-05 11:30 ๐ฎ๐น Italy: FP3
2026-09-05 15:00 ๐ฎ๐น Italy: Qualy
2026-09-06 14:00 ๐ฎ๐น Italy: Grand Prix
2026-09-24 09:30 ๐ฆ๐ฟ Baku: FP1
2026-09-24 13:00 ๐ฆ๐ฟ Baku: FP2
2026-09-25 09:30 ๐ฆ๐ฟ Baku: FP3
2026-09-25 13:00 ๐ฆ๐ฟ Baku: Qualy
2026-09-26 12:00 ๐ฆ๐ฟ Baku: Grand Prix
2026-10-09 09:30 ๐ธ๐ฌ Singapore: FP1
2026-10-09 13:30 ๐ธ๐ฌ Singapore: Sprint Qualy
2026-10-10 10:00 ๐ธ๐ฌ Singapore: Sprint
2026-10-10 14:00 ๐ธ๐ฌ Singapore: Qualy
2026-10-11 13:00 ๐ธ๐ฌ Singapore: Grand Prix
2026-10-23 18:30 ๐บ๐ธ Austin: FP1
2026-10-23 22:00 ๐บ๐ธ Austin: FP2
2026-10-24 18:30 ๐บ๐ธ Austin: FP3
2026-10-24 22:00 ๐บ๐ธ Austin: Qualy
2026-10-25 20:00 ๐บ๐ธ Austin: Grand Prix
2026-10-30 18:30 ๐ฒ๐ฝ Mexico: FP1
2026-10-30 22:00 ๐ฒ๐ฝ Mexico: FP2
2026-10-31 17:30 ๐ฒ๐ฝ Mexico: FP3
2026-10-31 21:00 ๐ฒ๐ฝ Mexico: Qualy
2026-11-01 20:00 ๐ฒ๐ฝ Mexico: Grand Prix
2026-11-06 15:30 ๐ง๐ท Brazil: FP1
2026-11-06 19:00 ๐ง๐ท Brazil: FP2
2026-11-07 14:30 ๐ง๐ท Brazil: FP3
2026-11-07 18:00 ๐ง๐ท Brazil: Qualy
2026-11-08 17:00 ๐ง๐ท Brazil: Grand Prix
2026-11-20 00:30 ๐บ๐ธ Las Vegas: FP1
2026-11-20 04:00 ๐บ๐ธ Las Vegas: FP2
2026-11-21 00:30 ๐บ๐ธ Las Vegas: FP3
2026-11-21 04:00 ๐บ๐ธ Las Vegas: Qualy
2026-11-22 04:00 ๐บ๐ธ Las Vegas: Grand Prix
2026-11-27 13:30 ๐ถ๐ฆ Qatar: FP1
2026-11-27 17:00 ๐ถ๐ฆ Qatar: FP2
2026-11-28 14:30 ๐ถ๐ฆ Qatar: FP3
2026-11-28 18:00 ๐ถ๐ฆ Qatar: Qualy
2026-11-29 16:00 ๐ถ๐ฆ Qatar: Grand Prix
2026-12-04 09:30 ๐ฆ๐ช Abu Dhabi: FP1
2026-12-04 13:00 ๐ฆ๐ช Abu Dhabi: FP2
2026-12-05 10:30 ๐ฆ๐ช Abu Dhabi: FP3
2026-12-05 14:00 ๐ฆ๐ช Abu Dhabi: Qualy
2026-12-06 13:00 ๐ฆ๐ช Abu Dhabi: Grand Prix
2026-06-12 12:30 ๐ช๐ธ Barcelona: FP1
2026-06-12 16:00 ๐ช๐ธ Barcelona: FP2
2026-06-13 11:30 ๐ช๐ธ Barcelona: FP3
2026-06-13 15:00 ๐ช๐ธ Barcelona: Qualy
2026-06-14 14:00 ๐ช๐ธ Barcelona: Grand Prix
2026-09-11 12:30 ๐ช๐ธ Madrid: FP1
2026-09-11 16:00 ๐ช๐ธ Madrid: FP2
2026-09-12 11:30 ๐ช๐ธ Madrid: FP3
2026-09-12 15:00 ๐ช๐ธ Madrid: Qualy
2026-09-13 14:00 ๐ช๐ธ Madrid: Grand Prix