Compare commits

...

7 Commits

Author SHA1 Message Date
Mike Lang a2403b56cd schedulebot: fix time zones 2 months ago
Mike Lang c53c5a98d5 pubbot: work in years besides 2024 2 months ago
Mike Lang 2c423f65fb prizebot: Fix year regex 2 months ago
Mike Lang 6b7ae95c1f prizebot: make it work for years besides 2024 2 months ago
Mike Lang 15f4767f32 fix another issue with the thing i yolo'd into prod 2 months ago
Mike Lang b24612d271 fix typo in prev commit 2 months ago
Mike Lang 67d351e549 cutter: Add mode where it only uploads rows with uploader_whitelist set
So you can have a "special" cutter that doesn't upload most videos
2 months ago

@ -123,7 +123,7 @@ class Cutter(object):
ERROR_RETRY_INTERVAL = 5 ERROR_RETRY_INTERVAL = 5
RETRYABLE_UPLOAD_ERROR_WAIT_INTERVAL = 5 RETRYABLE_UPLOAD_ERROR_WAIT_INTERVAL = 5
def __init__(self, upload_locations, dbmanager, stop, name, segments_path, tags): def __init__(self, upload_locations, dbmanager, stop, name, segments_path, tags, uploader_explicit_only=False):
"""upload_locations is a map {location name: upload location backend} """upload_locations is a map {location name: upload location backend}
Conn is a database connection. Conn is a database connection.
Stop is an Event triggering graceful shutdown when set. Stop is an Event triggering graceful shutdown when set.
@ -136,6 +136,7 @@ class Cutter(object):
self.stop = stop self.stop = stop
self.segments_path = segments_path self.segments_path = segments_path
self.tags = tags self.tags = tags
self.uploader_explicit_only = uploader_explicit_only
self.logger = logging.getLogger(type(self).__name__) self.logger = logging.getLogger(type(self).__name__)
self.refresh_conn() self.refresh_conn()
@ -241,14 +242,18 @@ class Cutter(object):
"""Return a list of all available candidates that we might be able to cut.""" """Return a list of all available candidates that we might be able to cut."""
# We only accept candidates if they haven't excluded us by whitelist, # We only accept candidates if they haven't excluded us by whitelist,
# and we are capable of uploading to their desired upload location. # and we are capable of uploading to their desired upload location.
uploader_condition = "(uploader_whitelist IS NULL OR %(name)s = ANY (uploader_whitelist))"
if self.uploader_explicit_only:
uploader_condition = "%(name)s = ANY (uploader_whitelist)"
built_query = sql.SQL(""" built_query = sql.SQL("""
SELECT id, {} SELECT id, {}
FROM events FROM events
WHERE state = 'EDITED' WHERE state = 'EDITED'
AND (uploader_whitelist IS NULL OR %(name)s = ANY (uploader_whitelist)) AND {}
AND upload_location = ANY (%(upload_locations)s) AND upload_location = ANY (%(upload_locations)s)
""").format( """).format(
sql.SQL(", ").join(sql.Identifier(key) for key in CUT_JOB_PARAMS) sql.SQL(", ").join(sql.Identifier(key) for key in CUT_JOB_PARAMS),
sql.SQL(uploader_condition),
) )
result = query(self.conn, built_query, name=self.name, upload_locations=list(self.upload_locations.keys())) result = query(self.conn, built_query, name=self.name, upload_locations=list(self.upload_locations.keys()))
return result.fetchall() return result.fetchall()
@ -870,6 +875,7 @@ def main(
tags='', tags='',
metrics_port=8003, metrics_port=8003,
backdoor_port=0, backdoor_port=0,
uploader_explicit_only=False,
): ):
"""dbconnect should be a postgres connection string, which is either a space-separated """dbconnect should be a postgres connection string, which is either a space-separated
list of key=value pairs, or a URI like: list of key=value pairs, or a URI like:
@ -960,7 +966,7 @@ def main(
if not no_updater: if not no_updater:
needs_updater[location] = backend needs_updater[location] = backend
cutter = Cutter(upload_locations, dbmanager, stop, name, base_dir, tags) cutter = Cutter(upload_locations, dbmanager, stop, name, base_dir, tags, uploader_explicit_only)
transcode_checkers = [ transcode_checkers = [
TranscodeChecker(location, backend, dbmanager, stop) TranscodeChecker(location, backend, dbmanager, stop)
for location, backend in needs_transcode_check.items() for location, backend in needs_transcode_check.items()

@ -344,6 +344,7 @@
pubbot:: { pubbot:: {
zulip_email: "blog-bot@chat.videostrike.team", zulip_email: "blog-bot@chat.videostrike.team",
zulip_api_key: "", zulip_api_key: "",
year: "2024",
# The id for this year's total # The id for this year's total
total_id: "RZZQRDQNLNLW", total_id: "RZZQRDQNLNLW",
# The ids of any prizes to watch # The ids of any prizes to watch
@ -353,6 +354,7 @@
prizebot:: { prizebot:: {
email: "blog-bot@chat.videostrike.team", email: "blog-bot@chat.videostrike.team",
api_key: "", api_key: "",
year: $.pubbot.year,
state: "/prizebot_state.json", state: "/prizebot_state.json",
// Path in host fs for the state file. // Path in host fs for the state file.
// Must exist and be initialized to "{}" // Must exist and be initialized to "{}"

@ -15,8 +15,8 @@ from .zulip import Client
Prize = namedtuple("Prize", ["id", "link", "type", "title", "state", "result"]) Prize = namedtuple("Prize", ["id", "link", "type", "title", "state", "result"])
def get_prizes(type): def get_prizes(year, type):
resp = requests.get(f"https://desertbus.org/2024/prizes/{type}", {"User-Agent": ""}) resp = requests.get(f"https://desertbus.org/{year}/prizes/{type}", {"User-Agent": ""})
resp.raise_for_status() resp.raise_for_status()
html = BeautifulSoup(resp.content.decode(), "html.parser") html = BeautifulSoup(resp.content.decode(), "html.parser")
@ -24,7 +24,7 @@ def get_prizes(type):
prizes = [] prizes = []
for a in main.find_all("a"): for a in main.find_all("a"):
# look for prize links # look for prize links
match = re.match("^/\d+/prize/([A-Z]+)$", a["href"]) match = re.match("^/[^/]+/prize/([A-Z]+)$", a["href"])
if not match: if not match:
continue continue
# skip image links # skip image links
@ -73,8 +73,10 @@ def main(config_file, test=False, all=False, once=False, interval=60):
""" """
Config: Config:
url, email, api_key: zulip creds url, email, api_key: zulip creds
year: the correct URL part for the prizes page: https://desertbus.org/YEAR/prizes/giveaway
state: path to state file state: path to state file
""" """
logging.basicConfig(level="INFO")
config = get_config(config_file) config = get_config(config_file)
with open(config['state']) as f: with open(config['state']) as f:
# state is {id: last seen state} # state is {id: last seen state}
@ -83,7 +85,7 @@ def main(config_file, test=False, all=False, once=False, interval=60):
while True: while True:
start = time.time() start = time.time()
for type in ('live', 'silent', 'giveaway'): for type in ('live', 'silent', 'giveaway'):
prizes = get_prizes(type) prizes = get_prizes(config['year'], type)
for prize in prizes: for prize in prizes:
logging.info(f"Got prize: {prize}") logging.info(f"Got prize: {prize}")
if prize.state == "sold" and (all or state.get(prize.id, "sold") != "sold"): if prize.state == "sold" and (all or state.get(prize.id, "sold") != "sold"):

@ -40,7 +40,7 @@ def get_current(channel):
giveaway_cache = [None, None] giveaway_cache = [None, None]
def get_giveaway(): def get_giveaway(year):
REFRESH_RISING = 30 REFRESH_RISING = 30
REFRESH_FALLING = 300 REFRESH_FALLING = 300
t, g = giveaway_cache t, g = giveaway_cache
@ -48,7 +48,7 @@ def get_giveaway():
timeout = REFRESH_RISING if g is None else REFRESH_FALLING timeout = REFRESH_RISING if g is None else REFRESH_FALLING
if t is None or now - t > timeout: if t is None or now - t > timeout:
try: try:
g = _get_giveaway() g = _get_giveaway(year)
except Exception: except Exception:
logging.warning("Failed to fetch giveaway", exc_info=True) logging.warning("Failed to fetch giveaway", exc_info=True)
else: else:
@ -57,8 +57,8 @@ def get_giveaway():
return giveaway_cache[1] return giveaway_cache[1]
def _get_giveaway(): def _get_giveaway(year):
resp = session.get("https://desertbus.org/2024/donate", headers={"User-Agent": ""}) resp = session.get(f"https://desertbus.org/{year}/donate", headers={"User-Agent": ""})
resp.raise_for_status() resp.raise_for_status()
html = BeautifulSoup(resp.content.decode(), "html.parser") html = BeautifulSoup(resp.content.decode(), "html.parser")
island = html.find("astro-island", **{"component-export": "DonateForm"}) island = html.find("astro-island", **{"component-export": "DonateForm"})
@ -73,18 +73,18 @@ def _get_giveaway():
prize_cache = {} prize_cache = {}
def get_prize_name(id): def get_prize_name(year, id):
if id not in prize_cache: if id not in prize_cache:
try: try:
prize_cache[id] = _get_prize_name(id) prize_cache[id] = _get_prize_name(year, id)
except Exception: except Exception:
logging.warning(f"Failed to get prize title for {id}", exc_info=True) logging.warning(f"Failed to get prize title for {id}", exc_info=True)
return "Unknown prize" return "Unknown prize"
return prize_cache[id] return prize_cache[id]
def _get_prize_name(id): def _get_prize_name(year, id):
resp = requests.get(f"https://desertbus.org/2024/prize/{id}", {"User-Agent": ""}) resp = requests.get(f"https://desertbus.org/{year}/prize/{id}", {"User-Agent": ""})
resp.raise_for_status() resp.raise_for_status()
html = BeautifulSoup(resp.content.decode(), "html.parser") html = BeautifulSoup(resp.content.decode(), "html.parser")
div = html.body.main.find("div", **{"class": lambda cl: "text-brand-gold" in cl}) div = html.body.main.find("div", **{"class": lambda cl: "text-brand-gold" in cl})
@ -97,6 +97,7 @@ def main(conf_file, message_log_file, name=socket.gethostname()):
zulip_url zulip_url
zulip_email zulip_email
zulip_api_key zulip_api_key
year
total_id: id for donation total channel total_id: id for donation total channel
prize_ids: list of ids for prizes to watch bids for prize_ids: list of ids for prizes to watch bids for
""" """
@ -104,6 +105,7 @@ def main(conf_file, message_log_file, name=socket.gethostname()):
config = get_config(conf_file) config = get_config(conf_file)
client = Client(config["zulip_url"], config["zulip_email"], config["zulip_api_key"]) client = Client(config["zulip_url"], config["zulip_email"], config["zulip_api_key"])
year = config["year"]
message_log = open(message_log_file, "a") message_log = open(message_log_file, "a")
def write_log(log): def write_log(log):
@ -144,7 +146,7 @@ def main(conf_file, message_log_file, name=socket.gethostname()):
increase = None if total is None else msg["d"] - total increase = None if total is None else msg["d"] - total
log["increase"] = increase log["increase"] = increase
increase_str = "" if increase is None else " (+${:.2f})".format(msg["d"] - total) increase_str = "" if increase is None else " (+${:.2f})".format(msg["d"] - total)
giveaway_amount = get_giveaway() giveaway_amount = get_giveaway(year)
entries_str = "" entries_str = ""
if increase is not None and giveaway_amount is not None: if increase is not None and giveaway_amount is not None:
if (increase + 0.005) % giveaway_amount < 0.01: if (increase + 0.005) % giveaway_amount < 0.01:
@ -163,7 +165,7 @@ def main(conf_file, message_log_file, name=socket.gethostname()):
log["type"] = "prize" log["type"] = "prize"
prize_id = msg["c"].removeprefix("bid:") prize_id = msg["c"].removeprefix("bid:")
log["prize_id"] = prize_id log["prize_id"] = prize_id
prize_name = get_prize_name(prize_id) prize_name = get_prize_name(year, prize_id)
log["prize_name"] = prize_name log["prize_name"] = prize_name
data = msg["d"] data = msg["d"]
logging.info(f"Prize update for {prize_id}: {data}") logging.info(f"Prize update for {prize_id}: {data}")

@ -7,6 +7,7 @@ import logging
import time import time
from calendar import timegm from calendar import timegm
from datetime import datetime, timedelta from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import gevent.pool import gevent.pool
import argh import argh
@ -75,7 +76,9 @@ def hour_to_shift(hour, start_time):
"""Converts an hour number into a datetime, shift number (0-3), shift name, and hour-of-shift (1-6)""" """Converts an hour number into a datetime, shift number (0-3), shift name, and hour-of-shift (1-6)"""
start_time = datetime.utcfromtimestamp(start_time) start_time = datetime.utcfromtimestamp(start_time)
current_time = (start_time + timedelta(hours=hour)).replace(minute=0, second=0, microsecond=0) current_time = (start_time + timedelta(hours=hour)).replace(minute=0, second=0, microsecond=0)
current_time_pst = current_time - timedelta(hours=8) utc = ZoneInfo("utc")
pst = ZoneInfo("America/Vancouver")
current_time_pst = current_time.replace(tzinfo=utc).astimezone(pst)
hour_pst = current_time_pst.hour hour_pst = current_time_pst.hour
shift = hour_pst // 6 shift = hour_pst // 6
shift_name = ["zeta", "dawn-guard", "alpha-flight", "night-watch"][shift] shift_name = ["zeta", "dawn-guard", "alpha-flight", "night-watch"][shift]
@ -217,7 +220,7 @@ def parse_schedule(sheets_client, user_ids, schedule_sheet_id, schedule_sheet_na
for row in raw_schedule: for row in raw_schedule:
name = row[0].lower() name = row[0].lower()
if name in ["", "Chat Member", "Hour of the Run"] or name.startswith("-") or name.startswith("["): if name in ["", "chat member", "hour of the run"] or name.startswith("-") or name.startswith("["):
continue continue
if name not in user_ids: if name not in user_ids:
logging.warning(f"No user id known for user {name}") logging.warning(f"No user id known for user {name}")

Loading…
Cancel
Save