"""IRC support for managing and executing the IdleRPG game. SPDX-FileCopyrightText: © 2024 Brian S. Stephan SPDX-License-Identifier: AGPL-3.0-or-later """ import logging import threading import time import irc.client from django.db.utils import IntegrityError from ircbot.lib import Plugin from idlerpg.models import Character, Game logger = logging.getLogger(__name__) class IdleRPG(Plugin): """Run a game of IdleRPG.""" SLEEP_BETWEEN_LEVEL_CHECKS = 1 LOGIN_COMMAND_PATTERN = r'^LOGIN\s+(?P\S+)\s+(?P\S+)$' LOGOUT_COMMAND_PATTERN = r'^LOGOUT$' REGISTER_COMMAND_PATTERN = r'^REGISTER\s+(?P\S+)\s+(?P\S+)\s+(?P.*)$' REMOVEME_COMMAND_PATTERN = r'^REMOVEME$' STATUS_COMMAND_PATTERN = r'^(STATUS|WHOAMI)$' def __init__(self, bot, connection, event): """Initialize miscellaneous state stuff.""" self.seen_hostmasks = set() super().__init__(bot, connection, event) def handle_join(self, connection, event): """Track a character as online if their player joins the game channel.""" self.seen_hostmasks.add(event.source) logger.info("Added %s to the set of seen hostmasks.", event.source) def handle_kick_penalty(self, connection, event): """Penalize characters for their user getting kicked from the channel.""" self.seen_hostmasks.discard(event.source) logger.info("Removed %s from the set of seen hostmasks.", event.source) return self._handle_generic_penalty_and_logout(event.source, event.target, 250, "getting kicked from the game channel", 'time_penalized_kicked') def handle_nick_penalty(self, connection, event): """Penalize characters for changing their nick while in the channel.""" # TODO: figure out how to update the character and seen hostmasks return self._handle_generic_penalty_and_logout(event.source, event.target, 30, "changing their nick", 'time_penalized_nick_change') def handle_part_penalty(self, connection, event): """Penalize characters for their user parting.""" self.seen_hostmasks.discard(event.source) logger.info("Removed %s from the set of seen hostmasks.", event.source) return self._handle_generic_penalty_and_logout(event.source, event.target, 200, "parting the game channel", 'time_penalized_part') def handle_message_penalty(self, connection, event): """Pay attention to messages in the game channel, and penalize talkers.""" hostmask = event.source logger.debug("looking for %s to try to penalize them", hostmask) try: character = Character.objects.get(enabled=True, hostmask=hostmask) logger.debug("found character %s", character) message = event.arguments[0] penalty = character.penalize(len(message), "sending a privmsg to the game channel") character.time_penalized_privmsg += penalty character.save() except Character.DoesNotExist: logger.debug("no character found for %s", hostmask) return def handle_quit_penalty(self, connection, event): """Penalize characters for their user quitting IRC.""" self.seen_hostmasks.discard(event.source) logger.info("Removed %s from the set of seen hostmasks.", event.source) return self._handle_generic_penalty_and_logout(event.source, event.target, 20, "quitting IRC", 'time_penalized_quit') def handle_login(self, connection, event, match): """Log in a character when requested, assuming they're online.""" hostmask = event.source nick = irc.client.NickMask(hostmask).nick try: character = Character.objects.get(enabled=True, name=match.group('name')) character.check_password(match.group('password')) except (Character.DoesNotExist, ValueError): return self.bot.reply(event, "The requested character does not exist, or has been disabled, " "or your password does not match.") if hostmask not in self.seen_hostmasks: return self.bot.reply(event, f"Please join {character.game.channel.name} before logging in.") if character.status != Character.CHARACTER_STATUS_OFFLINE: return self.bot.reply(event, f"Cannot log in {character.name}, either they already are, " "or they are in a state that cannot be modified.") character.log_in(match.group('password'), hostmask) character.save() self.bot.reply(None, f"{character}, is now online from nickname {nick}. " f"Next level at {character.next_level_str()}.", explicit_target=character.game.channel.name) return self.bot.reply(event, f"{character} has been successfully logged in.") def _handle_generic_penalty_and_logout(self, hostmask: str, channel: str, penalty: int, reason: str, penalty_log_attr: str): """Penalize a character and log them out, for a provided reason. Args: hostmask: the hostmask for the character in question channel: the game channel the event occurred in penalty: the penalty to apply reason: the reason for the penalty """ logger.debug("looking for %s to try to penalize them", hostmask) try: character = Character.objects.get(enabled=True, hostmask=hostmask) logger.debug("character found for %s", hostmask) seconds = character.penalize(penalty, reason) log = getattr(character, penalty_log_attr) setattr(character, penalty_log_attr, log + seconds) try: character.log_out() except ValueError: logger.debug("tried to log out %s but they already were", character) character.save() except Character.DoesNotExist: logger.debug("no character found for %s", hostmask) plugin = IdleRPG