|
1 | 1 | import time |
2 | | -from typing import Union |
| 2 | +from typing import Union, Dict, Any, Optional |
3 | 3 |
|
4 | 4 | from slack_sdk.web import SlackResponse |
5 | 5 |
|
6 | 6 | from slack_bolt.request import BoltRequest |
7 | | - |
| 7 | +from slack_bolt.request.payload_utils import ( |
| 8 | + is_action, |
| 9 | + is_event, |
| 10 | + is_options, |
| 11 | + is_shortcut, |
| 12 | + is_slash_command, |
| 13 | + is_view, |
| 14 | + is_workflow_step_edit, |
| 15 | + is_workflow_step_save, |
| 16 | + is_workflow_step_execute, |
| 17 | +) |
8 | 18 |
|
9 | 19 | # ------------------------------- |
10 | 20 | # Error |
@@ -94,10 +104,185 @@ def warning_unhandled_by_global_middleware( # type: ignore |
94 | 104 | ) |
95 | 105 |
|
96 | 106 |
|
| 107 | +_unhandled_request_suggestion_prefix = """ |
| 108 | +--- |
| 109 | +[Suggestion] You can handle this type of event with the following listener function: |
| 110 | +""" |
| 111 | + |
| 112 | + |
| 113 | +def _build_filtered_body(body: Optional[Dict[str, Any]]) -> dict: |
| 114 | + if body is None: |
| 115 | + return {} |
| 116 | + |
| 117 | + payload_type = body.get("type") |
| 118 | + filtered_body = {"type": payload_type} |
| 119 | + |
| 120 | + if "view" in body: |
| 121 | + view = body["view"] |
| 122 | + # view_submission, view_closed, workflow_step_save |
| 123 | + filtered_body["view"] = { |
| 124 | + "type": view.get("type"), |
| 125 | + "callback_id": view.get("callback_id"), |
| 126 | + } |
| 127 | + |
| 128 | + if payload_type == "block_actions": |
| 129 | + # Block Kit Interactivity |
| 130 | + actions = body.get("actions", []) |
| 131 | + if len(actions) > 0 and actions[0] is not None: |
| 132 | + filtered_body["block_id"] = actions[0].get("block_id") |
| 133 | + filtered_body["action_id"] = actions[0].get("action_id") |
| 134 | + if payload_type == "block_suggestion": |
| 135 | + # Block Kit - external data source |
| 136 | + filtered_body["block_id"] = body.get("block_id") |
| 137 | + filtered_body["action_id"] = body.get("action_id") |
| 138 | + filtered_body["value"] = body.get("value") |
| 139 | + |
| 140 | + if payload_type == "event_callback" and "event" in body: |
| 141 | + # Events API, workflow_step_execute |
| 142 | + event_payload = body.get("event", {}) |
| 143 | + filtered_event = {"type": event_payload.get("type")} |
| 144 | + if "subtype" in body["event"]: |
| 145 | + filtered_event["subtype"] = event_payload.get("subtype") |
| 146 | + filtered_body["event"] = filtered_event |
| 147 | + |
| 148 | + if "command" in body: |
| 149 | + # Slash Commands |
| 150 | + filtered_body["command"] = body.get("command") |
| 151 | + |
| 152 | + if payload_type in ["workflow_step_edit", "shortcut", "message_action"]: |
| 153 | + # Workflow Steps, Global Shortcuts, Message Shortcuts |
| 154 | + filtered_body["callback_id"] = body.get("callback_id") |
| 155 | + |
| 156 | + if payload_type == "interactive_message": |
| 157 | + # Actions in Attachments |
| 158 | + filtered_body["callback_id"] = body.get("callback_id") |
| 159 | + filtered_body["actions"] = body.get("actions") |
| 160 | + |
| 161 | + if payload_type == "dialog_suggestion": |
| 162 | + # Dialogs - external data source |
| 163 | + filtered_body["callback_id"] = body.get("callback_id") |
| 164 | + filtered_body["value"] = body.get("value") |
| 165 | + if payload_type == "dialog_submission": |
| 166 | + # Dialogs - clicking submit button |
| 167 | + filtered_body["callback_id"] = body.get("callback_id") |
| 168 | + filtered_body["submission"] = body.get("submission") |
| 169 | + if payload_type == "dialog_cancellation": |
| 170 | + # Dialogs - clicking cancel button |
| 171 | + filtered_body["callback_id"] = body.get("callback_id") |
| 172 | + |
| 173 | + return filtered_body |
| 174 | + |
| 175 | + |
| 176 | +def _build_unhandled_request_suggestion(default_message: str, code_snippet: str): |
| 177 | + return f"""{default_message}{_unhandled_request_suggestion_prefix}{code_snippet}""" |
| 178 | + |
| 179 | + |
97 | 180 | def warning_unhandled_request( # type: ignore |
98 | 181 | req: Union[BoltRequest, "AsyncBoltRequest"], # type: ignore |
99 | 182 | ) -> str: # type: ignore |
100 | | - return f"Unhandled request ({req.body})" |
| 183 | + filtered_body = _build_filtered_body(req.body) |
| 184 | + default_message = f"Unhandled request ({filtered_body})" |
| 185 | + if ( |
| 186 | + is_workflow_step_edit(req.body) |
| 187 | + or is_workflow_step_save(req.body) |
| 188 | + or is_workflow_step_execute(req.body) |
| 189 | + ): |
| 190 | + # @app.step |
| 191 | + callback_id = ( |
| 192 | + filtered_body.get("callback_id") |
| 193 | + or filtered_body.get("view", {}).get("callback_id") # type: ignore |
| 194 | + or "your-callback-id" |
| 195 | + ) |
| 196 | + return _build_unhandled_request_suggestion( |
| 197 | + default_message, |
| 198 | + f""" |
| 199 | +from slack_bolt.workflows.step import WorkflowStep |
| 200 | +ws = WorkflowStep( |
| 201 | + callback_id="{callback_id}", |
| 202 | + edit=edit, |
| 203 | + save=save, |
| 204 | + execute=execute, |
| 205 | +) |
| 206 | +# Pass Step to set up listeners |
| 207 | +app.step(ws) |
| 208 | +""", |
| 209 | + ) |
| 210 | + if is_action(req.body): |
| 211 | + # @app.action |
| 212 | + action_id_or_callback_id = req.body.get("callback_id") |
| 213 | + if req.body.get("type") == "block_actions": |
| 214 | + action_id_or_callback_id = req.body.get("actions")[0].get("action_id") |
| 215 | + return _build_unhandled_request_suggestion( |
| 216 | + default_message, |
| 217 | + f""" |
| 218 | +@app.action("{action_id_or_callback_id}") |
| 219 | +def handle_some_action(ack, body, logger): |
| 220 | + ack() |
| 221 | + logger.info(body) |
| 222 | +""", |
| 223 | + ) |
| 224 | + if is_options(req.body): |
| 225 | + # @app.options |
| 226 | + constraints = '"action-id"' |
| 227 | + if req.body.get("action_id") is not None: |
| 228 | + constraints = '"' + req.body.get("action_id") + '"' |
| 229 | + elif req.body.get("type") == "dialog_suggestion": |
| 230 | + constraints = f"""{{"type": "dialog_suggestion", "callback_id": "{req.body.get('callback_id')}"}}""" |
| 231 | + return _build_unhandled_request_suggestion( |
| 232 | + default_message, |
| 233 | + f""" |
| 234 | +@app.options({constraints}) |
| 235 | +def handle_some_options(ack): |
| 236 | + ack(options=[ ... ]) |
| 237 | +""", |
| 238 | + ) |
| 239 | + if is_shortcut(req.body): |
| 240 | + # @app.shortcut |
| 241 | + id = req.body.get("action_id") or req.body.get("callback_id") |
| 242 | + return _build_unhandled_request_suggestion( |
| 243 | + default_message, |
| 244 | + f""" |
| 245 | +@app.shortcut("{id}") |
| 246 | +def handle_shortcuts(ack, body, logger): |
| 247 | + ack() |
| 248 | + logger.info(body) |
| 249 | +""", |
| 250 | + ) |
| 251 | + if is_view(req.body): |
| 252 | + # @app.view |
| 253 | + return _build_unhandled_request_suggestion( |
| 254 | + default_message, |
| 255 | + f""" |
| 256 | +@app.view("{req.body.get('view', {}).get('callback_id', 'modal-view-id')}") |
| 257 | +def handle_view_events(ack, body, logger): |
| 258 | + ack() |
| 259 | + logger.info(body) |
| 260 | +""", |
| 261 | + ) |
| 262 | + if is_event(req.body): |
| 263 | + # @app.event |
| 264 | + event_type = req.body.get("event", {}).get("type") |
| 265 | + return _build_unhandled_request_suggestion( |
| 266 | + default_message, |
| 267 | + f""" |
| 268 | +@app.event("{event_type}") |
| 269 | +def handle_{event_type}_events(body, logger): |
| 270 | + logger.info(body) |
| 271 | +""", |
| 272 | + ) |
| 273 | + if is_slash_command(req.body): |
| 274 | + # @app.command |
| 275 | + command = req.body.get("command", "/your-command") |
| 276 | + return _build_unhandled_request_suggestion( |
| 277 | + default_message, |
| 278 | + f""" |
| 279 | +@app.command("{command}") |
| 280 | +def handle_some_command(ack, body, logger): |
| 281 | + ack() |
| 282 | + logger.info(body) |
| 283 | +""", |
| 284 | + ) |
| 285 | + return default_message |
101 | 286 |
|
102 | 287 |
|
103 | 288 | def warning_did_not_call_ack(listener_name: str) -> str: |
|
0 commit comments