]> git.cworth.org Git - turbot/blob - turbot.py
Convert "make run" to use "flask run" instead of direct python script execution
[turbot] / turbot.py
1 #!/usr/bin/env python3
2
3 from flask import Flask, request, make_response
4
5 from slackeventsapi import SlackEventAdapter
6 from slack import WebClient
7 from slack.errors import SlackApiError
8 from slack.signature import SignatureVerifier
9 import os
10 import requests
11 import re
12
13 app = Flask(__name__)
14
15 slack_signing_secret = os.environ['SLACK_SIGNING_SECRET']
16 slack_bot_token = os.environ['SLACK_BOT_TOKEN']
17
18 slack_events = SlackEventAdapter(slack_signing_secret, "/slack/events", app)
19 signature_verifier = SignatureVerifier(slack_signing_secret)
20 slack_client = WebClient(slack_bot_token)
21
22 def rot_string(str, n=13):
23     """Return a rotated version of a string
24
25     Specifically, this functions returns a version of the input string
26     where each uppercase letter has been advanced 'n' positions in the
27     alphabet (wrapping around). Lowercase letters and any non-alphabetic
28     characters will be unchanged."""
29
30     result = ''
31     for letter in str:
32         if letter.isupper():
33             result += chr(ord("A") + (ord(letter) - ord("A") + n) % 26)
34         else:
35             result += letter
36     return result
37
38 @app.route('/rot', methods = ['POST'])
39 def rot():
40     """Implements the /rot route for the /rot slash command in Slack
41
42     This implements the /rot command of our Slack bot. The format of this
43     command is as follows:
44
45         /rot [count|*] String to be rotated
46
47     The optional count indicates an amount to rotate each character in the
48     string. If the count is '*' or is not present, then the string will
49     be rotated through all possible 25 values.
50
51     The result of the rotation is provided as a message in Slack. If the
52     slash command was issued in a direct message, the response is made by
53     using the "response_url" from the request. This allows the bot to reply
54     in a direct message that it is not a member of. Otherwise, if the slash
55     command was issued in a channel, the bot will reply in that channel."""
56
57     data = request.get_data()
58     headers = request.headers
59     response_url = request.form.get('response_url')
60     channel_name = request.form.get('channel_name')
61     channel = request.form.get('channel_id')
62     query = request.form.get('text')
63
64     if not signature_verifier.is_valid_request(data, headers):
65         return make_response("invalid request", 403)
66
67     match = re.match(r'^([0-9]+|\*) (.*)$', query)
68     if (match):
69         try:
70             count = int(match.group(1))
71         except ValueError:
72             count = None
73         text = match.group(2)
74     else:
75         count = None
76         text = query
77
78     text = text.upper()
79
80     reply = "```/rot {} {}\n".format(count if count else '*', text)
81
82     if count:
83         reply += rot_string(text, count)
84     else:
85         reply += "\n".join(["{:02d} ".format(count) + rot_string(text, count)
86                             for count in range(1, 26)])
87
88     reply += "```"
89
90     if (channel_name == "directmessage"):
91         resp = requests.post(response_url,
92                              json = {"text": reply},
93                              headers = {"Content-type": "application/json"})
94         if (resp.status_code != 200):
95             app.logger.error("Error posting request to Slack: " + resp.text)
96     else:
97         try:
98             slack_client.chat_postMessage(channel=channel, text=reply)
99         except SlackApiError as e:
100             app.logger.error("Slack API error: " + e.response["error"])
101     return ""
102
103 @slack_events.on("error")
104 def handle_error(error):
105     app.logger.error("Error from Slack: " + str(error))