mirror of https://github.com/ekimekim/wubloader
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
5.4 KiB
Python
192 lines
5.4 KiB
Python
|
|
import json
|
|
import logging
|
|
import os
|
|
import socket
|
|
import time
|
|
|
|
from bs4 import BeautifulSoup
|
|
|
|
from .config import get_config
|
|
from .zulip import Client
|
|
|
|
import requests
|
|
session = requests.Session()
|
|
|
|
def stream(channels):
|
|
channels = ",".join(channels)
|
|
tt = 0
|
|
tr = 0
|
|
while True:
|
|
resp = session.get(f"https://ps8.pndsn.com/v2/subscribe/sub-cbd7f5f5-1d3f-11e2-ac11-877a976e347c/{channels}/0",
|
|
params={
|
|
"tt": tt,
|
|
"tr": tr,
|
|
},
|
|
)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
for msg in data.get("m", []):
|
|
yield msg
|
|
tt = data["t"]["t"]
|
|
tr = data["t"]["r"]
|
|
|
|
|
|
def get_current(channel):
|
|
resp = session.get(f"https://pubsub.pubnub.com/history/sub-cbd7f5f5-1d3f-11e2-ac11-877a976e347c/{channel}/0/1")
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
return data[0]
|
|
|
|
|
|
giveaway_cache = [None, None]
|
|
def get_giveaway():
|
|
REFRESH_RISING = 30
|
|
REFRESH_FALLING = 300
|
|
t, g = giveaway_cache
|
|
now = time.time()
|
|
timeout = REFRESH_RISING if g is None else REFRESH_FALLING
|
|
if t is None or now - t > timeout:
|
|
try:
|
|
g = _get_giveaway()
|
|
except Exception:
|
|
logging.warning("Failed to fetch giveaway", exc_info=True)
|
|
else:
|
|
giveaway_cache[0] = t
|
|
giveaway_cache[1] = g
|
|
return giveaway_cache[1]
|
|
|
|
|
|
def _get_giveaway():
|
|
resp = session.get("https://desertbus.org/2024/donate", headers={"User-Agent": ""})
|
|
resp.raise_for_status()
|
|
html = BeautifulSoup(resp.content.decode(), "html.parser")
|
|
island = html.find("astro-island", **{"component-export": "DonateForm"})
|
|
if island is None:
|
|
logging.warning("Could not find DonateForm astro-island in donate page")
|
|
return None
|
|
data = json.loads(island["props"])
|
|
giveaways = data["giveaways"][1]
|
|
if giveaways:
|
|
return giveaways[0][1]["amount"][1] / 100.
|
|
return None
|
|
|
|
|
|
prize_cache = {}
|
|
def get_prize_name(id):
|
|
if id not in prize_cache:
|
|
try:
|
|
prize_cache[id] = _get_prize_name(id)
|
|
except Exception:
|
|
logging.warning(f"Failed to get prize title for {id}", exc_info=True)
|
|
return "Unknown prize"
|
|
return prize_cache[id]
|
|
|
|
|
|
def _get_prize_name(id):
|
|
resp = requests.get(f"https://desertbus.org/2024/prize/{id}", {"User-Agent": ""})
|
|
resp.raise_for_status()
|
|
html = BeautifulSoup(resp.content.decode(), "html.parser")
|
|
div = html.body.main.find("div", **{"class": lambda cl: "text-brand-gold" in cl})
|
|
# These divs have format "Silent Auction: NAME", "Giveaway: NAME", etc. Split after first ": ".
|
|
return div.string.split(": ", 1)[-1].strip()
|
|
|
|
|
|
def main(conf_file, message_log_file, name=socket.gethostname()):
|
|
"""Config:
|
|
zulip_url
|
|
zulip_email
|
|
zulip_api_key
|
|
total_id: id for donation total channel
|
|
prize_ids: list of ids for prizes to watch bids for
|
|
"""
|
|
logging.basicConfig(level="INFO")
|
|
|
|
config = get_config(conf_file)
|
|
client = Client(config["zulip_url"], config["zulip_email"], config["zulip_api_key"])
|
|
|
|
message_log = open(message_log_file, "a")
|
|
def write_log(log):
|
|
message_log.write(json.dumps(log) + '\n')
|
|
message_log.flush()
|
|
|
|
write_log({
|
|
"type": "startup",
|
|
"host": name,
|
|
"pid": os.getpid(),
|
|
"time": time.time(),
|
|
})
|
|
|
|
total_channel = f"total:{config['total_id']}"
|
|
channels = [total_channel] + [
|
|
f"bid:{prize_id}" for prize_id in config["prize_ids"]
|
|
]
|
|
total = get_current(total_channel)
|
|
for msg in stream(channels):
|
|
log = {
|
|
"type": "unknown",
|
|
"host": name,
|
|
"pid": os.getpid(),
|
|
"time": time.time(),
|
|
"message": msg,
|
|
}
|
|
|
|
try:
|
|
try:
|
|
message_time = float(msg["p"]["t"]) / 10000000
|
|
except (KeyError, ValueError):
|
|
message_time = None
|
|
|
|
log["message_time"] = message_time
|
|
|
|
if msg["c"] == total_channel:
|
|
log["type"] = "total"
|
|
increase = None if total is None else msg["d"] - total
|
|
log["increase"] = increase
|
|
increase_str = "" if increase is None else " (+${:.2f})".format(msg["d"] - total)
|
|
giveaway_amount = get_giveaway()
|
|
entries_str = ""
|
|
if increase is not None and giveaway_amount is not None:
|
|
if (increase + 0.005) % giveaway_amount < 0.01:
|
|
entries = int((increase + 0.005) / giveaway_amount)
|
|
log["giveaway_amount"] = giveaway_amount
|
|
log["giveaway_entries"] = entries
|
|
entries_str = " ({} entries of ${:.2f})".format(entries, giveaway_amount)
|
|
logging.info("New donation total: {}{}{}".format(msg["d"], increase_str, entries_str))
|
|
total = msg["d"]
|
|
if increase is not None and increase > 0:
|
|
client.send_to_stream("bot-spam", "Donation Firehose", "Donation total is now ${:.2f}{}{}".format(msg["d"], increase_str, entries_str))
|
|
if increase is not None and increase >= 500:
|
|
client.send_to_stream("bot-spam", "Notable Donations", "Large donation of ${:.2f} (total ${:.2f}){}".format(increase, msg['d'], entries_str))
|
|
|
|
elif msg["c"].startswith("bid:"):
|
|
log["type"] = "prize"
|
|
prize_id = msg["c"].removeprefix("bid:")
|
|
log["prize_id"] = prize_id
|
|
prize_name = get_prize_name(prize_id)
|
|
log["prize_name"] = prize_name
|
|
data = msg["d"]
|
|
logging.info(f"Prize update for {prize_id}: {data}")
|
|
if "name" in data and "amount" in data:
|
|
log["bidder"] = data["name"]
|
|
amount = data["amount"] / 100
|
|
log["bid"] = amount
|
|
client.send_to_stream(
|
|
"bot-spam",
|
|
"Bids",
|
|
f"At <time:{message_time}>, {data['name']} ({data['donorID']}) has the high bid of ${amount:.2f} for prize [{prize_name}](https://desertbus.org/prize/{prize_id})",
|
|
)
|
|
|
|
else:
|
|
logging.warning("Unknown message: {}".format(msg))
|
|
except Exception:
|
|
logging.exception(f"Failed to handle message {msg}")
|
|
log["type"] = "error"
|
|
|
|
write_log(log)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argh
|
|
argh.dispatch_command(main)
|