KEriks 65cf45e600 Granularity
changes

Loop fixers
2021-08-31 17:37:37 +03:00

168 lines
9.4 KiB
Python

import asyncio
import datetime
import time
import discord
import feedparser
import pytz
import requests
from erepublik.constants import COUNTRIES
from dbot.base import ADMIN_ID, DB, DEFAULT_CHANNEL_ID, LOOP, PRODUCTION, logger
from dbot.constants import events
from dbot.utils import check_battles, get_battle_page, timestamp
class DiscordClient(discord.Client):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.last_event_timestamp = timestamp()
async def on_ready(self):
logger.info("Client running")
logger.info("------")
# create the background task and run it in the background
self.bg_task = self.loop.create_task(self.report_battle_events())
self.bg_rss_task = self.loop.create_task(self.report_rss_events())
async def on_error(self, event_method, *args, **kwargs):
logger.warning(f"Ignoring exception in {event_method}")
async def send_msg(self, channel_id, *args, **kwargs):
if PRODUCTION:
return self.get_channel(channel_id).send(*args, **kwargs)
else:
return logger.debug(f"Sending message to: {channel_id}\nArgs: {args}\nKwargs{kwargs}")
async def report_rss_events(self):
await self.wait_until_ready()
feed_response = None
while not self.is_closed():
try:
for country in COUNTRIES.values():
latest_ts = DB.get_rss_feed_timestamp(country.id)
rss_link = f"https://www.erepublik.com/en/main/news/military/all/{country.link}/1/rss"
feed_response = requests.get(rss_link)
feed_response.raise_for_status()
for entry in reversed(feedparser.parse(feed_response.text).entries):
entry_ts = time.mktime(entry["published_parsed"])
entry_link = entry["link"]
# Check if event timestamp is after latest processed event for country
if entry_ts > latest_ts:
DB.set_rss_feed_timestamp(country.id, entry_ts)
title = text = ""
msg = entry["summary"]
dont_send = False
for kind in events:
match = kind.regex.search(msg)
if match:
values = match.groupdict()
# Special case for Dictator/Liberation wars
if "invader" in values and not values["invader"]:
values["invader"] = values["defender"]
# Special case for resource concession
if "link" in values:
__link = values["link"]
entry_link = __link if __link.startswith("http") else f"https://www.erepublik.com{__link}"
logger.debug(kind.format.format(**dict(match.groupdict(), **{"current_country": country.name})))
logger.debug(entry_link)
is_latvia = country.id == 71
has_latvia = any("Latvia" in v for v in values.values())
if is_latvia or has_latvia:
text = kind.format.format(**dict(match.groupdict(), **{"current_country": country.name}))
title = kind.name
else:
dont_send = True
break
else:
logger.warning(f"Unable to parse: {str(entry)}")
continue
if dont_send:
continue
entry_datetime = datetime.datetime.fromtimestamp(entry_ts, pytz.timezone("US/Pacific"))
embed = discord.Embed(title=title, url=entry_link, description=text)
embed.set_author(name=country.name, icon_url=f"https://www.erepublik.com/images/flags/L/{country.link}.gif")
embed.set_footer(text=f"{entry_datetime.strftime('%F %T')} (eRepublik time)")
logger.debug(f"Message sent: {text}")
for channel_id in DB.get_kind_notification_channel_ids("events"):
await self.get_channel(channel_id).send(embed=embed)
except Exception as e:
logger.error("eRepublik event reader ran into a problem!", exc_info=e)
try:
with open(f"debug/{timestamp()}.rss", "w") as f:
f.write(feed_response.text)
except (NameError, AttributeError):
logger.error("There was no Response object!", exc_info=e)
finally:
await asyncio.sleep((timestamp() // 300 + 1) * 300 - timestamp())
async def report_battle_events(self):
await self.wait_until_ready()
while not self.is_closed():
try:
r = get_battle_page()
if not isinstance(r.get("battles"), dict):
sleep_seconds = r.get("last_updated") + 60 - timestamp()
await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0)
continue
desc = "'Empty' medals are being guessed based on the division wall. Expect false-positives!"
empty_divisions = {
1: discord.Embed(title="Possibly empty **__last-minute__ D1** medals", description=desc),
2: discord.Embed(title="Possibly empty **__last-minute__ D2** medals", description=desc),
3: discord.Embed(title="Possibly empty **__last-minute__ D3** medals", description=desc),
4: discord.Embed(title="Possibly empty **__last-minute__ D4** medals", description=desc),
11: discord.Embed(title="Possibly empty **__last-minute__ Air** medals", description=desc),
}
for kind, div, data in check_battles(r.get("battles")):
if kind == "epic" and not DB.check_epic(data["div_id"]):
embed = discord.Embed(
title=" ".join(data["extra"]["intensity_scale"].split("_")).title(),
url=data["url"],
description=f"Epic battle {' vs '.join(data['sides'])}!\nBattle for {data['region']}, Round {data['zone_id']}",
)
embed.set_footer(text=f"Round time {data['round_time']}")
for channel_id in DB.get_kind_notification_channel_ids("epic"):
role_id = DB.get_role_id_for_channel_division(kind="epic", channel_id=channel_id, division=div)
logger.info(f"Sending epic d{div} message to {channel_id}, role to mention {role_id=}")
if role_id:
await self.get_channel(channel_id).send(f"<@&{role_id}> epic battle detected!", embed=embed)
else:
await self.get_channel(channel_id).send(embed=embed)
DB.add_epic(data["div_id"])
logger.info(f"{data['div_id']} added to notified list")
if kind == "empty" and data["round_time_s"] >= 85 * 60 and not DB.check_empty_medal(data["div_id"]):
if len(empty_divisions[div]) < 10:
empty_divisions[div].add_field(
name=f"**Battle for {data['region']} {' '.join(data['sides'])}**",
value=f"[R{data['zone_id']} | Time {data['round_time']}]({data['url']})",
)
DB.add_empty_medal(data["div_id"])
for d, e in empty_divisions.items():
if e.fields:
for channel_id in DB.get_kind_notification_channel_ids("empty"):
logger.info(f"Sending empty {d} message to {channel_id} with {len(e.fields)} battles: {e.fields}")
if role_id := DB.get_role_id_for_channel_division(kind="empty", channel_id=channel_id, division=d):
await self.get_channel(channel_id).send(f"<@&{role_id}> empty medals in late rounds!", embed=e)
else:
await self.get_channel(channel_id).send(embed=e)
sleep_seconds = r.get("last_updated") + 60 - timestamp()
await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0)
except Exception as e:
logger.error("Discord bot's eRepublik epic watcher died!", exc_info=e)
try:
with open(f"debug/{timestamp()}.json", "w") as f:
f.write(f"{r}")
except NameError:
logger.error("There was no Response object!", exc_info=e)
await asyncio.sleep(10)
await self.get_channel(DEFAULT_CHANNEL_ID).send(f"<@{ADMIN_ID}> I've stopped, please restart")
client = DiscordClient(loop=LOOP)