8 Commits

Author SHA1 Message Date
92ad93a710 Merge branch 'remove-golly-notifier'
All checks were successful
The Waste Land / post-line (push) Successful in 6s
The Odyssey / post-chunk (push) Successful in 7s
Paradise Lost / post-line (push) Successful in 6s
* remove-golly-notifier:
  remove gitea workflows
  remove golly-notifier files
2026-04-25 07:31:00 -07:00
a75748073c remove gitea workflows 2026-04-25 07:30:46 -07:00
f9ed9f63e9 remove golly-notifier files 2026-04-25 07:30:06 -07:00
cbb18d7fb2 update readme 2026-04-25 07:29:40 -07:00
84a89adabf fix duplicate header and text
All checks were successful
The Waste Land / post-line (push) Successful in 6s
Hellmouth Cup Notifier / notify (push) Successful in 6s
The Odyssey / post-chunk (push) Successful in 6s
Paradise Lost / post-line (push) Successful in 6s
2026-04-24 13:21:02 -07:00
53133e4074 fix color issue
All checks were successful
The Waste Land / post-line (push) Successful in 8s
Hellmouth Cup Notifier / notify (push) Successful in 7s
The Odyssey / post-chunk (push) Successful in 7s
Paradise Lost / post-line (push) Successful in 7s
2026-04-24 11:23:45 -07:00
8487bb5b48 tighten up cron schedule
All checks were successful
The Waste Land / post-line (push) Successful in 6s
Hellmouth Cup Notifier / notify (push) Successful in 7s
The Odyssey / post-chunk (push) Successful in 7s
Paradise Lost / post-line (push) Successful in 7s
2026-04-24 10:15:34 -07:00
823304964a Merge branch 'golly-showy'
All checks were successful
The Waste Land / post-line (push) Successful in 6s
Hellmouth Cup Notifier / notify (push) Successful in 6s
The Odyssey / post-chunk (push) Successful in 7s
Paradise Lost / post-line (push) Successful in 6s
* golly-showy:
  update golly notifier to use Block Kit blocks
2026-04-22 19:05:27 -07:00
9 changed files with 3 additions and 690 deletions

View File

@@ -1,31 +0,0 @@
name: Hellmouth Cup Notifier
on:
schedule:
# Every hour at minute 1, Friday through Sunday UTC
# 8 AM Pacific = 15:00 UTC (PDT) or 16:00 UTC (PST)
# Run every hour Fri-Sun UTC to cover both; script checks Pacific time
- cron: '1 * * * 5,6,0'
workflow_dispatch:
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r golly-notifier/requirements.txt
- name: Run Hellmouth Cup notifier
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.GOLLY_NOTIFIER_CHANNEL_ID }}
run: python golly-notifier/notifier.py hellmouth

View File

@@ -1,31 +0,0 @@
name: Star Cup Notifier
on:
schedule:
# Every hour at minute 1, Tuesday and Wednesday UTC
# 5 AM Pacific = 12:00 UTC (PDT) or 13:00 UTC (PST)
# Run every hour Tue-Wed UTC to cover both; script checks Pacific time
- cron: '1 * * * 2,3'
workflow_dispatch:
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r golly-notifier/requirements.txt
- name: Run Star Cup notifier
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.GOLLY_NOTIFIER_CHANNEL_ID }}
run: python golly-notifier/notifier.py star

View File

