uhavin / slackers Goto Github PK
View Code? Open in Web Editor NEWSlack webhooks API served by FastAPI
License: MIT License
Slack webhooks API served by FastAPI
License: MIT License
Hello, could you please help me to create some examples how to test handlers?
eg.
This is a handler using a save_text
function, I know I cam simply test save_text
and other parts.
from fastapi import FastAPI
from slackers.hooks import commands
from slackers.models import SlackCommand
from slackers.server import router
app = FastAPI(debug=True)
app.include_router(router)
app.include_router(router, prefix="/slack")
async def save_text(text: str):
with open("command_log.txt", "a+") as outf:
print(text, file=outf)
@commands.on("cmd")
async def handle_command(payload):
cmd = SlackCommand(**payload).text
# .. more complex parsing of cmd in between
await save_text(cmd)
This is my test:
from fastapi.testclient import TestClient
def test_shout_command( client: TestClient):
command_data = {
"token": "1234erfghjuiop",
"command": "/cmd",
"response_url": "https://hooks.slack.com/commands/ABCDEFG/1289665550278/6RQ7UIUXhMzAFXwApOPiMB1o",
"trigger_id": "1296602173907.524169929122.0fa6d05ec3b37886607b56011efcbc9d",
"user_id": "UFE4ZTC8J",
"user_name": "1user1",
"team_id": "TFE4ZTB3L",
"channel_id": "D0184EACFNK",
"text": "to <@UJ03ECZLG|1user1_1> <@UJ03E1ESU|1user1_2> for less",
}
head = {
"x-slack-signature": "mySecret",
"x-slack-request-timestamp": "1531420618"
}
resp = client.post("/slack/commands", data=command_data, headers=head)
print(resp)
assert resp.ok
But response I receive is 403
.
Could you please recommend how can I test handle_command
with pytest?
Thanks for this great library! I was able to process actions but I'm having issues with custom responses. I have my code that looks as follows:
@actions.on("block_actions:approve_workflow")
def handle_workflow_unpause_action(payload):
log.info("Action started.")
log.debug(payload)
print("action: ", payload)
@responder("block_actions:approve_workflow")
def respond_to_workflow_unpause_action(payload):
log.info("Response.")
log.debug(payload)
print("response: ", payload)
user = payload["user"]["username"]
return JSONResponse(
{
"replace_original": "true",
"text": f"Workflow approved by @{user}",
}
)
The issue is I'm expecting the original block_action
to be replaced by the message Workflow approved by {user}
. But this doesn't seem to be happening. Is there something I'm missing? Thanks for your help and providing this library!
I try to receive actions by callback_id but it only reaches /actions witch does nothing but to return a 200 Ok response :
@commands.on("newreq3")
def handle_command(payload):
print("Command received")
print(payload)
slack_web_client.api_call(
api_method="dialog.open",
json= {
"trigger_id": payload["trigger_id"],
"dialog" : {
"title": "Nouvelle permission",
"submit_label": "Envoyer",
"callback_id": "newreqc",
[...]
@actions.on("block_actions:newreqc")
def handle_action(payload):
print("Action started.")
print(payload)
@actions.on("block_actions")
def handle_action2(payload):
slack_web_client.chat_postMessage(
channel='C01EWV6DL9K',
text="Asking for permission ?")
print(payload)
So the dialog box well open when launching command, the submit button sends a callback on /actions only where I think it should send a callback on /actions/newreqc.
Here with your stack I can't see what's inside the callback since I didn't succeed in catching it.
INFO: 3.90.1.212:0 - "POST /commands HTTP/1.0" 200 OK
Command received
{'token': '---------', 'command': '/newreq3', 'response_url': 'https://hooks.slack.com/commands/T01F673DS04/1524134228434/ib7GScKFh45MqXa9juInAZnW', 'trigger_id': '1517403300102.1516241468004.4231bd72bc1b564c08851f55f5947949', 'user_id': 'U01ETA7UDJA', 'user_name': 'gs', 'team_id': 'T01F673DS04', 'channel_id': 'C01EWV6DL9K', 'text': ''}
INFO: 54.198.138.148:0 - "POST /actions HTTP/1.0" 200 OK
Hi @uhavin, I would like to bring up this issue regarding input validation for view_submission
actions.
Modals provide the ability for developers to add input blocks to which users can respond to. If a developer were to attempt to validate the inputs sent through a view_submission
action, they can follow the instructions detailed here.
Basically, the developer could respond with this payload:
{
"response_action": "errors",
"errors": {
"<block_id>": "Some text here"
}
}
However, using this library, currently it is not possible for us to respond with a custom object as such (if it is possible, do enlighten me on how to execute it). From what I know, this part of the code will be sent first before any custom code is executed:
slackers/src/slackers/server.py
Line 61 in ede0cdb
Due to its empty body, Slack's server will assume that everything went fine and the modal will just be closed without any further follow-up. Thus, any object that is going to be returned after that will be ignored.
Is it possible to somehow modify the aforementioned part of the code to enable developers to provide input validation? Perhaps some kind of option to write a custom object that overrides the default one? Another idea would be to include a try
clause. Thank you!
Hi! Really appreciate your setup for using Fastapi for the slack bot. I was setting this up myself, replicating all the code.
When clicking a button in a slack message, the following events are printed (printing the variable event) in the emit function in hooks.py:
block_actions
block_actions:et6Dk
block_actions:button
Hence, the action response from slack is received. I'm testing this locally using ngrok. I have created this action listener:
# Listening for the action type.
@actions.on("block_actions")
def handle_action(payload):
log.info("Action started.")
log.debug(payload)
However, when clicking the button, I don't see anything being registered to the log. Shouldn't the function above be called? Is the log not being registered somehow since it is within an async process?
I'm running the starting the fast API server local by calling:
python3 -m uvicorn PROJECTNAME.main:app --reload
In the http tests, hmac is mocked and the signatures all evaulate to valid. There are tests with unmocked hmac, but they only touch the failing scenarios.
There really should be a test that verifies signatures.
Hello there!
I've found an issue. When I have an action type being interactive_message
, the data I receive doesn't contain an action_id
, only name
, type
and selected_options
, therefore I receive this error:
INFO: 34.207.162.7:0 - "POST /slack/actions HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/fastapi/applications.py", line 149, in __call__
await super().__call__(scope, receive, send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/applications.py", line 102, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/routing.py", line 550, in __call__
await route.handle(scope, receive, send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/fastapi/routing.py", line 196, in app
raw_response = await run_endpoint_function(
File "/home/marcelo/anaconda3/envs/coinprice/lib/python3.8/site-packages/fastapi/routing.py", line 148, in run_endpoint_function
return await dependant.call(**values)
File "/home/marcelo/Development/fastapi/slackers/src/slackers/server.py", line 47, in post_actions
triggered_events = [
File "/home/marcelo/Development/fastapi/slackers/src/slackers/server.py", line 48, in <listcomp>
f"{action.type}:{triggered_action['action_id']}"
Can someone help me?
Changing the conditionals on the server.actions
solves the problem:
@router.post(
"/actions",
status_code=HTTP_200_OK,
dependencies=[Depends(verify_signature), Depends(check_timeout)],
)
async def post_actions(request: Request) -> Response:
form = await request.form()
form_data = json.loads(form["payload"])
# have the convenience of pydantic validation
action = SlackAction(**form_data)
_events = [action.type]
if action.callback_id:
_events.append(f"{action.type}:{action.callback_id}")
elif action.actions:
triggered_events = [
f"{action.type}:{triggered_action['action_id']}"
for triggered_action in action.actions
]
_events.extend(triggered_events)
@uhavin are you still maintaining this package?
We're trying to incorporate this library into an FAQ API we've built on top of FastAPI.
When we try to enable the endpoint via Slack, the response is an HTTP error.
Here is the output from uvicorn:
INFO: 44.195.79.65:52938 - "POST /slack/events HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/usr/local/lib/python3.8/dist-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/fastapi/applications.py", line 199, in __call__
await super().__call__(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/starlette/applications.py", line 112, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.8/dist-packages/starlette/middleware/cors.py", line 78, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/usr/local/lib/python3.8/dist-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 580, in __call__
await route.handle(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 241, in handle
await self.app(scope, receive, send)
File "/usr/local/lib/python3.8/dist-packages/starlette/routing.py", line 52, in app
response = await func(request)
File "/usr/local/lib/python3.8/dist-packages/fastapi/routing.py", line 191, in app
solved_result = await solve_dependencies(
File "/usr/local/lib/python3.8/dist-packages/fastapi/dependencies/utils.py", line 548, in solve_dependencies
solved = await call(**sub_values)
File "/usr/local/lib/python3.8/dist-packages/slackers/verification.py", line 29, in verify_signature
os.environ.get("SLACK_SIGNING_SECRET").encode(), to_verify, sha256
AttributeError: 'NoneType' object has no attribute 'encode'
The routes appear on the FastAPI /docs page (screenshot attached)
For now, we've just included the very basics of the code implementation, which we understand should automatically handle the Slack Events requests. We've also confirmed that the env variables are set on the OS (Ubuntu).
from fastapi import FastAPI, HTTPException
import uvicorn
# SlackBot Imports
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from slackers.server import router
from slackers.hooks import actions
from slackers.hooks import responder
from slackers.hooks import events
from dotenv import load_dotenv
# Slack variables
load_dotenv()
signing_secret = os.environ.get("SLACK_SIGNING_SECRET")
slack_token = os.environ.get("SLACK_BOT_TOKEN")
app = FastAPI()
app.include_router(router, prefix='/slack')
Any help is greatly appreciated and please let me know if you need more info.
The slash command does not use a pydantic model.
Slack sends a timestamp in X-Slack-Request-Timestamp. There are no timeouts implemented to prevent replay attacks.
The format of the message that the MessageBuilder.dict returns, is good for the Slack API. The python slackclient however, implements chat_postMessage accepting a blocks parameter. It would be convenient to have a helper method on the message object, so we can use it like so
slack_web_client.chat_postMessage(channel="ABC", blocks=message.slack_blocks())
I am unable to process app_home_opened events, getting 422 errors. I am able to process 'message.im' events fine. Is there an issue with the incoming payloads on these two being different?
https://api.slack.com/events/message.im
{ "token": "one-long-verification-token", "team_id": "T061EG9R6", "api_app_id": "A0PNCHHK2", "event": { "type": "message", "channel": "D024BE91L", "user": "U2147483697", "text": "Hello hello can you hear me?", "ts": "1355517523.000005", "event_ts": "1355517523.000005", "channel_type": "im" }, "type": "event_callback", "authed_teams": [ "T061EG9R6" ], "event_id": "Ev0PV52K21", "event_time": 1355517523 }
https://api.slack.com/events/app_home_opened
{ "type": "app_home_opened", "user": "U061F7AUR", "channel": "D0LAN2Q65", "event_ts": "1515449522000016", "tab": "home", "view": { "id": "VPASKP233", "team_id": "T21312902", "type": "home", "blocks": [ ... ], "private_metadata": "", "callback_id": "", "state":{ ... }, "hash":"1231232323.12321312", "clear_on_close": false, "notify_on_close": false, "root_view_id": "VPASKP233", "app_id": "A21SDS90", "external_id": "", "app_installed_team_id": "T21312902", "bot_id": "BSDKSAO2" } }
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.