import inspect def wolflistener(name=None, priority=0): """A decorator that marks a function as a listener. This is the werewolf.Game equivalent of :meth:`.Cog.listener`. Parameters ------------ name: :class:`str` The name of the event being listened to. If not provided, it defaults to the function's name. priority: :class:`int` The priority of the listener. Priority guide as follows: _at_night_start 0. No Action 1. Detain actions (Jailer/Kidnapper) 2. Group discussions and choose targets _at_night_end 0. No Action 1. Self actions (Veteran) 2. Target switching and role blocks (bus driver, witch, escort) 3. Protection / Preempt actions (bodyguard/framer) 4. Non-disruptive actions (seer/silencer) 5. Disruptive actions (Killing) 6. Role altering actions (Cult / Mason / Shifter) Raises -------- TypeError The function is not a coroutine function or a string was not passed as the name. """ if name is not None and not isinstance(name, str): raise TypeError( "Game.listener expected str but received {0.__class__.__name__!r} instead.".format( name ) ) def decorator(func): actual = func if isinstance(actual, staticmethod): actual = actual.__func__ if not inspect.iscoroutinefunction(actual): raise TypeError("Listener function must be a coroutine function.") actual.__wolf_listener__ = priority to_assign = name or actual.__name__ try: actual.__wolf_listener_names__.append((priority, to_assign)) except AttributeError: actual.__wolf_listener_names__ = [(priority, to_assign)] # we have to return `func` instead of `actual` because # we need the type to be `staticmethod` for the metaclass # to pick it up but the metaclass unfurls the function and # thus the assignments need to be on the actual function return func return decorator class WolfListenerMeta(type): def __new__(mcs, cls, *args, **kwargs): name, bases = args listeners = {} need_at_msg = "Listeners must start with at_ (in method {0.__name__}.{1})" new_cls = super().__new__(cls, name, bases, **kwargs) for base in reversed(new_cls.__mro__): for elem, value in base.__dict__.items(): if elem in listeners: del listeners[elem] is_static_method = isinstance(value, staticmethod) if is_static_method: value = value.__func__ if inspect.iscoroutinefunction(value): try: is_listener = getattr(value, "__wolf_listener__") except AttributeError: continue else: if not elem.startswith("at_"): raise TypeError(need_at_msg.format(mcs, elem)) listeners[elem] = value listeners_as_list = [] for listener in listeners.values(): for priority, listener_name in listener.__wolf_listener_names__: # I use __name__ instead of just storing the value so I can inject # the self attribute when the time comes to add them to the bot listeners_as_list.append((priority, listener_name, listener.__name__)) new_cls.__wolf_listeners__ = listeners_as_list return new_cls class WolfListener(metaclass=WolfListenerMeta): def __init__(self, game): for priority, name, method_name in self.__wolf_listeners__: game.add_ww_listener(getattr(self, method_name), priority, name)