]> git.cworth.org Git - turbot/blobdiff - turbot.py
Convert "make run" to use "flask run" instead of direct python script execution
[turbot] / turbot.py
index 1cb6e68032d427038753232c6ab85d820651acd1..5095d2a39de779b82f5879a00c062b112a7bb753 100755 (executable)
--- a/turbot.py
+++ b/turbot.py
 #!/usr/bin/env python3
 
-from flask import Flask
-from flask_restful import reqparse, abort, Api, Resource
+from flask import Flask, request, make_response
+
+from slackeventsapi import SlackEventAdapter
+from slack import WebClient
+from slack.errors import SlackApiError
+from slack.signature import SignatureVerifier
+import os
+import requests
+import re
 
 app = Flask(__name__)
-api = Api(app)
 
-TODOS = {}
+slack_signing_secret = os.environ['SLACK_SIGNING_SECRET']
+slack_bot_token = os.environ['SLACK_BOT_TOKEN']
+
+slack_events = SlackEventAdapter(slack_signing_secret, "/slack/events", app)
+signature_verifier = SignatureVerifier(slack_signing_secret)
+slack_client = WebClient(slack_bot_token)
+
+def rot_string(str, n=13):
+    """Return a rotated version of a string
+
+    Specifically, this functions returns a version of the input string
+    where each uppercase letter has been advanced 'n' positions in the
+    alphabet (wrapping around). Lowercase letters and any non-alphabetic
+    characters will be unchanged."""
 
-def abort_if_todo_doesnt_exist(todo_id):
-    if todo_id not in TODOS:
-        abort(404, message="Todo {} doesn't exist".format(todo_id))
+    result = ''
+    for letter in str:
+        if letter.isupper():
+            result += chr(ord("A") + (ord(letter) - ord("A") + n) % 26)
+        else:
+            result += letter
+    return result
 
-parser = reqparse.RequestParser()
-parser.add_argument('task')
+@app.route('/rot', methods = ['POST'])
+def rot():
+    """Implements the /rot route for the /rot slash command in Slack
 
-class Todo(Resource):
-    def get(self, todo_id):
-        abort_if_todo_doesnt_exist(todo_id)
-        return TODOS[todo_id]
+    This implements the /rot command of our Slack bot. The format of this
+    command is as follows:
 
-    def delete(self, todo_id):
-        abort_if_todo_doesnt_exist(todo_id)
-        del TODOS[todo_id]
-        return '', 204
+        /rot [count|*] String to be rotated
 
-    def put(self, todo_id):
-        args = parser.parse_args()
-        task = {'task': args['task']}
-        TODOS[todo_id] = task
-        return task, 201
+    The optional count indicates an amount to rotate each character in the
+    string. If the count is '*' or is not present, then the string will
+    be rotated through all possible 25 values.
 
-class TodoList(Resource):
-    def get(self):
-        return TODOS
+    The result of the rotation is provided as a message in Slack. If the
+    slash command was issued in a direct message, the response is made by
+    using the "response_url" from the request. This allows the bot to reply
+    in a direct message that it is not a member of. Otherwise, if the slash
+    command was issued in a channel, the bot will reply in that channel."""
 
-    def post(self):
-        args = parser.parse_args()
+    data = request.get_data()
+    headers = request.headers
+    response_url = request.form.get('response_url')
+    channel_name = request.form.get('channel_name')
+    channel = request.form.get('channel_id')
+    query = request.form.get('text')
+
+    if not signature_verifier.is_valid_request(data, headers):
+        return make_response("invalid request", 403)
+
+    match = re.match(r'^([0-9]+|\*) (.*)$', query)
+    if (match):
         try:
-            todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
-        except:
-            todo_id = 1
-        todo_id = 'todo%i' % todo_id
-        TODOS[todo_id] = {'task': args['task']}
-        return TODOS[todo_id], 201
-
-api.add_resource(TodoList, '/todos')
-api.add_resource(Todo, '/todos/<todo_id>')
-
-if __name__ == '__main__':
-    app.run(debug=True)
+            count = int(match.group(1))
+        except ValueError:
+            count = None
+        text = match.group(2)
+    else:
+        count = None
+        text = query
+
+    text = text.upper()
+
+    reply = "```/rot {} {}\n".format(count if count else '*', text)
+
+    if count:
+        reply += rot_string(text, count)
+    else:
+        reply += "\n".join(["{:02d} ".format(count) + rot_string(text, count)
+                            for count in range(1, 26)])
+
+    reply += "```"
+
+    if (channel_name == "directmessage"):
+        resp = requests.post(response_url,
+                             json = {"text": reply},
+                             headers = {"Content-type": "application/json"})
+        if (resp.status_code != 200):
+            app.logger.error("Error posting request to Slack: " + resp.text)
+    else:
+        try:
+            slack_client.chat_postMessage(channel=channel, text=reply)
+        except SlackApiError as e:
+            app.logger.error("Slack API error: " + e.response["error"])
+    return ""
+
+@slack_events.on("error")
+def handle_error(error):
+    app.logger.error("Error from Slack: " + str(error))