@@ -4,13 +4,9 @@ Scheduled bots that post content into Slack channels. One app, multiple personas
## Structure
Each channel lives in its own subdirectory:
Each notifier (slack tv channel) lives in its own subdirectory.
```
waste-land/ # Posts one line of The Waste Land per hour
```
A channel directory contains a `notifier.py` (what to post and how the bot appears), an `icon.png` (bot avatar), and a `poem.txt` or equivalent source file.
A notifier directory contains a `notifier.py` (what to post and how the bot appears), an `icon.png` (bot avatar), and any supplementary files.
## Setup
@@ -19,6 +15,5 @@ See [SETUP.md](SETUP.md) for Slack app creation, GitHub secrets, and how to add
## Slack TV Channel Directory
- `waste-land/` - **The Waste Land, One Line Per Hour** — Eliot's poem is 434 lines. At one line per hour, it takes ~18 days to complete, then loops. The line-per-hour pace is critical: each line sits alone in the channel long enough to be read as a standalone fragment, which is how the poem actually works — collage, juxtaposition, voices interrupting each other. The footnotes post as threaded replies.
- `paradise-lost/` - **Paradise Lost, Two Lines Per Hour** — Paradise Lost, Book I through Book XII, one line every 30 minutes (:15 and :45).
- `dactylic-odyssey/` - **The Odyssey in Dactylic Drip** — Post 5 lines of the Fagles (or Lattimore, or Fitzgerald) translation every 4 hours. The Odyssey is ~12,110 lines. At 30 lines/day, the full poem takes ~403 days — just over a year. A 5-line chunk is roughly one complete Homeric image or action beat. The year-long duration mirrors the scale of the journey itself. When it finishes, start the Iliad.
- `golly-notifier/` - **Golly Postseason Notifications** — Tracks the [Golly](https://golly.life) postseason and posts game results, series updates, and schedule notifications. Two bot personas: **Star Cup** (TuesdayWednesday) and **Hellmouth Cup** (FridaySunday). Each checks the Golly API hourly during its window, reporting series starts, daily scores with replay links, series clinches, and cup winners. Scheduling: Star runs 5 AM1 AM PT Tue, 5 AM5 PM PT Wed; Hellmouth runs 8 AM4 PM PT Fri, 8 AM8 PM PT SatSun.

View File

@@ -1,37 +0,0 @@
I want to create a notifier kind of like @waste-land but with more customized
logic. instead of sending notifications that are lines of a poem sent one
per hour, we are going to check an API during a specific time slot.
we are going to have a star cup notifier and a hellmouth cup notifier. Star
cup notifier runs Tuesday and Wednesday, hellmouth cup notifier runs Friday,
Saturday, Sunday. Star Cup notifier will start at 5 AM Pacific, Hellmouth Cup
notifier will start at 8 AM Pacific.
Both notifiers will hit an API /mode endpoint that will indicate the state of
the game simulator. the mode shifts from 21 (waiting for round 1 series of
postseason to begin), to 31 (round 1 series of postseason ongoing), to 22
(waiting for round 2 series of postseason to begin), to 32 (round 2 series of
postseason ongoing), to 23 (waiting for final cup series of postseason to
begin), to 33 (final cup series of postseason is ongoing), to 40 (postseason
is over).
The procedure for the notifications will work as follows.
at the top of each hour (minute 1) during each specified window:
- check the mode
- if the mode is 31, 32, or 33, the series is ongoing.
- if the game description has "Game 1" in the title, it is the notification of the series. send a notification
that says "The XYZ Series is starting now!"
- if the game description has any other non-1 game number in the title, the series is ongoing. send a
notification that has yesterday's game outcome/scores.
- for each day, if the series is ongoing, and a matchup occurred yesterday but not today, determine who won
the series, and the notification should announce who won the series.
- if the mode is 21, the postseason has not started yet, and there is nothing to do.
- if the mode is 22, and it is Friday, check if less than one hour has elapsed, if so, announce the outcome of
round 1 of the postseason
- if the mode is 22, and it is Saturday, there is nothing to do.
- if the mode is 23, and it is Saturday, check if less than one hour has elapsed, if so, announce the outcome of
round 2 of the postseason
- if the mode is 23, and it is Sunday, there is nothing to do.
- if the mode is 40, and less than one hour has elapsed, announce the outcome of the final cup series.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -1,53 +0,0 @@
star notifications:
- loop for League Division Series and League Championship Series starts running at 5 AM Pacific Tuesday, runs every hour for 20 hours
- loop for Star Cup Series starts running at 5 AM Pacific Wednesday, runs every hour for 12 hours
hellmouth notifications:
- loop for League Division Series starts running at 10 AM Pacific Friday, runs every hour for 8 hours
- loop for League Championship Series starts running at 8 AM Pacific Saturday, runs every hour for 12 hours
- loop for Hellmouth Cup Series starts running at 8 AM Pacific Sunday, runs every hour for 12 hours
general notification rules:
- if mode 21:
- do nothing
- if mode 31:
- if it has been mode 31 for less than 3600 seconds, send a notification that the League Division Series is starting
- otherwise, print two notifications:
- zeroth, determine how many hours have elapsed. that is the zero-indexed Day. you must report the one-indexed Day in the notification.
- for example, if 3700 seconds have elapsed since mode 31, it is Day 2.
- first, determine the outcome of the game from the prior day. create a notification with that score (plus links to those games).
- second, post a notification about the ongoing games for the current day (plus links to those games).
- if mode 22:
- if it has been mode 22 for less than 3600 seconds, send a notification of who won the League Division Series
- otherwise, do not send any notification at all
- if mode 32:
- if it has been mode 32 for less than 3600 seconds, send a notification that the League Championship Series is starting
- otherwise, print two notifications:
- zeroth, determine how many hours have elapsed. that is the zero-indexed Day. you must report the one-indexed Day in the notification.
- for example, if 3700 seconds have elapsed since mode 32, it is Day 2.
- first, determine the outcome of the game from the prior day. create a notification with that score (plus links to those games).
- second, post a notification about the ongoing games for the current day (plus links to those games).
- if mode 23:
- if it has been mode 23 for less than 3600 seconds, send a notification of who won the League Championship Series
- otherwise, do not send any notification at all
- if mode 33:
- if it has been mode 33 for less than 3600 seconds, send a notification that the Star VII Cup Series is starting
- otherwise, send two notifications:
- zeroth, determine how many hours have elapsed. that is the zero-indexed Day. you must report the one-indexed Day in the notification.
- for example, if 3700 seconds have elapsed since mode 33, it is Day 2.
- first, determine the outcome of the game from the prior day. create a notification with that score (plus links to those games).
- second, post a notification about the ongoing games for the current day (plus links to those games).
- if mode 40:
- if it has been mode 40 for less than 3600 seconds, send a notification of who won the Cup Series
- otherwise, do not send any notification at all

View File

@@ -1,528 +0,0 @@
import os
import sys
import re
import requests
from datetime import datetime
import pytz
SLACK_API_URL = "https://slack.com/api/chat.postMessage"
CUPS = {
"star": {
"api_base": "https://cloud.star.vii.golly.life",
"site_base": "https://star.vii.golly.life",
"cup_name": "Star Cup",
"cup_series_key": "SCS",
"bot_username": "Star Cup",
"bot_icon_url": "https://git.charlesreid1.com/charlesreid1/slack-tv/raw/branch/main/golly-notifier/star-cup-icon.png",
# weekday(): 0=Mon, 1=Tue, 2=Wed, 3=Thu, 4=Fri, 5=Sat, 6=Sun
"schedule": {
1: {"start_hour": 5, "duration": 20}, # Tuesday: LDS+LCS, 5 AM for 20h
2: {"start_hour": 5, "duration": 12}, # Wednesday: Cup Series, 5 AM for 12h
},
},
"hellmouth": {
"api_base": "https://cloud.vii.golly.life",
"site_base": "https://vii.golly.life",
"cup_name": "Hellmouth Cup",
"cup_series_key": "HCS",
"bot_username": "Hellmouth Cup",
"bot_icon_url": "https://git.charlesreid1.com/charlesreid1/slack-tv/raw/branch/main/golly-notifier/hellmouth-cup-icon.png",
# weekday(): 0=Mon, 1=Tue, 2=Wed, 3=Thu, 4=Fri, 5=Sat, 6=Sun
"schedule": {
4: {"start_hour": 8, "duration": 8}, # Friday: LDS, 8 AM for 8h
5: {"start_hour": 8, "duration": 12}, # Saturday: LCS, 8 AM for 12h
6: {"start_hour": 8, "duration": 12}, # Sunday: Cup Series, 8 AM for 12h
},
},
}
SERIES_NAMES = {
"LDS": "Division Series",
"LCS": "Championship Series",
"HCS": "Hellmouth VII Cup",
"SCS": "Star VII Cup",
}
MODE_TO_SERIES = {
31: "LDS",
32: "LCS",
33: None, # filled per-cup: HCS or SCS
}
# ---------------------------------------------------------------------------
# Slack helpers
# ---------------------------------------------------------------------------
def fetch_json(url):
resp = requests.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
def send_slack_message(token, channel, text, username, icon_url, attachments=None):
payload = {
"channel": channel,
"text": text,
"username": username,
"icon_url": icon_url,
}
if attachments:
payload["attachments"] = attachments
response = requests.post(
SLACK_API_URL,
headers={"Authorization": f"Bearer {token}"},
json=payload,
timeout=10,
)
response.raise_for_status()
data = response.json()
if not data.get("ok"):
raise RuntimeError(f"Slack API error: {data.get('error')}")
return data
def game_link(site_base, game):
game_id = game.get("id", "")
desc = game.get("description", "")
match = re.match(r"Game (\d+)", desc)
label = f"Game {match.group(1)}" if match else "Watch"
url = f"{site_base}/simulator/index.html?gameId={game_id}"
return f"<{url}|{label}>"
def game_link_url(site_base, game):
game_id = game.get("id", "")
return f"{site_base}/simulator/index.html?gameId={game_id}"
def game_link_label(game):
desc = game.get("description", "")
match = re.match(r"Game (\d+)", desc)
return f"Game {match.group(1)}" if match else "View Simulation"
# ---------------------------------------------------------------------------
# Block Kit card builders (mimic vii.golly.life game card style)
#
# Design reference (from the site's HTML/CSS):
# - Dark card background (#272b30, Bootswatch Slate)
# - Team 1 name + (W-L) record, score right-aligned
# - Team 2 name + (W-L) record, score right-aligned
# - Winner is bold, loser is normal weight
# - Map name + generation count below
# - Green "View Simulation" button (#62c462)
# - Sidebar color = winning team's color
#
# Slack constraints:
# - No colored text in Block Kit, so we use *bold* for the winner
# - Colored sidebar via attachment "color" field
# - Section blocks with fields for the two-column team/score layout
# ---------------------------------------------------------------------------
def _finished_game_attachment(g, site_base=None):
"""Build a Slack attachment with Block Kit blocks for a finished game.
Mirrors the vii.golly.life finished-game-card layout:
Team 1 Name (W-L) score
Team 2 Name (W-L) score
Map: <map name>
<generations> Generations
[View Simulation]
"""
t1_score = g.get("team1Score", 0)
t2_score = g.get("team2Score", 0)
t1_color = g.get("team1Color", "#aaaaaa")
t2_color = g.get("team2Color", "#aaaaaa")
# Determine winner for bold styling and sidebar color
if t1_score > t2_score:
winner_color = t1_color
t1_fmt = "*{name}*"
t2_fmt = "{name}"
else:
winner_color = t2_color
t1_fmt = "{name}"
t2_fmt = "*{name}*"
# Series win-loss records (pre-game, as shown on the card)
w1, l1 = g.get("team1SeriesWinLoss", [0, 0])
w2, l2 = g.get("team2SeriesWinLoss", [0, 0])
t1_record = f" ({w1}-{l1})" if w1 or l1 else ""
t2_record = f" ({w2}-{l2})" if w2 or l2 else ""
t1_label = t1_fmt.format(name=g["team1Name"]) + t1_record
t2_label = t2_fmt.format(name=g["team2Name"]) + t2_record
map_name = g.get("mapName", "Unknown")
generations = g.get("generations", 0)
blocks = [
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": t1_label},
{"type": "mrkdwn", "text": f"*{t1_score}*" if t1_score > t2_score else str(t1_score)},
],
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": t2_label},
{"type": "mrkdwn", "text": f"*{t2_score}*" if t2_score > t1_score else str(t2_score)},
],
},
{
"type": "context",
"elements": [
{"type": "mrkdwn", "text": f"Map: {map_name}"},
{"type": "mrkdwn", "text": f"{generations} Generations"},
],
},
]
if site_base:
url = game_link_url(site_base, g)
label = "View Simulation"
blocks.append({
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": label},
"url": url,
"style": "primary",
},
],
})
# Plain text fallback for notifications / non-Block-Kit clients
fallback = (
f"{g['team1Name']} {t1_score} - {g['team2Name']} {t2_score} "
f"| Map: {map_name} | {generations} Gen"
)
return {
"color": winner_color,
"blocks": blocks,
"fallback": fallback,
}
def _upcoming_game_attachment(g, site_base=None):
"""Build a Slack attachment for an upcoming/in-progress game (no scores yet).
Mirrors the vii.golly.life scheduled/in-progress game card:
Team 1 Name (W-L)
Team 2 Name (W-L)
Map: <map name>
[View Simulation]
"""
w1, l1 = g.get("team1SeriesWinLoss", [0, 0])
w2, l2 = g.get("team2SeriesWinLoss", [0, 0])
t1_record = f" ({w1}-{l1})" if w1 or l1 else ""
t2_record = f" ({w2}-{l2})" if w2 or l2 else ""
t1_label = g["team1Name"] + t1_record
t2_label = g["team2Name"] + t2_record
map_name = g.get("mapName", "Unknown")
blocks = [
{
"type": "section",
"text": {"type": "mrkdwn", "text": f"*{t1_label}*\n*{t2_label}*"},
},
{
"type": "context",
"elements": [
{"type": "mrkdwn", "text": f"Map: {map_name}"},
],
},
]
if site_base:
url = game_link_url(site_base, g)
blocks.append({
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "View Simulation"},
"url": url,
"style": "primary",
},
],
})
fallback = f"{g['team1Name']} vs. {g['team2Name']} | Map: {map_name}"
# Use team1's color for the sidebar on upcoming games
sidebar_color = g.get("team1Color", "#3a3f44")
return {
"color": sidebar_color,
"blocks": blocks,
"fallback": fallback,
}
def _header_attachment(header_text):
"""Simple styled header as an attachment (for series titles, day headers)."""
return {
"color": "#3a3f44",
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": f"*{header_text}*"},
},
],
"fallback": header_text,
}
def _series_clinch_attachment(winner, winner_wl, loser, loser_wl, winner_color):
"""Styled announcement that a team has won the series."""
text = f"*{winner}* ({winner_wl}) wins the series over {loser} ({loser_wl})"
return {
"color": winner_color,
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": text},
},
],
"fallback": f"{winner} ({winner_wl}) wins the series over {loser} ({loser_wl})",
}
# ---------------------------------------------------------------------------
# Notification builders
#
# Each returns a list of message dicts: {"text": str, "attachments": list}
# A single Slack API call sends one message dict.
# ---------------------------------------------------------------------------
def format_matchup(g, site_base=None):
"""Build a single attachment for a finished game result."""
return _finished_game_attachment(g, site_base)
def format_current_games(current_games, site_base=None):
"""Build a list of attachments for today's upcoming/in-progress games."""
attachments = []
for g in sorted(current_games, key=lambda x: x.get("description", "")):
attachments.append(_upcoming_game_attachment(g, site_base))
return attachments
def updated_series_wl(game):
"""Return (team1_wins, team1_losses, team2_wins, team2_losses) accounting for this game's outcome."""
w1, l1 = game["team1SeriesWinLoss"]
w2, l2 = game["team2SeriesWinLoss"]
if game["team1Score"] > game["team2Score"]:
w1 += 1
l2 += 1
else:
w2 += 1
l1 += 1
return w1, l1, w2, l2
def build_notification(cup_config, mode_data, postseason, current_games=None):
"""Build styled Slack messages for the current game state.
Returns a list of message dicts, each with:
- "text": plain-text fallback
- "attachments": list of Slack attachments with Block Kit blocks
"""
mode = mode_data["mode"]
elapsed = mode_data.get("elapsed", 0)
cup_name = cup_config["cup_name"]
cup_series_key = cup_config["cup_series_key"]
site_base = cup_config.get("site_base", "")
just_entered = elapsed < 3600
if mode == 21:
return []
if mode in (31, 32, 33):
if mode == 33:
series_key = cup_series_key
else:
series_key = MODE_TO_SERIES[mode]
series_name = SERIES_NAMES.get(series_key, series_key)
# hours elapsed = zero-indexed day; report one-indexed day
# e.g., 3700s elapsed -> 3700//3600 = 1 -> Day 2
series_day = (elapsed // 3600) + 1
series_data = postseason.get(series_key, [])
if just_entered:
site_link = f"<{site_base}|{site_base.replace('https://', '')}>" if site_base else ""
header_text = f"{cup_name}: {series_name} is starting now! {site_link}".strip()
attachments = [_header_attachment(header_text)]
if current_games:
attachments.extend(format_current_games(current_games, site_base))
return [{"text": header_text, "attachments": attachments}]
messages = []
# Yesterday's results: use day-based index (series_day - 2 for 0-indexed prior day)
yesterday_idx = series_day - 2
if series_data and 0 <= yesterday_idx < len(series_data):
yesterday_games = series_data[yesterday_idx]
header = f"{series_name} \u2014 Day {series_day - 1} Results"
result_attachments = [_header_attachment(header)]
for g in yesterday_games:
result_attachments.append(format_matchup(g, site_base))
today_pairs = set()
if current_games:
for g in current_games:
pair = tuple(sorted([g["team1Name"], g["team2Name"]]))
today_pairs.add(pair)
for g in yesterday_games:
pair = tuple(sorted([g["team1Name"], g["team2Name"]]))
if pair not in today_pairs:
w1, l1, w2, l2 = updated_series_wl(g)
if w1 > w2:
winner, winner_wl = g["team1Name"], f"{w1}-{l1}"
loser, loser_wl = g["team2Name"], f"{w2}-{l2}"
winner_color = g.get("team1Color", "#62c462")
else:
winner, winner_wl = g["team2Name"], f"{w2}-{l2}"
loser, loser_wl = g["team1Name"], f"{w1}-{l1}"
winner_color = g.get("team2Color", "#62c462")
result_attachments.append(
_series_clinch_attachment(winner, winner_wl, loser, loser_wl, winner_color)
)
messages.append({"text": header, "attachments": result_attachments})
if current_games:
header = f"{series_name} \u2014 Day {series_day} Games"
upcoming_attachments = [_header_attachment(header)]
upcoming_attachments.extend(format_current_games(current_games, site_base))
messages.append({"text": header, "attachments": upcoming_attachments})
return messages
if mode == 22:
if not just_entered:
return []
result = announce_series_outcome(postseason, "LDS", "Division Series")
return [result] if result else []
if mode == 23:
if not just_entered:
return []
result = announce_series_outcome(postseason, "LCS", "Championship Series")
return [result] if result else []
if mode == 40:
if not just_entered:
return []
result = announce_series_outcome(postseason, cup_series_key, cup_name)
return [result] if result else []
return []
def announce_series_outcome(postseason, series_key, series_name):
"""Build a styled message announcing the final outcome of a series."""
series_data = postseason.get(series_key, [])
if not series_data:
return None
last_day = series_data[-1]
header = f"{series_name} Results"
attachments = [_header_attachment(header)]
for game in last_day:
w1, l1, w2, l2 = updated_series_wl(game)
if w1 > w2:
winner, winner_wl = game["team1Name"], f"{w1}-{l1}"
loser, loser_wl = game["team2Name"], f"{w2}-{l2}"
winner_color = game.get("team1Color", "#62c462")
else:
winner, winner_wl = game["team2Name"], f"{w2}-{l2}"
loser, loser_wl = game["team1Name"], f"{w1}-{l1}"
winner_color = game.get("team2Color", "#62c462")
attachments.append(
_series_clinch_attachment(winner, winner_wl, loser, loser_wl, winner_color)
)
return {"text": header, "attachments": attachments}
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def run(cup_key):
token = os.getenv("SLACK_BOT_TOKEN")
channel = os.getenv("SLACK_CHANNEL_ID")
if not token or not channel:
print("SLACK_BOT_TOKEN and SLACK_CHANNEL_ID must be set. Skipping.")
return
if cup_key not in CUPS:
print(f"Unknown cup: {cup_key}. Must be 'star' or 'hellmouth'.")
return
cup = CUPS[cup_key]
pacific_tz = pytz.timezone("America/Los_Angeles")
now_pt = datetime.now(pacific_tz)
print(f"[{now_pt.strftime('%Y-%m-%d %H:%M PT')}] Running {cup['cup_name']} notifier")
weekday = now_pt.weekday()
schedule = cup.get("schedule", {})
if weekday not in schedule:
print(f" Not a {cup['cup_name']} day (weekday={weekday}). Skipping.")
return
day_config = schedule[weekday]
start_hour = day_config["start_hour"]
duration = day_config["duration"]
end_hour = start_hour + duration
if now_pt.hour < start_hour:
print(f" Too early (hour={now_pt.hour}, start={start_hour}). Skipping.")
return
if now_pt.hour >= end_hour:
print(f" Past window (hour={now_pt.hour}, end={end_hour}). Skipping.")
return
api_base = cup["api_base"]
mode_data = fetch_json(f"{api_base}/mode")
print(f" Mode: {mode_data}")
postseason = fetch_json(f"{api_base}/postseason")
print(f" Postseason series keys: {list(postseason.keys())}")
current_games = fetch_json(f"{api_base}/currentGames")
print(f" Current games: {len(current_games)}")
messages = build_notification(cup, mode_data, postseason, current_games)
if messages:
for i, msg in enumerate(messages, 1):
text = msg["text"]
attachments = msg.get("attachments")
print(f" Sending message {i}/{len(messages)}: {text[:80]}")
send_slack_message(token, channel, text, cup["bot_username"], cup["bot_icon_url"], attachments)
print(f" Sent message {i}.")
else:
print(" No notification to send.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python notifier.py <star|hellmouth>")
sys.exit(1)
run(sys.argv[1])

View File

@@ -1,2 +0,0 @@
requests==2.32.3
pytz==2024.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB