From 41787a6e8697b9c24d7a6216bc1fb1f95ccc86e2 Mon Sep 17 00:00:00 2001 From: LogicOber <133167749+LogicOber@users.noreply.github.com> Date: Sat, 24 May 2025 18:45:50 +0100 Subject: [PATCH 1/4] feat: Integrate Folo webhook and update Dify plugin dependency This commit introduces two key enhancements: 1. **Folo Webhook Integration:** The webhook middleware has been updated to support Folo (https://app.follow.is). This integration allows users to leverage Folo as a trigger source. For instance, Folo can monitor RSS feeds and, upon receiving new content, automatically initiate Dify workflows such as translation or summarization. This enables automated generation of personalized reports, marketing copy, or even promotional posters. 2. **Dependency Update:** The `requirements.txt` file has been updated to reflect new Dify plugin version requirements. These changes enhance the plugin's capabilities by adding a powerful new trigger source and ensuring compatibility with the latest Dify plugin versions. --- endpoints/helpers.py | 57 +++++++++++++++++++++++++++++++++++++++----- group/webhook.yaml | 5 ++++ requirements.txt | 2 +- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/endpoints/helpers.py b/endpoints/helpers.py index c9628b2..8423f50 100644 --- a/endpoints/helpers.py +++ b/endpoints/helpers.py @@ -1,8 +1,11 @@ import json +import logging # Added for logging from typing import Literal, Mapping, Optional from werkzeug import Request, Response from middlewares.discord_middleware import DiscordMiddleware -from middlewares.default_middleware import DefaultMiddleware +from middlewares.default_middleware import DefaultMiddleware + +logger = logging.getLogger(__name__) # Added logger def apply_middleware(r: Request, settings: Mapping) -> Optional[Response]: """ @@ -21,15 +24,57 @@ def apply_middleware(r: Request, settings: Mapping) -> Optional[Response]: response = middleware.invoke(r) if response: return response - except (json.JSONDecodeError, KeyError, TypeError) as e: - print(f"Middleware Error: {str(e)}") + elif middleware_type == "folo": # New Folo middleware logic + logger.debug("Applying Folo middleware") + try: + folo_payload = r.get_json() + if not folo_payload or "entry" not in folo_payload: + logger.error("Folo middleware: Invalid or missing 'entry' in payload") + return Response(json.dumps({"error": "Folo middleware: Invalid payload, 'entry' field is missing."}), + status=400, content_type="application/json") + + entry = folo_payload.get("entry", {}) + + title = entry.get("title") + content = entry.get("content") + author = entry.get("author") + url = entry.get("url") + published_at = entry.get("publishedAt") + + dify_payload = { + "query": "start", + "inputs": { + "title": title, + "content": content, + "author": author, + "url": url, + "publishedAt": published_at + } + } + + r.default_middleware_json = dify_payload + logger.info("Folo middleware: Payload transformed successfully") + + except json.JSONDecodeError as e: + logger.error(f"Folo middleware: JSONDecodeError - {str(e)}") + return Response(json.dumps({"error": f"Folo middleware: Invalid JSON payload - {str(e)}"}), + status=400, content_type="application/json") + except Exception as e: + logger.error(f"Folo middleware: Unexpected error - {str(e)}") + return Response(json.dumps({"error": f"Folo middleware: Unexpected error - {str(e)}"}), + status=500, content_type="application/json") + + except (json.JSONDecodeError, KeyError, TypeError) as e: + logger.error(f"Middleware Error: {str(e)}") return Response(json.dumps({"error": f"Middleware error: {str(e)}"}), status=500, content_type="application/json") try: - default_middleware = DefaultMiddleware() - default_middleware.invoke(r, settings) + if middleware_type not in ["discord", "folo"] or not hasattr(r, 'default_middleware_json'): + logger.debug("Applying Default middleware") + default_middleware = DefaultMiddleware() + default_middleware.invoke(r, settings) except (json.JSONDecodeError, KeyError, TypeError) as e: - print(f"Default Middleware Error: {str(e)}") + logger.error(f"Default Middleware Error: {str(e)}") return Response(json.dumps({"error": f"Default Middleware error: {str(e)}"}), status=500, content_type="application/json") return None diff --git a/group/webhook.yaml b/group/webhook.yaml index 3fd3101..9a0163c 100644 --- a/group/webhook.yaml +++ b/group/webhook.yaml @@ -64,6 +64,11 @@ settings: en_US: Discord zh_Hans: Discord pt_BR: Discord + - value: folo + label: + en_US: Folo + zh_Hans: Folo + pt_BR: Folo - value: none label: en_US: None diff --git a/requirements.txt b/requirements.txt index 2ec1f54..e37e776 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ certifi==2025.1.31 cffi==1.17.1 charset-normalizer==3.4.1 click==8.1.8 -dify_plugin==0.0.1b72 +dify_plugin>=0.1.0,<0.2.0 dpkt==1.9.8 Flask==3.0.3 gevent==24.11.1 From d1e11b191f228c20b33e45c088a462b85e7649a8 Mon Sep 17 00:00:00 2001 From: LogicOber <133167749+LogicOber@users.noreply.github.com> Date: Sat, 24 May 2025 18:55:31 +0100 Subject: [PATCH 2/4] refactor: Standardize Folo middleware and improve control flow - Encapsulated Folo webhook logic into middlewares/folo_middleware.py. - Refined apply_middleware in helpers.py for clearer Folo integration and more robust DefaultMiddleware handling. - Ensures code structure aligns with existing project patterns. --- endpoints/helpers.py | 95 ++++++++++++++-------------------- middlewares/folo_middleware.py | 85 ++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 56 deletions(-) create mode 100644 middlewares/folo_middleware.py diff --git a/endpoints/helpers.py b/endpoints/helpers.py index 8423f50..f397fa1 100644 --- a/endpoints/helpers.py +++ b/endpoints/helpers.py @@ -1,11 +1,12 @@ import json -import logging # Added for logging +import logging # Ensure logging is imported from typing import Literal, Mapping, Optional from werkzeug import Request, Response from middlewares.discord_middleware import DiscordMiddleware -from middlewares.default_middleware import DefaultMiddleware +from middlewares.folo_middleware import FoloMiddleware # Import FoloMiddleware +from middlewares.default_middleware import DefaultMiddleware -logger = logging.getLogger(__name__) # Added logger +logger = logging.getLogger(__name__) # Ensure logger is initialized def apply_middleware(r: Request, settings: Mapping) -> Optional[Response]: """ @@ -15,69 +16,51 @@ def apply_middleware(r: Request, settings: Mapping) -> Optional[Response]: :param settings: A dictionary containing configuration settings :return: A Response object if middleware processing returns a response, otherwise None """ + middleware_response: Optional[Response] = None + middleware_processed_request = False # Flag to see if a specific middleware modified the request + try: middleware_type = settings.get("middleware") signature_verification_key = settings.get("signature_verification_key") if middleware_type == "discord": + logger.debug("Applying Discord middleware") middleware = DiscordMiddleware(signature_verification_key) - response = middleware.invoke(r) - if response: - return response - elif middleware_type == "folo": # New Folo middleware logic + middleware_response = middleware.invoke(r) + if hasattr(r, 'default_middleware_json'): # Check if DiscordMiddleware modified the request + middleware_processed_request = True + elif middleware_type == "folo": logger.debug("Applying Folo middleware") - try: - folo_payload = r.get_json() - if not folo_payload or "entry" not in folo_payload: - logger.error("Folo middleware: Invalid or missing 'entry' in payload") - return Response(json.dumps({"error": "Folo middleware: Invalid payload, 'entry' field is missing."}), - status=400, content_type="application/json") - - entry = folo_payload.get("entry", {}) - - title = entry.get("title") - content = entry.get("content") - author = entry.get("author") - url = entry.get("url") - published_at = entry.get("publishedAt") - - dify_payload = { - "query": "start", - "inputs": { - "title": title, - "content": content, - "author": author, - "url": url, - "publishedAt": published_at - } - } - - r.default_middleware_json = dify_payload - logger.info("Folo middleware: Payload transformed successfully") - - except json.JSONDecodeError as e: - logger.error(f"Folo middleware: JSONDecodeError - {str(e)}") - return Response(json.dumps({"error": f"Folo middleware: Invalid JSON payload - {str(e)}"}), - status=400, content_type="application/json") - except Exception as e: - logger.error(f"Folo middleware: Unexpected error - {str(e)}") - return Response(json.dumps({"error": f"Folo middleware: Unexpected error - {str(e)}"}), - status=500, content_type="application/json") - - except (json.JSONDecodeError, KeyError, TypeError) as e: - logger.error(f"Middleware Error: {str(e)}") - return Response(json.dumps({"error": f"Middleware error: {str(e)}"}), status=500, content_type="application/json") + middleware = FoloMiddleware() # Instantiate FoloMiddleware + middleware_response = middleware.invoke(r) # Call its invoke method + if hasattr(r, 'default_middleware_json'): # Check if FoloMiddleware modified the request + middleware_processed_request = True + + if middleware_response: # If any middleware returned a direct response (e.g. on error) + return middleware_response + + except Exception as e: # Catch errors during specific middleware instantiation or invocation + logger.error(f"Error during {middleware_type} middleware processing: {str(e)}", exc_info=True) + return Response(json.dumps({"error": f"Error in {middleware_type} middleware: {str(e)}"}), + status=500, content_type="application/json") - try: - if middleware_type not in ["discord", "folo"] or not hasattr(r, 'default_middleware_json'): + # Apply DefaultMiddleware only if no other middleware has processed the request + # and no specific middleware returned an error response. + if not middleware_processed_request and not middleware_response: + try: logger.debug("Applying Default middleware") default_middleware = DefaultMiddleware() - default_middleware.invoke(r, settings) - except (json.JSONDecodeError, KeyError, TypeError) as e: - logger.error(f"Default Middleware Error: {str(e)}") - return Response(json.dumps({"error": f"Default Middleware error: {str(e)}"}), status=500, content_type="application/json") - - return None + # Assuming DefaultMiddleware.invoke might also set r.default_middleware_json or return a Response + # It should also return None if it just modifies r.default_middleware_json + default_middleware_response = default_middleware.invoke(r, settings) + if default_middleware_response: + return default_middleware_response + except Exception as e: + logger.error(f"Default Middleware Error: {str(e)}", exc_info=True) + return Response(json.dumps({"error": f"Default Middleware error: {str(e)}"}), + status=500, content_type="application/json") + + return None # If all middlewares passed (returned None) and did not error def validate_api_key(r: Request, settings: Mapping) -> Optional[Response]: """ diff --git a/middlewares/folo_middleware.py b/middlewares/folo_middleware.py new file mode 100644 index 0000000..c3dabe4 --- /dev/null +++ b/middlewares/folo_middleware.py @@ -0,0 +1,85 @@ +import json +import logging +from werkzeug import Request, Response +from typing import Optional + +logger = logging.getLogger(__name__) + +class FoloMiddleware: + def __init__(self): + """ + Initializes the FoloMiddleware. + Currently, it does not require specific settings from the main config. + """ + pass + + def invoke(self, r: Request) -> Optional[Response]: + """ + Processes the request if it's a Folo webhook. + - Parses the Folo payload. + - Transforms it into the Dify-expected format with query="start" + and specific fields mapped to inputs. + - Stores the transformed payload in r.default_middleware_json. + + Returns: + - A Response object if an error occurs during processing (e.g., invalid payload). + - None if the transformation is successful, allowing the main handler to proceed + using r.default_middleware_json. + """ + logger.debug("FoloMiddleware: Processing request...") + try: + folo_payload = r.get_json() + if not folo_payload or "entry" not in folo_payload: + logger.error("FoloMiddleware: Invalid or missing 'entry' in Folo payload.") + return Response( + json.dumps({"error": "Folo middleware: Invalid payload, 'entry' field is missing."}), + status=400, + content_type="application/json" + ) + + entry = folo_payload.get("entry", {}) + + # Extract required fields from the Folo 'entry' object + title = entry.get("title") + content = entry.get("content") + author = entry.get("author") + url = entry.get("url") + published_at = entry.get("publishedAt") + + # Construct the new payload for Dify + dify_inputs = { + "title": title, + "content": content, + "author": author, + "url": url, + "publishedAt": published_at + } + + # Add debug logging for extracted inputs + logger.debug(f"FoloMiddleware: Extracted inputs for Dify: {dify_inputs}") + + dify_payload = { + "query": "start", + "inputs": dify_inputs + } + + # Store the transformed payload in the request object for the main endpoint handler + r.default_middleware_json = dify_payload + logger.info("FoloMiddleware: Payload transformed successfully and stored in r.default_middleware_json.") + + return None # Indicate successful transformation + + except json.JSONDecodeError as e: + logger.error(f"FoloMiddleware: JSONDecodeError while parsing Folo payload - {str(e)}") + return Response( + json.dumps({"error": f"Folo middleware: Invalid JSON payload - {str(e)}"}), + status=400, + content_type="application/json" + ) + except Exception as e: + logger.error(f"FoloMiddleware: Unexpected error during processing - {str(e)}", exc_info=True) + return Response( + json.dumps({"error": f"Folo middleware: Unexpected error - {str(e)}"}), + status=500, + content_type="application/json" + ) From 5a0adb9969153c28d61738cea50eb4d865c8e611 Mon Sep 17 00:00:00 2001 From: LogicOber <133167749+LogicOber@users.noreply.github.com> Date: Sat, 24 May 2025 22:21:12 +0100 Subject: [PATCH 3/4] chore: bump version from 0.5.1 to 0.5.2 --- manifest.yaml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.yaml b/manifest.yaml index a344baf..cbf41bb 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,4 +1,4 @@ -version: 0.5.1 +version: 0.5.2 type: plugin author: perzeuss name: webhook diff --git a/requirements.txt b/requirements.txt index e37e776..2ec1f54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ certifi==2025.1.31 cffi==1.17.1 charset-normalizer==3.4.1 click==8.1.8 -dify_plugin>=0.1.0,<0.2.0 +dify_plugin==0.0.1b72 dpkt==1.9.8 Flask==3.0.3 gevent==24.11.1 From a96e00e7b63fba70f67ad84811f3ea3345ea6af0 Mon Sep 17 00:00:00 2001 From: LogicOber <133167749+LogicOber@users.noreply.github.com> Date: Sat, 24 May 2025 22:46:18 +0100 Subject: [PATCH 4/4] feat: add description field to Dify webhook payload mapping --- middlewares/folo_middleware.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/middlewares/folo_middleware.py b/middlewares/folo_middleware.py index c3dabe4..d73485d 100644 --- a/middlewares/folo_middleware.py +++ b/middlewares/folo_middleware.py @@ -45,6 +45,7 @@ def invoke(self, r: Request) -> Optional[Response]: author = entry.get("author") url = entry.get("url") published_at = entry.get("publishedAt") + description = entry.get("description") # Construct the new payload for Dify dify_inputs = { @@ -52,7 +53,8 @@ def invoke(self, r: Request) -> Optional[Response]: "content": content, "author": author, "url": url, - "publishedAt": published_at + "publishedAt": published_at, + "description": description } # Add debug logging for extracted inputs