async def control_register(ctx, *args): if " ".join(args) == "From AF With Love!": DB.update_member(ctx.author.id, str(ctx.author), True) return await ctx.send( "✅ You have been registered and are allowed to issue commands privately! 🥳" ) return await ctx.send(MESSAGES["command_failed"])
async def control_mention_set(ctx, kind: str, division: str, role: str): for guild_role in ctx.guild.roles: if guild_role.mention == role: if not guild_role.mentionable: return await ctx.send( f"❌ Unable to use {role=}, because this role is not globally mentionable!" ) DB.add_role_mapping_entry(kind, ctx.channel.id, DIVISION_MAPPING[division], guild_role.id) return await ctx.send( f"✅ Success! For {division} epics I will mention {guild_role.mention}" ) return await ctx.send(MESSAGES["command_failed"])
async def control_notify(ctx, kind): if kind == "epic": if DB.add_notification_channel(ctx.guild.id, ctx.channel.id, kind): return await ctx.send( MESSAGES["notifications_set"].format("epic battles")) elif kind == "events": if DB.add_notification_channel(ctx.guild.id, ctx.channel.id, kind): return await ctx.send( MESSAGES["notifications_set"].format("eLatvia's events")) elif kind == "empty": if DB.add_notification_channel(ctx.guild.id, ctx.channel.id, kind): return await ctx.send( MESSAGES["notifications_set"].format("empty medals")) return await ctx.send(MESSAGES["nothing_to_do"])
async def empty(ctx, division, minutes: int = 0): _process_member(ctx.message.author) if not (ctx.channel.id == 603527159109124096 or DB.get_member(ctx.message.author.id).get("pm_is_allowed")): return await ctx.send("Currently unavailable!") try: div = int(division) except ValueError: try: div = dict(D1=1, D2=3, D3=3, D4=4, Air=11)[division.title()] except (AttributeError, KeyError): return await ctx.send( "First argument must be a value from: 1, d1, 2, d2, 3, d3, 4, d4, 11, air!" ) s_div = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"}[div] embed = Embed( title=f"Possibly empty {s_div} medals", description= "'Empty' medals are being guessed based on the division wall. Expect false-positives!", ) for kind, div_div, data in check_battles(get_battle_page().get("battles")): if kind == "empty" and div_div == div and data[ "round_time_s"] >= minutes * 60: embed.add_field( name= f"**Battle for {data['region']} {' '.join(data['sides'])}**", value= f"[R{data['zone_id']} | Time {data['round_time']}]({data['url']})", ) if len(embed.fields) >= 10: return await ctx.send(embed=embed) if embed.fields: return await ctx.send(embed=embed) else: return await ctx.send(f"No empty {s_div} medals found")
async def control_order_set(ctx, battle_id, side): if not DB.get_battle_order(battle_id): side_id = None try: side_id = COUNTRIES[int(side)].id except (ValueError, KeyError): try: side_id = [ c for c in COUNTRIES.values() if side.lower() in repr(c).lower() ][0].id except IndexError: return await ctx.send(MESSAGES["command_failed"]) DB.set_battle_order(battle_id, side_id) return await ctx.send( f"✅ Order has been set! {COUNTIRES[side_id].name} must win") return await ctx.send(MESSAGES["nothing_to_do"])
async def control_unnotify(ctx, kind): if DB.remove_kind_notification_channel(kind, ctx.channel.id): if kind == "epic": return await ctx.send( MESSAGES["notifications_unset"].format("epic battles")) if kind == "events": return await ctx.send(MESSAGES["notifications_unset"].format( "eLatvia's notifications")) if kind == "empty": return await ctx.send( MESSAGES["notifications_unset"].format("empty medals")) return await ctx.send(MESSAGES["command_failed"])
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_data = dict( 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']}", footer=f"Round time {data['round_time']}", ) embed = discord.Embed.from_dict(embed_data) logger.debug(f"{embed_data=}") for channel_id in DB.get_kind_notification_channel_ids( "epic"): if role_id := DB.get_role_id_for_channel_division( kind="epic", channel_id=channel_id, division=div): 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"]) if kind == "empty" and data[ "round_time_s"] >= 85 * 60 and not DB.check_empty_medal( data["div_id"]): 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"): 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)
import feedparser import pytz import requests from constants import events from erepublik.constants import COUNTRIES from dbot.base import ADMIN_ID, DB, DB_NAME, DEFAULT_CHANNEL_ID, DISCORD_TOKEN, PRODUCTION, logger from dbot.bot_commands import bot from dbot.utils import check_battles, get_battle_page, timestamp if PRODUCTION: logger.warning("Production mode enabled!") logger.setLevel(logging.INFO) _ts = int(time.time()) for c_id in COUNTRIES.keys(): DB.set_rss_feed_timestamp(c_id, _ts) del _ts logger.debug( f"Active configs:\nDISCORD_TOKEN='{DISCORD_TOKEN}'\nDEFAULT_CHANNEL_ID='{DEFAULT_CHANNEL_ID}'\nADMIN_ID='{ADMIN_ID}'\nDB_NAME='{DB_NAME}'" ) class MyClient(discord.Client): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # create the background task and run it in the background self.last_event_timestamp = timestamp() async def on_ready(self): logger.info("Client running")
async def control_mention_remove(ctx, kind: str, division: str): if DB.remove_role_mapping(kind, ctx.channel.id, DIVISION_MAPPING[division]): return await ctx.send( f"✅ I won't mention here any role about {division} events!") return await ctx.send(MESSAGES["nothing_to_do"])
async def control(ctx: commands.Context, command: str, *args): _process_member(ctx.message.author) if command == "register": return await control_register(ctx, *args) if command in ["notify", "unnotify"]: if ctx.channel.type == ChannelType.private: return await ctx.send(MESSAGES["not_in_pm"]) if not ctx.author.guild_permissions.administrator: return await ctx.send(MESSAGES["not_admin"]) if not args: return await ctx.send( f"❌ Please provide what kind of notifications You would like to {'en' if command == 'notify' else 'dis'}able! Currently available: {', '.join(NOTIFICATION_KINDS)}" ) kind = str(args[0]).lower() if kind not in NOTIFICATION_KINDS: return await ctx.send( f'❌ Notification {kind=} is unknown! Currently available: {", ".join(NOTIFICATION_KINDS)}' ) if command == "notify": return await control_notify(ctx, kind) if command == "unnotify": return await control_unnotify(ctx, kind) if command == "mention": if ctx.channel.type == ChannelType.private: return await ctx.send(MESSAGES["not_in_pm"]) if not ctx.author.guild_permissions.administrator: return await ctx.send(MESSAGES["not_admin"]) if not args or not 3 <= len(args) <= 4: return await ctx.send( MESSAGES["mention_help"].format(command=command)) try: kind, action, division, *role = args if role: role = role[0] kind = str(kind).lower() if ctx.channel.id not in DB.get_kind_notification_channel_ids( kind): return await ctx.send(MESSAGES["only_registered_channels"]) if kind not in ("epic", "empty"): return await ctx.send( f"❌ {kind=} doesn't support division mentioning!") if action not in ("set", "remove"): return await ctx.send( MESSAGES["mention_help"].format(command=command)) action = str(action).lower() division = str(division).title() if division not in DIVISION_MAPPING: await ctx.send( f"❌ Unknown {division=}! Available divisions: {', '.join(d.title() for d in DIVISION_MAPPING.keys())}" ) return await ctx.send( MESSAGES["mention_info"].format(kind=kind)) if action == "set": return await control_mention_set(ctx, kind, division, role) if action == "remove": return await control_mention_remove(ctx, kind, division) except Exception as e: logger.warning(str(e), exc_info=e, stacklevel=3) return await ctx.send( MESSAGES["mention_help"].format(command=command)) if command == "exit": if ctx.author.id == ADMIN_ID: await ctx.send(f"{ctx.author.mention} Bye!") sys.exit(0) return await ctx.send(f"❌ Unknown {command=}!")
def _process_member(member): if not DB.get_member(member.id): DB.add_member(member.id, str(member))
async def control_order_unset(ctx, battle_id): if DB.delete_battle_order(battle_id): return await ctx.send(f"✅ Order has been unset!") return await ctx.send(MESSAGES["nothing_to_do"])