]> git.cworth.org Git - turbot/blob - turbot.py
2b9462fff79a6399743a915d5d40ae95291c3937
[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 slack_is_valid_request(request):
23     """Returns true if request actually came from Slack.
24
25     By means of checking the requests signature together with the slack
26     signing key.
27
28     Note: If flask is in debug mode, this function will always return true."""
29
30     if app.debug:
31         return True
32
33     data = request.get_data()
34     headers = request.headers
35
36     return signature_verifier.is_valid_request(data, headers)
37
38 def slack_send_reply(request, text):
39     """Send a Slack message as a reply to a specified request.
40
41     If the request is associated with a direct message, the reply is
42     made by using the "response_url" from the request. Otherwise, the
43     reply will be sent to the channel associated with the request.
44
45     Note: If flask is in debug mode, this function will just print the
46     text to stdout."""
47
48     channel_name = request.form.get('channel_name')
49     response_url = request.form.get('response_url')
50     channel = request.form.get('channel_id')
51
52     if (app.debug):
53         print("Sending message to channel '{}': {}".format(channel, text))
54         return
55
56     if (channel_name == "directmessage"):
57         resp = requests.post(response_url,
58                              json = {"text": text},
59                              headers = {"Content-type": "application/json"})
60         if (resp.status_code != 200):
61             app.logger.error("Error posting request to Slack: " + resp.text)
62     else:
63         try:
64             slack_client.chat_postMessage(channel=channel, text=text)
65         except SlackApiError as e:
66             app.logger.error("Slack API error: " + e.response["error"])
67
68 def rot_string(str, n=13):
69     """Return a rotated version of a string
70
71     Specifically, this functions returns a version of the input string
72     where each uppercase letter has been advanced 'n' positions in the
73     alphabet (wrapping around). Lowercase letters and any non-alphabetic
74     characters will be unchanged."""
75
76     result = ''
77     for letter in str:
78         if letter.isupper():
79             result += chr(ord("A") + (ord(letter) - ord("A") + n) % 26)
80         else:
81             result += letter
82     return result
83
84 @app.route('/rot', methods = ['POST'])
85 def rot():
86     """Implements the /rot route for the /rot slash command in Slack
87
88     This implements the /rot command of our Slack bot. The format of this
89     command is as follows:
90
91         /rot [count|*] String to be rotated
92
93     The optional count indicates an amount to rotate each character in the
94     string. If the count is '*' or is not present, then the string will
95     be rotated through all possible 25 values.
96
97     The result of the rotation is provided as a message in Slack. If the
98     slash command was issued in a direct message, the response is made by
99     using the "response_url" from the request. This allows the bot to reply
100     in a direct message that it is not a member of. Otherwise, if the slash
101     command was issued in a channel, the bot will reply in that channel."""
102
103     if not slack_is_valid_request(request):
104         return make_response("invalid request", 403)
105
106     query = request.form.get('text')
107     match = re.match(r'^([0-9]+|\*) (.*)$', query)
108     if (match):
109         try:
110             count = int(match.group(1))
111         except ValueError:
112             count = None
113         text = match.group(2)
114     else:
115         count = None
116         text = query
117
118     text = text.upper()
119
120     reply = "```/rot {} {}\n".format(count if count else '*', text)
121
122     if count:
123         reply += rot_string(text, count)
124     else:
125         reply += "\n".join(["{:02d} ".format(count) + rot_string(text, count)
126                             for count in range(1, 26)])
127
128     reply += "```"
129
130     slack_send_reply(request, reply)
131
132     return ""
133
134 @slack_events.on("error")
135 def handle_error(error):
136     app.logger.error("Error from Slack: " + str(error))