Carl Worth [Fri, 23 Oct 2020 21:29:06 +0000 (14:29 -0700)]
Use blocks to send the welcome message
This approach has the benefit of allowing separate sections, (I tried
doing this with newlines in the 'text' parameter but they were getting
swallowed by Slack).
Carl Worth [Fri, 23 Oct 2020 12:54:27 +0000 (05:54 -0700)]
Cache SSM parameter values into environment variables
The goal here is to reduce SSM parameter reads/writes. Hopefully, as
AWS reuses a container for multiple calls to our Lambda function,
these values can be read from the environment instead of needing to
reach out to AWS.
The reason we want to reduce calls to SSM functions in that AWS Free
Tier gives us only 20,000 KMS requests compared to 1,000,000 AWS
Lambda requests.
Carl Worth [Thu, 22 Oct 2020 12:15:04 +0000 (05:15 -0700)]
Use absolute URLs to link to channels
This way we can just make the puzzle name a link rather than having
the noise of the channel name present in the view too, (which doesn't
actually add anything).
Carl Worth [Thu, 22 Oct 2020 10:36:51 +0000 (03:36 -0700)]
Drop the hunt_channel_id from the metadata passed in the puzzle modal
We've now got another handoff, from the puzzle modal's submission
handler to a separate channel_created event before the hunt's
channel_id is needed. And by then we've lost the context of this
private_metadata, (so the current code scans through the database
'hunts' table to find the ID). Given all that, we can drop this unused
field from the metadata.
Carl Worth [Thu, 22 Oct 2020 07:50:09 +0000 (00:50 -0700)]
Defer all sheet creation until after the channel is created
Specifically, we no longer attempt to do sheet creation in the handler
for the interaction of the user submitting a modal dialog. The reason
that was a a bad idea is that the sheet creation can take several
seconds, (including time to copy data from a template sheet, etc.),
while Slack will timeout after merely 3 seconds of a user submission
and report an error.
This way, we can promptly validate the user's input (giving them a
clean error on the form if necessary), then simply create the channel
in the handler and primomptly return (so Slack doesn't timeout).
Then, separately, we now have a listener setup for the channel_created
event and in _that_ handler we do all the sheet creation necessary. We
have all the time we want here since channel_created is just an event
without a user waiting for a response, so Slack doesn't impose a
3-second timeout on us.
Carl Worth [Thu, 22 Oct 2020 09:37:04 +0000 (02:37 -0700)]
Create the hunts table if it doesn't already exist
I just realized I was probably missing a necessary index on the hunts
table, meaning I need to destroy it ad recreate it. But I don't want
to do that manually without any code to capture what I did.
And if I'm going to write code, I might as well have that code called
implcitly as needed. So that's what's present here, the code necessary
to create the hunts table if it doesn't already exist.
Carl Worth [Wed, 21 Oct 2020 23:12:42 +0000 (16:12 -0700)]
Implement a new `/puzzle` slash command to create a puzzle
This command doesn't (yet) accept any arguments. Instead, it pops up a
modal dialog to prompt for the puzzle name, ID, and URL.
The command also only works from a top-level hunt channel.
Some things that would be nice improvements here:
* Auto-fill the puzzle ID based on the puzzle name without
punctuation and spaces replaced with underscores.
* Allow the command to work from the channel of any puzzle in the
hunt
* Fix the bug that the modal dialog reports an error even when
things work correctly, (the problem is that the lambda is taking
more than the maximum 3 seconds that Slack is willing to wait for
a response).
Carl Worth [Wed, 21 Oct 2020 23:00:20 +0000 (16:00 -0700)]
Combine actions.py and commands.py into interaction.py
We're about to create a new slash command that fires up a modal, and
the response to that modal will be an action. So we want the
implementation of that slash command (which creates the view for the
modal) to be in the same file as the handler for the action.
So we combine these together for now.
If this file become unwieldy later, we can re-separate things based on
functional groups, (interactions related to hunts, puzzles, word-play
utilities, etc.), but always keeping the view creators and the
view-submission handlers together for any given view.
Carl Worth [Wed, 21 Oct 2020 20:38:40 +0000 (13:38 -0700)]
Drop unused initialization of submission_handlers dictionary
We don't use this dictionary with any statically-initialized keys,
(such as "new_hunt" as we have it here). Instead, this dictionary is
populated dynamically with keys that are view IDs. So we can just drop
this initialization here and everything continues to work as is.
Carl Worth [Wed, 21 Oct 2020 18:57:45 +0000 (11:57 -0700)]
Make text optional for slash commands
We're planning a new approach which is to make it so that a slash
command with no argument string pops up a modal dialog. For this to
work, we have to allow for a body that includes no 'text' key.
Carl Worth [Wed, 21 Oct 2020 18:46:09 +0000 (11:46 -0700)]
Dont' allow hunt_id to contain a hyphen
We're planning to use a hyphen in the puzzles' channel names to
separate the hunt_id from the rest of the puzzle's channel name, so
the hunt_id itself must not contain any hyphen.
Carl Worth [Wed, 21 Oct 2020 18:40:38 +0000 (11:40 -0700)]
Rename 'slug' field in hunt table to 'hunt_id'
We were already using the name "Hunt ID" in all user-visibile
interfaces, so there was no good justification for having a totally
different name internally.
Carl Worth [Wed, 21 Oct 2020 16:39:54 +0000 (09:39 -0700)]
Add a stub function for hanlding a shortcut invocation
We're not currently pursuing this option, (because the global shortcuts
are annoyingly truly global---they would be a lot more useful it the
payload included the ID of the current channel from where they were
invoked). But here's the stub in case we ever want to start using these
in the future.
Carl Worth [Tue, 20 Oct 2020 01:48:08 +0000 (18:48 -0700)]
Correct the code storing the picked base64 token
Using 'str()' here was incorrect. That did go from a bytes value to a
string, but it did it by adding "b'" to the beginning and "'" to the
end, breaking the parsing of the base64 value.
Here, instead, we use the decode('us-ascii') method on the bytes value.
Carl Worth [Mon, 19 Oct 2020 21:43:40 +0000 (14:43 -0700)]
Create a sheet for the hunt when creating a new hunt
I don't know how much we'll need this, but here it is for consistency.
This commit adds support for the Google sheets API (accessed via a
pickled token from an encrypted SSM parameter). This will be very
handy in the future when creating sheets for each puzzle channel, etc.
Carl Worth [Sat, 17 Oct 2020 00:21:17 +0000 (17:21 -0700)]
Fix new hunt submission to not rely on global python state
It was only by accident that what I had written happened to work,
(apparently AWS reuses Lambda containers within a 30-minute window or
so). Obviously, I don't want to rely on anything like that, so instead
in this commit I take advantage of the "private_metadata" field that
Slack offers for me to pass data back and forth on a view and use that
to decide which function to invoke to handle view submission.
Carl Worth [Fri, 16 Oct 2020 20:22:15 +0000 (13:22 -0700)]
Handle the submission of the "New Hunt" modal
By creating a new entry in the hunts table.
As part of this change we rework turbot/views.py so that the block
functions it had are now in turbot/blocks.py (where they are usefully
shared). Meanwhile, the actual views that were being created are now
created above in events.py, actions.py, etc. where they can be
associated with functions to handle the submission of those views,
etc.
Carl Worth [Fri, 16 Oct 2020 20:26:21 +0000 (13:26 -0700)]
Rename 'channel' to 'channel_id'
Tracking a change made to the (non-) schema of the database. Here
'channel_id' is more clear for how this is used, (where I would have
expected 'channel' to be the actual name of the channel).
Carl Worth [Wed, 14 Oct 2020 04:26:55 +0000 (21:26 -0700)]
Plumb the turb class down through all the functions here
It might not be that pythonic to do this with just a simple structure
and functions and not with any methods, but that's actually what I prefer
at this point at least.
Carl Worth [Tue, 13 Oct 2020 23:35:37 +0000 (16:35 -0700)]
Add a dispatch table to turbot.commands
This allows the turbot/commands.py file to be self-sufficient, in the
sense that adding a new command will not require changing any of the
dispatch code above in turbot_lambda.py, but instead just the local
dispatch table.
Carl Worth [Tue, 13 Oct 2020 23:31:35 +0000 (16:31 -0700)]
Move rot function down into turbot/commands.py
Completing the bulk of the code movement out of turbot_lambda.py
Now, the code in turbot_lambda is really just about handling the
request and figuring out which type it is, (whether a Slack action,
command, event, etc.), and calling out to the appropriate code in
turbot.actions, turbot.commands, turbot.events, etc.
Carl Worth [Tue, 13 Oct 2020 23:07:01 +0000 (16:07 -0700)]
Fold all of turbot/slack.py up into turbot_flask/turbot.py
Just making the Flask bot self-contained at this point, (since we're
basically about to abandon it in its development for the time being
while we focus on the Lambda-based bot that has access to DynamoDB).
Carl Worth [Tue, 13 Oct 2020 22:28:58 +0000 (15:28 -0700)]
Fix several problems pointed out by flake8
I really should integrate flake8 more decisively into my workflow somehow.
Note: I'm ignoring W503 here since it's contradictory with W504,
(breaking a "line too long" problem at an operator will cause
one of W503 or W504 to fire and there's no way I can see to shut
them both up).
Carl Worth [Tue, 13 Oct 2020 19:57:44 +0000 (12:57 -0700)]
Revamp the Home page of our app to start looking like what we want
This gets rid of the original placeholder blocks which I had gotten
from a tutorial and replaces it with placeholders that look more like
what we are going to want, (a list of hunts and a button to create a
new hunt).
While doing this, we provide some utility functions to create blocks
to reduce the amount of boilerplate we need while constructing these
things.
Carl Worth [Tue, 13 Oct 2020 18:03:30 +0000 (11:03 -0700)]
Introduce a new turbot_interactive function
Now that we've introduced a new entry point for our Slack app, which is
the interactive features, (that is, an entry point that Slack will POST
to when the user clicks on a button or some other interactive element that
our app. provides to them).
Carl Worth [Tue, 13 Oct 2020 03:07:40 +0000 (20:07 -0700)]
Implement url_verification for Slack event handling
This isn't a real event, but it is required to correctly be able to handle
this url_verification challenge before Slack will allow us to register
our URL in its interface.
Carl Worth [Mon, 12 Oct 2020 22:04:30 +0000 (15:04 -0700)]
turbot_lambda: Add Slack signature verification
This will reject any request that does not come from Slack itself,
(as verified by the Slack-specified HMAC algorithm and the
SLACK_SIGNING_SECRET that Slack made available to us and that we
have registered as an encrypted SSM parameter).
Carl Worth [Mon, 12 Oct 2020 21:13:24 +0000 (14:13 -0700)]
turbot_lambda: Inject /rot response into the same channel as the command
Prior to this commit, we were just including the rot result in the
response to the POST request. That was sufficient for Slack to take
the result and present it privately to the user that issued the
command. But that would not allow any other user in the same channel
to see the same result.
Here, instead, we use the Slack WebClient API to inject the result
into the user's channel as a new message. This takes advantage of a
new, encrypted SSM parameter SLACK_BOT_TOKEN to authenticate our
bot. It also requires bundling all of the virtualenv dependencies into
the zip file uploaded to AWS for the lambda, so that's what the
Makefile modifications do here.
Carl Worth [Mon, 12 Oct 2020 20:07:50 +0000 (13:07 -0700)]
turbot_lambda: Parse the command from the request
This will allow us to implement multiple slash commands within a single
Lambda function. Currently just the /rot (also its /rotlambda alias)
command is implemented, but we've got all the structure in place to do
more as necessary.
Carl Worth [Mon, 12 Oct 2020 03:52:01 +0000 (20:52 -0700)]
Switch to parse the x-www-form-urlencoded data in Python
Previously, we were doing this in a mapping template within AWS,
but I decided that that mechanism was more trouble that it was
worth, (it was quite buried in AWS, hard to share across multiple
lambdas, and meanwhile this approach is a single line of code).
Carl Worth [Mon, 12 Oct 2020 03:08:13 +0000 (20:08 -0700)]
Switch from event['args'] to event['text'] for input to the lambda
Since that's what we actually get from Slack, (now that we have the
"mapping template" in place so that we receive a dictionary here rather
than a bunch of x-www-form-urlencoded data).
Carl Worth [Mon, 12 Oct 2020 02:59:54 +0000 (19:59 -0700)]
Rename lamdba_function.lambda_handler to turbot_lambda.turbot_lambda
Now that I realized that the lambda_function.lambda_handler naming
isn't hard-coded in AWS but is instead just a default, (and now that
I've configured it to this instead).
Carl Worth [Sun, 11 Oct 2020 21:12:42 +0000 (14:12 -0700)]
Share the rot() implementation between both Lambda and Flask implementations
This provides a model of how to do code sharing between the two
implementations we have, (with the expectation that the code in the
"turbot" package will be generic and that code in turbot_slack and
turbot_lambda will use that common code while doing whatever
additional marshalling is necessary for their particular application
environments).
Carl Worth [Sun, 11 Oct 2020 20:59:19 +0000 (13:59 -0700)]
Move top-level flask app from turbot/turbot.py to turbot_flask/turbot.py
This is a first baby step toward teasing apart true "library" code in
turbot, (which can be shared by both the Lambda and Flask
implementations), from code that is Flask-specific.
(For symmetry, the lambda directory is renamed turbot_lambda here as well.)
Carl Worth [Sun, 11 Oct 2020 17:35:45 +0000 (10:35 -0700)]
Start implementing code intended to be deployed to AWS Lambda
Here, lambda/rot.py is a copy of the turbot/rot.py code, but massaged
into the calling conventions of AWS Lambda (and without yet any actual
code to integrate with Slack in either direction).
Carl Worth [Fri, 9 Oct 2020 22:41:11 +0000 (15:41 -0700)]
Use a thread for creating the Google Sheet when a new channel is created
This allows us to return quickly so that Slack doesn't retry sending the event.
As implemented in this commit, this code is a bit cheesy in that it
disables the debug mode and error handling in slack_send_message,
(since the prior implementation of that depended on having a current
Flask app, but now that we are calling this function from a thread we
don't have a current app context).