Skip to content

Instantly share code, notes, and snippets.

@boringparty
Created March 21, 2026 09:59
Show Gist options
  • Select an option

  • Save boringparty/665177b80193749e1e2c8c1e7863247b to your computer and use it in GitHub Desktop.

Select an option

Save boringparty/665177b80193749e1e2c8c1e7863247b to your computer and use it in GitHub Desktop.
A weechat plugin to show countdowns

A quick and dirty plugin to show countdowns to events -- in this case, F1 events

script

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()

data

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment