drbotzo-idlerpg/idlerpg/ircplugin.py

140 lines
6.4 KiB
Python

"""IRC support for managing and executing the IdleRPG game.
SPDX-FileCopyrightText: © 2024 Brian S. Stephan <bss@incorporeal.org>
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<name>\S+)\s+(?P<password>\S+)$'
LOGOUT_COMMAND_PATTERN = r'^LOGOUT$'
REGISTER_COMMAND_PATTERN = r'^REGISTER\s+(?P<name>\S+)\s+(?P<password>\S+)\s+(?P<char_class>.*)$'
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