diff --git a/extensions/hooks/example_hook.py b/extensions/hooks/example_hook.py index aa4f049..983b76c 100644 --- a/extensions/hooks/example_hook.py +++ b/extensions/hooks/example_hook.py @@ -27,3 +27,21 @@ def example_pre_hook(context: Context, change: dict): def example_post_hook(message: DiscordMessage, metadata: DiscordMessageMetadata, context: Context, change: dict): print("Our Discord message looks as follows: ") print(message) + + +def verify_upload(context: Context, change: dict) -> bool: + return hasattr(context, "event") and context.event.startswith("upload") + + +@pre_hook(priority=105, condition=verify_upload) +def example_pre_hook_with_args(context: Context, change: dict): + print("I'm an upload! Also I'm using a fancy decorator!") + + +def never_register_this_one(settings): + return False + + +@pre_hook(priority=2, register=never_register_this_one) +def example_pre_hook_that_will_never_register(context: Context, change: dict): + print("EVIL THINGS HAPPENING IN HERE MUHAHHAHAHHA") \ No newline at end of file diff --git a/src/api/hook.py b/src/api/hook.py index b0ac0c4..7b7f5db 100644 --- a/src/api/hook.py +++ b/src/api/hook.py @@ -14,27 +14,53 @@ # along with RcGcDw. If not, see . import logging +import functools +from src.configloader import settings +from typing import Optional, Callable import src.api.hooks logger = logging.getLogger("src.api.hook") -def pre_hook(func): + +def pre_hook(_func: Optional[Callable] = None, priority: int = 100, condition: Optional[Callable] = None, register: Optional[Callable] = None): """ Decorator to register a pre hook and return a function :return: func """ - logger.info("{hook_name} has been registered as a pre hook.".format(hook_name=func.__name__)) - src.api.hooks.pre_hooks.append(func) - return func + def decorator_prehook(func): + if register is None or register(settings): + logger.info("{hook_name} has been registered as a pre hook.".format(hook_name=func.__name__)) + src.api.hooks.pre_hooks.append((func, priority, condition)) + + @functools.wraps(func) + def wrapper_prehook(*args, **kwargs): + return func(*args, **kwargs) + return wrapper_prehook + + if _func is None: + return decorator_prehook + else: + return decorator_prehook(_func) -def post_hook(func): +def post_hook(_func: Optional[Callable] = None, priority: int = 100, condition: Optional[Callable] = None, register: Optional[Callable] = None): """ Decorator to register a post hook and return a function :return: func """ - logger.info("{hook_name} has been registered as a post hook.".format(hook_name=func.__name__)) - src.api.hooks.post_hooks.append(func) - return func + def decorator_posthook(func): + if register is None or register(settings): + logger.info("{hook_name} has been registered as a post hook.".format(hook_name=func.__name__)) + src.api.hooks.post_hooks.append((func, priority, condition)) + + @functools.wraps(func) + def wrapper_posthook(*args, **kwargs): + return func(*args, **kwargs) + return wrapper_posthook + + if _func is None: + return decorator_posthook + else: + return decorator_posthook(_func) diff --git a/src/api/hooks.py b/src/api/hooks.py index b0d7674..a8b4d3e 100644 --- a/src/api/hooks.py +++ b/src/api/hooks.py @@ -14,10 +14,10 @@ # along with RcGcDw. If not, see . # Made just to avoid circular imports -from typing import Callable, List, Dict +from typing import Callable, List, Dict, Tuple, Optional from src.discord.message import DiscordMessage, DiscordMessageMetadata from src.api.context import Context formatter_hooks: Dict[str, Callable[[Context, dict], DiscordMessage]] = {} -pre_hooks: List[Callable[[Context, dict], None]] = [] -post_hooks: List[Callable[[DiscordMessage, DiscordMessageMetadata, Context, dict], None]] = [] \ No newline at end of file +pre_hooks: List[Tuple[Callable[[Context, dict], None], int, Optional[Callable[[Context, dict], bool]]]] = [] +post_hooks: List[Tuple[Callable[[DiscordMessage, DiscordMessageMetadata, Context, dict], None], int, Optional[Callable[[DiscordMessage, DiscordMessageMetadata, Context, dict], bool]]]] = [] \ No newline at end of file diff --git a/src/misc.py b/src/misc.py index 46d764f..b68cf29 100644 --- a/src/misc.py +++ b/src/misc.py @@ -17,9 +17,11 @@ import base64 import json, logging, sys, re, platform from html.parser import HTMLParser +from typing import Callable, Tuple, List, Optional, Union from urllib.parse import urlparse, urlunparse import requests +from api.context import Context from src.argparser import command_args from src.configloader import settings import src.api.util @@ -314,13 +316,24 @@ def prepare_paths(path: str, dry=False): prepare_paths(settings["wiki_url"]) -def run_hooks(hooks, *arguments): +def run_hooks(hooks: Union[List[Tuple[Callable[[Context, dict], None], int, Optional[Callable]]], List[Tuple[Callable[[DiscordMessage, DiscordMessageMetadata, Context, dict], None], int, Optional[Callable]]]], *arguments): for hook in hooks: + if hook[2] is not None: + try: + if hook[2](*arguments) is False: + misc_logger.debug(f"Ignoring hook {hook[0].__name__} due to conditions not being met for execution") + continue + except: + if settings.get("error_tolerance", 1) > 0: + misc_logger.exception("On running a hook check function, ignoring hook") + else: + raise try: - hook(*arguments) + misc_logger.debug(f"Running {hook[0].__name__} hook") + hook[0](*arguments) except: if settings.get("error_tolerance", 1) > 0: - misc_logger.exception("On running a pre hook, ignoring pre-hook") + misc_logger.exception("On running a hook, ignoring hook") else: raise diff --git a/src/rcgcdw.py b/src/rcgcdw.py index 15d860f..c9588d8 100644 --- a/src/rcgcdw.py +++ b/src/rcgcdw.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - +import operator # This file is part of Recent changes Goat compatible Discord webhook (RcGcDw). # RcGcDw is free software: you can redistribute it and/or modify @@ -25,7 +25,8 @@ import src.configloader from src.migrations import * from collections import defaultdict, Counter, OrderedDict from src.argparser import command_args -from typing import Optional +from typing import Optional, Callable, Tuple +from operator import itemgetter import src.api.client from src.api.context import Context from src.api.hooks import formatter_hooks, pre_hooks, post_hooks @@ -53,6 +54,12 @@ logger = logging.getLogger("rcgcdw") logger.debug("Current settings: {settings}".format(settings=settings)) +def sort_hooks(): + """Sort hooks according to their priority, first are executed hooks that have higher priority then lower""" + pre_hooks.sort(key=operator.itemgetter(1), reverse=True) + post_hooks.sort(key=operator.itemgetter(1), reverse=True) + + def load_extensions(): """Loads all of the extensions, can be a local import because all we need is them to register""" try: @@ -309,6 +316,7 @@ def abuselog_processing(entry): load_extensions() +sort_hooks() # Log in and download wiki information wiki = Wiki(rc_processor, abuselog_processing) client = src.api.client.Client(formatter_hooks, wiki)