Granularity
changes Loop fixers
This commit is contained in:
parent
fc7c57abe5
commit
65cf45e600
@ -10,4 +10,6 @@ __pycache__/
|
||||
*.py[cod]
|
||||
**/*.py[cod]
|
||||
|
||||
.git
|
||||
venv/
|
||||
|
||||
|
@ -9,8 +9,9 @@ RUN groupadd -g 1000 discordbot \
|
||||
&& chmod +x /run.sh
|
||||
|
||||
USER discordbot
|
||||
ENV PATH=$PATH:/home/discordbot/.local/bin
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
ENV PYTHONPATH=$PYTHONPATH:/app/dbot
|
||||
#CMD python discord_bot.py
|
||||
ENTRYPOINT ["/usr/local/bin/python", "/app/dbot/discord_bot.py"]
|
||||
ENTRYPOINT ["/usr/local/bin/python", "/app/dbot/main.py"]
|
||||
|
@ -1,8 +1,9 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from db import DiscordDB
|
||||
from dbot.db import DiscordDB
|
||||
|
||||
APP_NAME = "discord_bot"
|
||||
|
||||
@ -22,6 +23,7 @@ stream_logger = logging.StreamHandler()
|
||||
stream_logger.setLevel(logging.DEBUG)
|
||||
stream_logger.setFormatter(formatter)
|
||||
logger.addHandler(stream_logger)
|
||||
logger.propagate = False
|
||||
|
||||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
DEFAULT_CHANNEL_ID = os.getenv("DEFAULT_CHANNEL_ID", 603527159109124096)
|
||||
@ -29,6 +31,7 @@ ADMIN_ID = os.getenv("ADMIN_ID", 220849530730577920)
|
||||
DB_NAME = os.getenv("DB_NAME", "discord.db")
|
||||
PRODUCTION = bool(os.getenv("PRODUCTION"))
|
||||
DB = DiscordDB(DB_NAME)
|
||||
DB.load_base_data()
|
||||
|
||||
|
||||
MENTION_MAPPING = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"}
|
||||
@ -52,3 +55,5 @@ MESSAGES = dict(
|
||||
notifications_unset="✅ I won't notify about {} in this channel!",
|
||||
notifications_set="✅ I will notify about {} in this channel!",
|
||||
)
|
||||
|
||||
LOOP = loop = asyncio.get_event_loop()
|
||||
|
@ -3,13 +3,14 @@ import sys
|
||||
from discord import Embed
|
||||
from discord.enums import ChannelType
|
||||
from discord.ext import commands
|
||||
from erepublik.constants import COUNTRIES
|
||||
|
||||
from dbot.base import ADMIN_ID, DB, DIVISION_MAPPING, MESSAGES, NOTIFICATION_KINDS, logger
|
||||
from dbot.base import ADMIN_ID, DB, DIVISION_MAPPING, LOOP, MESSAGES, NOTIFICATION_KINDS, logger
|
||||
from dbot.utils import check_battles, get_battle_page
|
||||
|
||||
__all__ = ["bot"]
|
||||
__all__ = ["DiscordBot"]
|
||||
|
||||
bot = commands.Bot(command_prefix="!")
|
||||
bot = DiscordBot = commands.Bot(command_prefix="!", loop=LOOP)
|
||||
|
||||
|
||||
def _process_member(member):
|
||||
@ -75,12 +76,13 @@ async def control_order_set(ctx, battle_id, side):
|
||||
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(f"✅ Order has been set! {COUNTRIES[side_id].name} must win")
|
||||
return await ctx.send(MESSAGES["nothing_to_do"])
|
||||
|
||||
|
||||
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("✅ Order has been unset!")
|
||||
return await ctx.send(MESSAGES["nothing_to_do"])
|
||||
|
||||
|
||||
@ -89,6 +91,7 @@ async def control_order(ctx, action, *args):
|
||||
return await control_order_set(ctx, *args)
|
||||
return await ctx.send(MESSAGES["nothing_to_do"])
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
logger.info("Bot loaded")
|
||||
@ -203,4 +206,3 @@ async def control(ctx: commands.Context, command: str, *args):
|
||||
async def control_error(ctx, error):
|
||||
logger.exception(error, exc_info=error)
|
||||
return await ctx.send(MESSAGES["command_failed"])
|
||||
|
@ -1,41 +1,29 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
|
||||
import discord
|
||||
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.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
|
||||
|
||||
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):
|
||||
class DiscordClient(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")
|
||||
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())
|
||||
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}")
|
||||
@ -132,29 +120,33 @@ class MyClient(discord.Client):
|
||||
}
|
||||
for kind, div, data in check_battles(r.get("battles")):
|
||||
if kind == "epic" and not DB.check_epic(data["div_id"]):
|
||||
embed_data = dict(
|
||||
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']}",
|
||||
footer=f"Round time {data['round_time']}",
|
||||
)
|
||||
embed = discord.Embed.from_dict(embed_data)
|
||||
logger.debug(f"{embed_data=}")
|
||||
embed.set_footer(text=f"Round time {data['round_time']}")
|
||||
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):
|
||||
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']})"
|
||||
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:
|
||||
@ -172,18 +164,4 @@ class MyClient(discord.Client):
|
||||
await self.get_channel(DEFAULT_CHANNEL_ID).send(f"<@{ADMIN_ID}> I've stopped, please restart")
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
client = MyClient()
|
||||
|
||||
|
||||
def main():
|
||||
global loop
|
||||
logger.info("Starting Bot loop")
|
||||
loop.create_task(bot.start(DISCORD_TOKEN))
|
||||
logger.info("Starting Client loop")
|
||||
loop.create_task(client.start(DISCORD_TOKEN))
|
||||
loop.run_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
client = DiscordClient(loop=LOOP)
|
42
dbot/db.py
42
dbot/db.py
@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from erepublik.constants import COUNTRIES
|
||||
from sqlite_utils import Database
|
||||
from sqlite_utils.db import NotFoundError
|
||||
|
||||
@ -10,6 +12,7 @@ class DiscordDB:
|
||||
_db: Database
|
||||
|
||||
def __init__(self, db_name: str = ""):
|
||||
self.logger = logging.getLogger(self.__class__.__name__)
|
||||
self._db = Database(db_name) if db_name else Database(memory=True)
|
||||
|
||||
self.initialize()
|
||||
@ -31,12 +34,12 @@ class DiscordDB:
|
||||
self._db.create_table("member_tmp", {"name": str, "pm_is_allowed": bool}, pk="id", not_null={"name", "pm_is_allowed"}, defaults={"pm_is_allowed": False})
|
||||
for row in self._db.table("member").rows:
|
||||
self._db["member_tmp"].insert(row)
|
||||
logging.info(f"Moving row {row} to tmp member table")
|
||||
self.logger.info(f"Moving row {row} to tmp member table")
|
||||
self._db["member"].drop(True)
|
||||
self._db.create_table("member", {"name": str, "pm_is_allowed": bool}, pk="id", not_null={"name", "pm_is_allowed"}, defaults={"pm_is_allowed": False})
|
||||
for row in self._db.table("member_tmp").rows:
|
||||
self._db["member"].insert(row)
|
||||
logging.info(f"Moving row {row} from tmp member table")
|
||||
self.logger.info(f"Moving row {row} from tmp member table")
|
||||
self._db["member_tmp"].drop(True)
|
||||
|
||||
if "player" not in db_tables:
|
||||
@ -45,7 +48,7 @@ class DiscordDB:
|
||||
try:
|
||||
self._db.create_table("channel", {"guild_id": int, "channel_id": int, "kind": str}, pk="id", not_null={"guild_id", "channel_id", "kind"}, defaults={"kind": "epic"})
|
||||
self._db["channel"].create_index(("guild_id", "channel_id", "kind"), unique=True)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
for row in self._db.table("notification_channel").rows:
|
||||
self._db["channel"].insert(row)
|
||||
@ -53,17 +56,31 @@ class DiscordDB:
|
||||
self._db.create_table("role_mapping", {"channel_id": int, "division": int, "role_id": int}, pk="id", not_null={"channel_id", "division", "role_id"})
|
||||
self._db["role_mapping"].add_foreign_key("channel_id", "channel", "id")
|
||||
self._db["role_mapping"].create_index(("channel_id", "division"), unique=True)
|
||||
|
||||
else:
|
||||
for row in self._db["role_mapping"].rows:
|
||||
try:
|
||||
self._db["channel"].get(row["channel_id"])
|
||||
except NotFoundError:
|
||||
if any(self._db["channel"].rows_where("channel_id = ?", (row["channel_id"],))):
|
||||
self._db["role_mapping"].update(row["id"], {"channel_id": next(self._db["channel"].rows_where("channel_id = ?", (row["channel_id"],)))["id"]})
|
||||
else:
|
||||
self.logger.warning(f"RoleMapping contained unknown channel id {row['channel_id']}!")
|
||||
self.logger.warning(f"DELETED:{row=}")
|
||||
self._db["role_mapping"].delete(row["id"])
|
||||
for table in self._db.table_names():
|
||||
if table not in hard_tables:
|
||||
self._db.table(table).drop(ignore=True)
|
||||
|
||||
self._db.create_table("division", {"division_id": int, "epic": bool, "empty": bool}, pk="id", defaults={"epic": False, "empty": False}, not_null={"division_id"})
|
||||
self._db.create_table("rss_feed", {"timestamp": float}, pk="id", not_null={"timestamp"})
|
||||
self._db.create_table("battleorder", {"battle_id": int, "side": int}, pk="id", not_null={"battle_id","side"}, defaults={"side":71})
|
||||
self._db.create_table("battleorder", {"battle_id": int, "side": int}, pk="id", not_null={"battle_id", "side"}, defaults={"side": 71})
|
||||
|
||||
self._db.vacuum()
|
||||
|
||||
def load_base_data(self):
|
||||
for country_id in COUNTRIES.keys():
|
||||
self.set_rss_feed_timestamp(country_id, time.time())
|
||||
|
||||
# Player methods
|
||||
|
||||
def get_player(self, pid: int) -> Optional[Dict[str, Union[int, str]]]:
|
||||
@ -158,10 +175,7 @@ class DiscordDB:
|
||||
:param division_id: int Division ID
|
||||
:return: bool
|
||||
"""
|
||||
try:
|
||||
return bool(next(self.division.rows_where("division_id = ? and epic = ?", (division_id, True))))
|
||||
except StopIteration:
|
||||
return False
|
||||
return any(self.division.rows_where("division_id = ? and epic = ?", (division_id, True)))
|
||||
|
||||
def add_epic(self, division_id: int) -> bool:
|
||||
"""Register epic in division.
|
||||
@ -280,7 +294,7 @@ class DiscordDB:
|
||||
for row in rows:
|
||||
return row["role_id"]
|
||||
|
||||
def set_battle_order(self, battle_id:int, side:int):
|
||||
def set_battle_order(self, battle_id: int, side: int):
|
||||
if self.get_battle_order(battle_id):
|
||||
return False
|
||||
self.battleorder.insert(dict(battle_id=battle_id, side=side))
|
||||
@ -288,16 +302,16 @@ class DiscordDB:
|
||||
|
||||
def get_battle_order(self, battle_id: int = None):
|
||||
if battle_id is None:
|
||||
return list(sef.battleorder.rows)
|
||||
return list(self.battleorder.rows)
|
||||
try:
|
||||
row = next(self.battleorder.rows_where('battle_id = ?', (battle_id,)))
|
||||
row = next(self.battleorder.rows_where("battle_id = ?", (battle_id,)))
|
||||
return row
|
||||
except StopIterationError:
|
||||
except StopIteration:
|
||||
return
|
||||
|
||||
def delete_battle_order(self, battle_id: int):
|
||||
bo = self.get_battle_order(battle_id)
|
||||
if bo:
|
||||
DB.battleorder.delete(bo['id'])
|
||||
self.battleorder.delete(bo["id"])
|
||||
return True
|
||||
return False
|
||||
|
44
dbot/main.py
Normal file
44
dbot/main.py
Normal file
@ -0,0 +1,44 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
|
||||
from erepublik.constants import COUNTRIES
|
||||
|
||||
from dbot.base import ADMIN_ID, DB, DB_NAME, DEFAULT_CHANNEL_ID, DISCORD_TOKEN, LOOP, PRODUCTION, logger
|
||||
from dbot.bot import bot
|
||||
from dbot.client import client
|
||||
|
||||
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}'")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
logger.info("Starting Bot loop")
|
||||
LOOP.create_task(bot.start(DISCORD_TOKEN))
|
||||
# asyncio.run(bot.start(DISCORD_TOKEN))
|
||||
|
||||
logger.info("Starting Client loop")
|
||||
# asyncio.run(client.start(DISCORD_TOKEN))
|
||||
LOOP.create_task(client.start(DISCORD_TOKEN))
|
||||
|
||||
LOOP.run_forever()
|
||||
|
||||
|
||||
async def run_main():
|
||||
logger.info("Starting Client loop")
|
||||
logger.info("Starting Bot loop")
|
||||
await asyncio.gather(bot.start(DISCORD_TOKEN), client.start(DISCORD_TOKEN))
|
||||
logger.info("Loops have finished")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# asyncio.run(run_main())
|
||||
main()
|
@ -1,5 +1,6 @@
|
||||
import feedparser
|
||||
from constants import COUNTRIES, events
|
||||
|
||||
from dbot.constants import COUNTRIES, events
|
||||
|
||||
|
||||
def main(country):
|
||||
|
@ -1,4 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
sh ./lint.sh
|
||||
ret=$?
|
||||
if test $ret != 0; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker rm -f discord_bot
|
||||
set -e
|
||||
docker build --tag discord_epicbot .
|
||||
|
2
lint.sh
2
lint.sh
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
isort dbot
|
||||
black dbot
|
||||
flake8 dbot
|
||||
PYTHONPATH="$(python -c "import os.path; print(os.path.realpath('$1'))")/dbot" python -m unittest dbot/tests.py
|
||||
|
@ -1,6 +1,6 @@
|
||||
black==21.7b0
|
||||
discord.py==1.7.3
|
||||
eRepublik==0.25.1.5
|
||||
eRepublik==0.27.0
|
||||
feedparser==6.0.8
|
||||
flake8==3.9.2
|
||||
isort==5.9.3
|
||||
|
Loading…
x
Reference in New Issue
Block a user