Updated to discord bot and its docker

gitignore

dockerignore

Formatting

Requirement update, code styling

Gitignore
This commit is contained in:
KEriks 2021-07-23 14:39:00 +03:00
parent 00c3959273
commit 71e69719d7
10 changed files with 148 additions and 53 deletions

13
.dockerignore Normal file
View File

@ -0,0 +1,13 @@
__pycache__
# Docker
docker-compose.yml
.docker
# Byte-compiled / optimized / DLL files
__pycache__/
*/__pycache__/
**/__pycache__/
*.py[cod]
**/*.py[cod]

6
.gitignore vendored
View File

@ -1,5 +1,9 @@
venv venv
*.db *.db
*.log
.env .env
pid
*pid
__pycache__ __pycache__
.idea .idea
debug/

View File

@ -1,7 +1,13 @@
FROM python:3.9-slim FROM python:3.9-slim
WORKDIR /app WORKDIR /app
RUN groupadd -g 1000 discordbot \
&& useradd -u 1000 -g 1000 discordbot \
&& mkdir /home/discordbot \
&& chown -R discordbot:discordbot /app \
&& chown -R discordbot:discordbot /home/discordbot
USER discordbot
COPY requirements.txt /app/requirements.txt COPY requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
COPY . /app
CMD python discord_bot.py CMD python discord_bot.py

4
db.py
View File

@ -1,4 +1,4 @@
from typing import Union, Dict, Optional from typing import Dict, Optional, Union
from sqlite_utils import Database from sqlite_utils import Database
from sqlite_utils.db import NotFoundError from sqlite_utils.db import NotFoundError
@ -21,7 +21,7 @@ class DiscordDB:
self._db.create_table("player", {"id": int, "name": str}, pk="id", not_null={"id", "name"}) self._db.create_table("player", {"id": int, "name": str}, pk="id", not_null={"id", "name"})
if "epic" not in self._db.table_names(): if "epic" not in self._db.table_names():
self._db.create_table("epic", {"id": int, }, pk="id", not_null={"id"}) self._db.create_table("epic", {"id": int, "fake": bool}, pk="id", not_null={"id"}, defaults={"fake": False})
self._db.vacuum() self._db.vacuum()

View File

@ -18,8 +18,7 @@ APP_NAME = "discord_bot"
os.chdir(os.path.abspath(os.path.dirname(sys.argv[0]))) os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))
load_dotenv() load_dotenv()
logging.basicConfig(level=logging.WARNING, filename="logging.log", logging.basicConfig(level=logging.WARNING, filename="logging.log", format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(APP_NAME) logger = logging.getLogger(APP_NAME)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logger.propagate = False logger.propagate = False
@ -28,29 +27,94 @@ fh.setLevel(logging.DEBUG)
logger.addHandler(fh) logger.addHandler(fh)
keep_fds = [fh.stream.fileno()] keep_fds = [fh.stream.fileno()]
os.makedirs('debug', exist_ok=True) os.makedirs("debug", exist_ok=True)
pidfile = "pid" pidfile = "pid"
with open(pidfile, 'w') as f: with open(pidfile, "w") as f:
f.write(str(os.getpid())) f.write(str(os.getpid()))
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN") DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
DEFAULT_CHANNEL_ID = os.getenv("DEFAULT_CHANNEL_ID", 603527159109124096) DEFAULT_CHANNEL_ID = os.getenv("DEFAULT_CHANNEL_ID", 603527159109124096)
ADMIN_ID = os.getenv("DEFAULT_CHANNEL_ID", 220849530730577920) ADMIN_ID = os.getenv("DEFAULT_CHANNEL_ID", 220849530730577920)
DB_NAME = os.getenv('DB_NAME', 'discord.db') DB_NAME = os.getenv("DB_NAME", "discord.db")
DB = DiscordDB(DB_NAME) DB = DiscordDB(DB_NAME)
FLAGS = {1: 'flag_ro', 9: 'flag_br', 10: 'flag_it', 11: 'flag_fr', 12: 'flag_de', 13: 'flag_hu', 14: 'flag_cn', FLAGS = {
15: 'flag_es', 23: 'flag_ca', 24: 'flag_us', 26: 'flag_mx', 27: 'flag_ar', 28: 'flag_ve', 29: 'flag_gb', 1: "flag_ro",
30: 'flag_ch', 31: 'flag_nl', 32: 'flag_be', 33: 'flag_at', 34: 'flag_cz', 35: 'flag_pl', 36: 'flag_sk', 9: "flag_br",
37: 'flag_no', 38: 'flag_se', 39: 'flag_fi', 40: 'flag_ua', 41: 'flag_ru', 42: 'flag_bg', 43: 'flag_tr', 10: "flag_it",
44: 'flag_gr', 45: 'flag_jp', 47: 'flag_kr', 48: 'flag_in', 49: 'flag_id', 50: 'flag_au', 51: 'flag_za', 11: "flag_fr",
52: 'flag_md', 53: 'flag_pt', 54: 'flag_ie', 55: 'flag_de', 56: 'flag_ir', 57: 'flag_pk', 58: 'flag_il', 12: "flag_de",
59: 'flag_th', 61: 'flag_si', 63: 'flag_hr', 64: 'flag_cl', 65: 'flag_rs', 66: 'flag_my', 67: 'flag_ph', 13: "flag_hu",
68: 'flag_sg', 69: 'flag_ba', 70: 'flag_ee', 71: 'flag_lv', 72: 'flag_lt', 73: 'flag_kp', 74: 'flag_uy', 14: "flag_cn",
75: 'flag_py', 76: 'flag_bo', 77: 'flag_pe', 78: 'flag_co', 79: 'flag_mk', 80: 'flag_me', 81: 'flag_tw', 15: "flag_es",
82: 'flag_cy', 83: 'flag_by', 84: 'flag_nz', 164: 'flag_sa', 165: 'flag_eg', 166: 'flag_ae', 167: 'flag_al', 23: "flag_ca",
168: 'flag_ge', 169: 'flag_am', 170: 'flag_ng', 171: 'flag_cu'} 24: "flag_us",
26: "flag_mx",
27: "flag_ar",
28: "flag_ve",
29: "flag_gb",
30: "flag_ch",
31: "flag_nl",
32: "flag_be",
33: "flag_at",
34: "flag_cz",
35: "flag_pl",
36: "flag_sk",
37: "flag_no",
38: "flag_se",
39: "flag_fi",
40: "flag_ua",
41: "flag_ru",
42: "flag_bg",
43: "flag_tr",
44: "flag_gr",
45: "flag_jp",
47: "flag_kr",
48: "flag_in",
49: "flag_id",
50: "flag_au",
51: "flag_za",
52: "flag_md",
53: "flag_pt",
54: "flag_ie",
55: "flag_de",
56: "flag_ir",
57: "flag_pk",
58: "flag_il",
59: "flag_th",
61: "flag_si",
63: "flag_hr",
64: "flag_cl",
65: "flag_rs",
66: "flag_my",
67: "flag_ph",
68: "flag_sg",
69: "flag_ba",
70: "flag_ee",
71: "flag_lv",
72: "flag_lt",
73: "flag_kp",
74: "flag_uy",
75: "flag_py",
76: "flag_bo",
77: "flag_pe",
78: "flag_co",
79: "flag_mk",
80: "flag_me",
81: "flag_tw",
82: "flag_cy",
83: "flag_by",
84: "flag_nz",
164: "flag_sa",
165: "flag_eg",
166: "flag_ae",
167: "flag_al",
168: "flag_ge",
169: "flag_am",
170: "flag_ng",
171: "flag_cu",
}
MENTION_MAPPING = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"} MENTION_MAPPING = {1: "D1", 2: "D2", 3: "D3", 4: "D4", 11: "Air"}
@ -67,20 +131,20 @@ def s_to_human(seconds: Union[int, float]) -> str:
h = seconds // 3600 h = seconds // 3600
m = (seconds - (h * 3600)) // 60 m = (seconds - (h * 3600)) // 60
s = seconds % 60 s = seconds % 60
return f'{h:01d}:{m:02d}:{s:02d}' return f"{h:01d}:{m:02d}:{s:02d}"
def get_battle_page(): def get_battle_page():
global __last_battle_update_timestamp, __last_battle_response global __last_battle_update_timestamp, __last_battle_response
if int(datetime.datetime.now().timestamp()) >= __last_battle_update_timestamp + 60: if int(datetime.datetime.now().timestamp()) >= __last_battle_update_timestamp + 60:
dt = datetime.datetime.now() dt = datetime.datetime.now()
r = requests.get('https://erep.lv/battles.json') r = requests.get("https://www.erepublik.com/en/military/campaignsJson/list")
try: try:
__last_battle_response = r.json() __last_battle_response = r.json()
except JSONDecodeError: except JSONDecodeError:
logger.warning("Received non json response from erep.lv/battles.json!") logger.warning("Received non json response from erep.lv/battles.json!")
return get_battle_page() return get_battle_page()
__last_battle_update_timestamp = __last_battle_response.get('last_updated', int(dt.timestamp())) __last_battle_update_timestamp = __last_battle_response.get("last_updated", int(dt.timestamp()))
return __last_battle_response return __last_battle_response
@ -95,11 +159,11 @@ class MyClient(discord.Client):
return int(datetime.datetime.now().timestamp()) return int(datetime.datetime.now().timestamp())
async def on_ready(self): async def on_ready(self):
print('Client running') print("Client running")
print('------') print("------")
async def on_error(self, event_method, *args, **kwargs): async def on_error(self, event_method, *args, **kwargs):
logger.warning(f'Ignoring exception in {event_method}') logger.warning(f"Ignoring exception in {event_method}")
async def report_epics(self): async def report_epics(self):
await self.wait_until_ready() await self.wait_until_ready()
@ -108,28 +172,27 @@ class MyClient(discord.Client):
while not self.is_closed(): while not self.is_closed():
try: try:
r = get_battle_page() r = get_battle_page()
if not isinstance(r.get('battles'), dict): if not isinstance(r.get("battles"), dict):
sleep_seconds = r.get('last_updated') + 60 - self.timestamp sleep_seconds = r.get("last_updated") + 60 - self.timestamp
await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0) await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0)
continue continue
for bid, battle in r.get('battles', {}).items(): for bid, battle in r.get("battles", {}).items():
for div in battle.get('div', {}).values(): for div in battle.get("div", {}).values():
if div.get('epic') and not DB.get_epic(div.get('id')): if div.get("epic") > 1 and not DB.get_epic(div.get("id")):
with open(f'debug/{self.timestamp}.json', 'wb') as f: with open(f"debug/{self.timestamp}.json", "w") as f:
json.dump(r, f) json.dump(r, f)
await self.get_channel(DEFAULT_CHANNEL_ID).send( await self.get_channel(DEFAULT_CHANNEL_ID).send(
f"{role_mapping[MENTION_MAPPING[div['div']]]} Epic battle! Round time {s_to_human(self.timestamp - battle['start'])}\n" f"{role_mapping[MENTION_MAPPING[div['div']]]} Epic battle! Round time {s_to_human(self.timestamp - battle['start'])}\n"
f"https://www.erepublik.com/en/military/battlefield/{battle['id']}") f"https://www.erepublik.com/en/military/battlefield/{battle['id']}"
DB.add_epic(div.get('id')) )
DB.add_epic(div.get("id"))
sleep_seconds = r.get('last_updated') + 60 - self.timestamp sleep_seconds = r.get("last_updated") + 60 - self.timestamp
await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0) await asyncio.sleep(sleep_seconds if sleep_seconds > 0 else 0)
except Exception as e: except Exception as e:
await self.get_channel(DEFAULT_CHANNEL_ID).send(
f"<@{ADMIN_ID}> Something bad has happened with epic notifier!")
logger.error("Discord bot's eRepublik epic watcher died!", exc_info=e) logger.error("Discord bot's eRepublik epic watcher died!", exc_info=e)
try: try:
with open(f"{self.timestamp}.json", 'w') as f: with open(f"debug/{self.timestamp}.json", "w") as f:
f.write(r.text) f.write(r.text)
except NameError: except NameError:
logger.error("There was no Response object!", exc_info=e) logger.error("There was no Response object!", exc_info=e)
@ -139,15 +202,15 @@ class MyClient(discord.Client):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
client = MyClient() client = MyClient()
bot = commands.Bot(command_prefix='!') bot = commands.Bot(command_prefix="!")
@bot.event @bot.event
async def on_ready(): async def on_ready():
print('Bot loaded') print("Bot loaded")
# print(bot.user.name) # print(bot.user.name)
# print(bot.user.id) # print(bot.user.id)
print('------') print("------")
@bot.command() @bot.command()

6
docker_run.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
docker rm -f discord_bot
set -e
docker build --tag discord_epicbot .
docker run --detach -v $PWD:/app --restart=always --name discord_bot discord_epicbot

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[tool.black]
line-length = 140
target-version = ['py38', 'py39']

View File

@ -1,4 +1,4 @@
discord.py==1.7.3 discord.py==1.7.3
requests==2.26.0 requests==2.26.0
python-dotenv==0.18.0 python-dotenv==0.19.0
sqlite_utils==3.12 sqlite_utils==3.13

1
run.sh
View File

@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
python -m venv venv
source venv/bin/activate source venv/bin/activate
echo "Checking queries..." echo "Checking queries..."
python -m unittest python -m unittest

View File

@ -10,28 +10,28 @@ class TestDatabase(unittest.TestCase):
self.db = DiscordDB() self.db = DiscordDB()
def test_member(self): def test_member(self):
member = {'id': 1200, 'name': 'username'} member = {"id": 1200, "name": "username"}
self.db.add_member(**member) self.db.add_member(**member)
self.assertEqual(self.db.add_member(**member), member) self.assertEqual(self.db.add_member(**member), member)
self.assertRaises(NotFoundError, self.db.get_member, member_id=100) self.assertRaises(NotFoundError, self.db.get_member, member_id=100)
self.assertEqual(self.db.get_member(member_id=member['id']), member) self.assertEqual(self.db.get_member(member_id=member["id"]), member)
member.update(name="Success") member.update(name="Success")
self.assertTrue(self.db.update_member(member['id'], member['name'])) self.assertTrue(self.db.update_member(member["id"], member["name"]))
self.assertEqual(self.db.get_member(member_id=member['id']), member) self.assertEqual(self.db.get_member(member_id=member["id"]), member)
def test_player(self): def test_player(self):
player = {'id': 1, 'name': 'plato'} player = {"id": 1, "name": "plato"}
self.assertTrue(self.db.add_player(player['id'], player['name'])) self.assertTrue(self.db.add_player(player["id"], player["name"]))
self.assertFalse(self.db.add_player(player['id'], player['name'])) self.assertFalse(self.db.add_player(player["id"], player["name"]))
self.assertEqual(self.db.get_player(0), None) self.assertEqual(self.db.get_player(0), None)
self.assertEqual(self.db.get_player(player['id']), player) self.assertEqual(self.db.get_player(player["id"]), player)
self.assertFalse(self.db.update_player(0, "Error")) self.assertFalse(self.db.update_player(0, "Error"))
player["name"] = "New name" player["name"] = "New name"
self.assertTrue(self.db.update_player(player["id"], player["name"])) self.assertTrue(self.db.update_player(player["id"], player["name"]))
self.assertEqual(self.db.get_player(player['id']), player) self.assertEqual(self.db.get_player(player["id"]), player)
def test_epic(self): def test_epic(self):
self.assertFalse(self.db.get_epic(123456)) self.assertFalse(self.db.get_epic(123456))