This repository has been archived on 2025-02-16. You can view files and clone it, but cannot push or open issues or pull requests.
Spiffo/cogs/console.py
Corban-Lee Jones 2b6f68de0d
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
database, data folder path & file reader class
2024-12-07 23:23:10 +00:00

137 lines
4.7 KiB
Python

"""
Reads and handles updates in the server console file.
"""
import re
import logging
import asyncio
from os import getenv
from pathlib import Path
import aiofiles
from rcon.source import rcon
from discord.ext import commands, tasks
from utils.reader import LogFileReader
ZOMBOID_FOLDER_PATH = Path(getenv("SPIFFO__ZOMBOID_FOLDER_PATH"))
CONSOLE_FILE_PATH = ZOMBOID_FOLDER_PATH / "server-console.txt"
log = logging.getLogger(__name__)
class ConsoleCog(commands.Cog):
"""
Reads and handles the server-console.txt file.
"""
_last_line_number = 0
def __init__(self, bot: commands.Bot):
self.bot = bot
self.listen_for_changes.start()
@tasks.loop(seconds=3)
async def listen_for_changes(self):
try:
file_reader = LogFileReader(CONSOLE_FILE_PATH)
except FileNotFoundError:
self.listen_for_changes.cancel()
async for line in file_reader.read():
await self.process_console_line(line)
# @tasks.loop(seconds=1)
# async def monitor_console(self):
# """
# Check the latest version of the console log file.
# """
# if not CONSOLE_FILE_PATH.exists():
# self.monitor_console.cancel()
# raise FileNotFoundError("Server console file doesn't exist, task cancelled.")
# async with aiofiles.open(CONSOLE_FILE_PATH, "r", encoding="utf-8") as file:
# # If we are at 0, restarting the bot would cause rapid fire of all log lines,
# # instead lets grab the latest line, to prevent spam.
# if self._last_line_number == 0:
# await file.seek(0, 2)
# self._last_line_number = await file.tell()
# return
# await file.seek(self._last_line_number)
# lines = await file.readlines()
# if not lines:
# log.debug("no new lines to read")
# return
# for line in lines:
# await self.process_console_line(line.strip())
# self._last_line_number = await file.tell()
async def process_console_line(self, line: str):
"""
Determine how to handle the given line from the server console.
"""
if "CheckModsNeedUpdate: Mods need update" in line:
await self.handle_mod_needs_update(line)
elif "ConnectionManager: [fully-connected]" in line:
cog = self.bot.get_cog("PlayersCog")
await cog.handle_player_connected(line)
elif "ConnectionManager: [disconnect]" in line:
cog = self.bot.get_cog("PlayersCog")
await cog.handle_player_disconnected(line)
async def alert_and_wait_for_restart(self, intervals_ms: list[int], reason: str):
for interval_ms in intervals_ms:
seconds_remaining = interval_ms / 1000
log.info("Planned restart in %s seconds, reason: %s", seconds_remaining, reason)
await rcon(f"Planned restart in {seconds_remaining} seconds, reason: {reason}", **self.bot.rcon_details)
while seconds_remaining > 0:
await asyncio.sleep(1)
seconds_remaining -= 1
async def kick_all_users(self):
players_message = await rcon("players", **self.bot.rcon_details)
re_pattern = r"Players connected \(\d+\):\s*(-[\w-]+(?:\s*[\w-]+)*)"
re_match = re.search(re_pattern, players_message)
if not re_match:
log.info("No players found to kick")
usernames_string = re_match.group(1)
for i, username in enumerate(usernames_string.split("-")):
if not username:
continue
await rcon(f'kickuser "{username}" -r "Server is Updating. Kicked to ensure your progress is saved"', **self.bot.rcon_details)
log.info("Kicked '%s' users for restart", i + 1)
async def handle_mod_needs_update(self, line: str):
"""
Report when one or more mods need to be updated.
"""
log.info("one or more mods are outdated")
await self.alert_and_wait_for_restart(
intervals_ms=[300000, 60000, 6000],
reason="Mod needs updating"
)
await self.kick_all_users()
await rcon("save", **self.bot.rcon_details)
await rcon("quit", **self.bot.rcon_details)
channel = self.bot.get_channel(self.bot.in_game_channel_id)
channel = await self.bot.fetch_channel(self.bot.in_game_channel_id) if not channel else channel
await channel.send(content="The server is currently being restarted for mod updates.")
async def setup(bot: commands.Bot):
cog = ConsoleCog(bot)
await bot.add_cog(cog)
log.info("Added %s cog", cog.__class__.__name__)