Skip to content

Commit c7d3a60

Browse files
committed
Sirbot: Add /admin & /snippet slack command
1 parent dd2c2b8 commit c7d3a60

File tree

6 files changed

+135
-7
lines changed

6 files changed

+135
-7
lines changed

pyslackersweb/sirbot/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from aiohttp import web
22

3-
from .views import routes
3+
from pyslackersweb.sirbot.views import readthedocs, slack
44

55

66
async def app_factory() -> web.Application:
@@ -11,5 +11,6 @@ async def app_factory() -> web.Application:
1111
scheduler=None, # populated via parent app signal
1212
)
1313

14-
sirbot.add_routes(routes)
14+
readthedocs.add_routes(sirbot.router)
15+
slack.add_routes(sirbot.router)
1516
return sirbot

pyslackersweb/sirbot/settings.py

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# production
44
READTHEDOCS_NOTIFICATION_CHANNEL = "community_projects"
5+
SLACK_TEAM_ID = os.environ.get("SLACK_TEAM_ID")
6+
SLACK_VERIFICATION_TOKEN = os.environ.get("SLACK_VERIFICATION_TOKEN")
57

68
# Development
79
if os.environ.get("PLATFORM_BRANCH") != "master":

pyslackersweb/sirbot/views.py renamed to pyslackersweb/sirbot/views/readthedocs.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
from slack.events import Message
66

77
from pyslackersweb.util.log import ContextAwareLoggerAdapter
8-
9-
from . import settings
8+
from pyslackersweb.sirbot import settings
109

1110
logger = ContextAwareLoggerAdapter(logging.getLogger(__name__))
1211

13-
routes = web.RouteTableDef()
12+
13+
def add_routes(router):
14+
router.add_view("/readthedocs", ReadTheDocsView)
1415

1516

16-
@routes.view("/readthedocs", name="readthedocs")
1717
class ReadTheDocsView(web.View):
1818
async def post(self):
1919
payload = await self.request.json()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import slack.commands
2+
3+
from aiohttp import web
4+
5+
from pyslackersweb.sirbot import settings
6+
7+
from . import commands
8+
9+
10+
def add_routes(router: web.UrlDispatcher) -> None:
11+
router.add_view("/slack/commands", SlackCommandsView)
12+
13+
14+
class SlackCommandsView(web.View):
15+
router = slack.commands.Router()
16+
router.register("/admin", commands.admin)
17+
router.register("/snippet", commands.snippet)
18+
19+
async def post(self) -> web.Response:
20+
payload = await self.request.json()
21+
cmd = slack.commands.Command(
22+
payload,
23+
verification_token=settings.SLACK_VERIFICATION_TOKEN,
24+
team_id=settings.SLACK_TEAM_ID,
25+
)
26+
for handler in self.router.dispatch(cmd):
27+
await handler(self.request, cmd)
28+
return web.Response(status=200)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import logging
2+
3+
from aiohttp import web
4+
from slack import methods
5+
from slack import commands
6+
from slack.events import Message
7+
8+
from pyslackersweb.util.log import ContextAwareLoggerAdapter
9+
10+
logger = ContextAwareLoggerAdapter(logging.getLogger(__name__))
11+
12+
13+
async def admin(request: web.Request, command: commands.Command) -> None:
14+
data = {
15+
"trigger_id": command["trigger_id"],
16+
"dialog": {
17+
"callback_id": "tell_admin",
18+
"title": "Message the admin team",
19+
"elements": [
20+
{
21+
"label": "Message",
22+
"name": "message",
23+
"type": "textarea",
24+
"value": command["text"],
25+
}
26+
],
27+
},
28+
}
29+
await request.app["slack_client"].query(url=methods.DIALOG_OPEN, data=data)
30+
31+
32+
async def snippet(request: web.Request, command: commands.Command) -> None:
33+
"""Post a message to the current channel about using snippets and backticks to visually
34+
format code."""
35+
response = Message()
36+
response["channel"] = command["channel_id"]
37+
response["unfurl_links"] = False
38+
39+
response["text"] = (
40+
"Please use the snippet feature, or backticks, when sharing code. \n"
41+
"To include a snippet, click the :paperclip: on the left and hover over "
42+
"`Create new...` then select `Code or text snippet`.\n"
43+
"By wrapping the text/code with backticks (`) you get:\n"
44+
"`text formatted like this`\n"
45+
"By wrapping a multiple line block with three backticks (```) you can get:\n"
46+
)
47+
48+
await request.app["slack_client"].query(url=methods.CHAT_POST_MESSAGE, data=response)
49+
50+
response["text"] = (
51+
"```\n"
52+
"A multiline codeblock\nwhich is great for short snippets!\n"
53+
"```\n"
54+
"For more information on snippets, click "
55+
"<https://get.slack.help/hc/en-us/articles/204145658-Create-a-snippet|here>.\n"
56+
"For more information on inline code formatting with backticks click "
57+
"<https://get.slack.help/hc/en-us/articles/202288908-Format-your-messages#inline-code|here>."
58+
)
59+
60+
await request.app["slack_client"].query(url=methods.CHAT_POST_MESSAGE, data=response)

tests/test_sirbot.py

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
from slack import methods
24

35
from pyslackersweb.sirbot import settings
@@ -8,7 +10,7 @@ async def test_readthedocs_notification(client):
810
assert r.status == 200
911

1012
# Assert we send a message to slack
11-
client.app["slack_client"]._request.assert_called_once()
13+
assert client.app["slack_client"]._request.call_count == 1
1214
assert methods.CHAT_POST_MESSAGE.value[0] in client.app["slack_client"]._request.call_args.args
1315
assert (
1416
settings.READTHEDOCS_NOTIFICATION_CHANNEL
@@ -22,3 +24,38 @@ async def test_readthedocs_notification_missing_name(client):
2224

2325
# Assert we did not send a message to slack
2426
assert not client.app["slack_client"]._request.called
27+
28+
29+
async def test_slack_admin_command(client):
30+
payload = {
31+
"command": "/admin",
32+
"trigger_id": "TEST-TRIGGER-ID",
33+
"channel_id": "TEST-CHANNEL-ID",
34+
"text": "TEST-TEXT",
35+
}
36+
r = await client.post("/bot/slack/commands", json=payload)
37+
assert r.status == 200
38+
assert client.app["slack_client"]._request.call_count == 1
39+
assert methods.DIALOG_OPEN.value[0] in client.app["slack_client"]._request.call_args.args
40+
41+
data = json.loads(client.app["slack_client"]._request.call_args.args[3])
42+
assert data["trigger_id"] == "TEST-TRIGGER-ID"
43+
assert data["dialog"]["callback_id"] == "tell_admin"
44+
45+
46+
async def test_slack_snippet_command(client):
47+
payload = {
48+
"command": "/snippet",
49+
"trigger_id": "TEST-TRIGGER-ID",
50+
"channel_id": "TEST-CHANNEL-ID",
51+
"text": "TEST-TEXT",
52+
}
53+
r = await client.post("/bot/slack/commands", json=payload)
54+
assert r.status == 200
55+
56+
assert client.app["slack_client"]._request.call_count == 2
57+
58+
for call in client.app["slack_client"]._request.calls:
59+
assert methods.CHAT_POST_MESSAGE.value[0] in call.args
60+
data = json.loads(call.args[3])
61+
assert data["channel_id"] == "TEST-CHANNEL-ID"

0 commit comments

Comments
 (0)