Added #279, added priority number for hooks so they can be started in sequence, added condition for running a hook

This commit is contained in:
Frisk 2025-01-02 01:44:33 +01:00
parent afc503a285
commit f870a53e82
5 changed files with 81 additions and 16 deletions

View file

@ -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")

View file

@ -14,27 +14,53 @@
# along with RcGcDw. If not, see <http://www.gnu.org/licenses/>.
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)

View file

@ -14,10 +14,10 @@
# along with RcGcDw. If not, see <http://www.gnu.org/licenses/>.
# 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]] = []
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]]]] = []

View file

@ -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

View file

@ -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)