Create a Slack application as the title says. If you write it properly, it will be a long sentence, so only the following points will be emphasized.
--Notes on Slack application --Application settings on Slack side (around permissions) --About Slack's HomeView (Home tab) and attachment --Implementation and library usage around Lambda (python) --APIGateWay settings
If you just want to try it simply, you can set a WebHook URL, schedule Lambda, etc. and POST it, but I'm assuming a scene where you can send a message to an interactive request using the SlackClient library.
Rather than making something that works if you follow the procedure Please be aware that it is troublesome to look up, and I will introduce the points that I was unexpectedly addicted to in fragments.
When clicking the button and returning a response, an error will occur if it exceeds 3 seconds.
This is a process that returns HTTP code 200 for the time being, After that, it can be easily avoided by performing processing asynchronously and notifying the channel of the result again.
I won't go into detail in this article, Actually, the response_url of the request parameter is valid for several minutes or more even after 3 seconds have passed. It is possible to POST directly to the response_url while preparing for a timeout.
You can try it with Block Kit Builder, but it's very troublesome.
The user ID is in PayLoad of requestBody. Note that it is not a user name. The channel ID is also in PayLoad of requestBody, but it may not come in with a specific pattern. You may want to notify another channel, so it's a good idea to embed the target ID with a constant.
The channel ID can be easily identified from the address when the target channel is opened with a browser.
/app.slack.com/client/
/ /details/top
There is no help for what you can't do. I forcibly shaped it using dividers, quote markup and space filling. ..
Only where it's okay to set this much. Surprisingly limited. I will not use WebHookURL this time, but I will set it for the time being.
After setting [Settings] -> [Installed App Settings] -> [(Re)Install to WorkSpace] Don't forget to reflect on.
[Settings] -> [Basic Infomation] -> [Display Information] App name, description, logo, etc. Appropriately.
WebHookURL [Features] -> [Incoming Webhooks]
--Turn on Activate Incoming Webhooks --Add with [Add New Webhook to Workspace] in [Webhook URLs for Your Workspace]
[Settings] -> [OAuth & Permissions]
[Bot User OAuth Access Token] Make a note of the value here as it will be needed to send messages etc. from lambda
[Scope] -> [Bot Token Scopes] What is essential I think it's about chat: write, chat: write.customize, channels: history. If you use WebHook, you can also use incoming-webhook. I also put app_mentions: read, commands, reactions: read and so on.
[Features] -> [Event Subscriptions]
After creating the API and setting up the Gateway (the method will be described later) Finally, turn on [Enable Events] here and enter various settings.
By setting the URL etc. of the API created here Make sure that the request flies when you actually perform a specific action.
I can't do it at this stage, so I'll set it later. Even if you enter an appropriate URL temporarily, it will be NG by authentication. At the very least, authentication will not pass unless challenge response is implemented in the processing on the API side.
Please be aware that it is the place to put the URL of the API at the end.
Set in [Subscribe to bot events].
For the time being, app_home_opened for the UI display of the home tab It's almost OK if you add message.channels to take the button event from the message.
Below is a simple example and image. The implementation image in the code will be described later together with the usage sample of SlackClient API.
HomeTabSample.json
{
"type": "home",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "This is a super simple app test."
}
},
{
"type": "divider"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Show current time"
},
"style": "primary",
"value": "click_from_home_tab"
}
]
}
]
}
MessageSample.json
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Hello, it is a sample application!"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"emoji": true,
"text": "Tell me the current time"
},
"style": "primary",
"value": "click_from_message_button"
}
]
}
]
}
Introducing only the points. By the way, since the log related to API Gateway is enough, it is not set in particular on the lambda side.
Slack application (mentioned above) ↓↑ AWS API Gateway (described later) ↓↑ lambda slack-request.py API (explained here)
Since there is a three-second rule, the process is delegated to the asynchronous lambda and the response is returned once. Most of the payload analysis etc. is done here ↓↑ lambda async-notice.py API (explained here) Process with all request details and attribute information received and notify Slack of the result This time, an example of schedule execution is also here
The part that is sober and difficult. It takes several hours if you get hooked. I make a lambda every time I forget it, so He said he forgot to wrap it in a python folder, uploaded it, and was confused because it didn't work.
Well, upload this and add it to both lambda layers. I feel like I only need slack-client (please google for how to make it).
I don't need it this time, but if you want to do various things, it's a good idea to include the latest versions of django and pandas. In the trial version, all you need is the above slack layer.
** Challenge-response implementation ** At a minimum, the request URL should include the following implementation at the beginning. Without it, you will not be authenticated in the application settings.
slack-request.py
if "challenge" in event:
return event["challenge"]
** Notifications are made using the Slack Client API **
Slack Client generation
#Bot User OAuth Access Token value in application settings
import slack
client = slack.WebClient(token="xoxb-XXXXXXXXXX....")
** Home tab UI drawing ** Specify in JSON
Draw the home tab
"""
Displaying the Home Tab menu
"""
if ('event' in event):
slack_event = event['event']
if (slack_event['type'] == 'app_home_opened') :
if ('tab' in slack_event) and (slack_event['tab'] == 'home'):
user = slack_event['user']
channel = slack_event['channel']
blocks = <Here is JSON>
views = {"type": "home", "blocks": blocks}
client.views_publish(user_id=user, view=views)
return {'statusCode': 200}
Home tab JSON
{
"type": "home",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "This is a super simple app test."
}
},
{
"type": "divider"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Show current time"
},
"style": "primary",
"value": "click_from_home_tab"
}
]
}
]
}
** Event handle (handling home button and interactive message button) ** Strictly speaking, it's better to sort them out a little more, but it still works.
The biggest point is ** Request asynchronously to another lambda and return the response that received the request first. ** **
Also, in the sample, you may not want to return a response to other Slack users at each notification. The message notification is set to "Only you can see" Ephemeral.
slack-request.py
"""
Hook Action, distribute processing, and return Response to Slack
ChannelId is fixed and invariant, so use a constant instead of from the request
"""
if ('body-json' in event):
#Extract the contents of payload
action_message = urllib.parse.unquote(event['body-json'].split('=')[1])
action_message = json.loads(action_message)
#Get the user ID to be notified
user_id = action_message['user']['id']
# Message Button ==Determine if it is an Interactive Message
isInteractiveMessage = False
if('type' in action_message ) and (action_message['type'] == 'interactive_message'):
isInteractiveMessage = True
#Hook the event if there is action
if ('actions' in action_message):
"""
Get value and request notification asynchronously to another lambda
There may be multiple asynchronous requests, but requests are mainly judged only in Primary Mode.
"""
delegateLambda_primaryMode = []
#Extract only the action message part from the payload
action_message = action_message['actions'][0]
#Since it is sent asynchronously, set the Default of the synchronous response message first.
send_message = "The request has been accepted. please wait a moment."
if (action_message['type'] == 'button') :
if (action_message['value'] == 'click_from_home_tab'):
"""
Home tab button clicked
"""
delegateLambda_primaryMode.append('click_from_home_tab')
elif (action_message['value'] == 'click_from_message_button'):
"""
Home tab button clicked
"""
delegateLambda_primaryMode.append('click_from_message_button')
"""
Make the set asynchronous request
In addition to Primary and Secondary, user_I made it a specification to pass id
Asynchronous processing will be described later
"""
for p in delegateLambda_primaryMode:
input_event = {
"primaryMode": p,
"secondaryMode": "now no mean",
"user_id": user_id
}
lambdaMediator.callNortifyAsync(json.dumps(input_event))
"""
Return the response message once
"""
if isSendEphemeral == True:
response = client.chat_postEphemeral(
channel=notice_channel_id,
text=send_message,
user=user_id
)
else:
response = client.chat_postMessage(
channel=notice_channel_id,
text=send_message
)
if isInteractiveMessage == True:
pass
else:
return {'statusCode': 200}
lambdaMediator.py
import boto3
def callNortifyAsync(payload):
"""
Asynchronously delegated lambda(async-notice.py fixed)Call
Parameters
----------
event :Event json of delegation destination lambda
PrimaryMode:First Key character string representing the request content
SecondaryMode:Second Key character string representing the request content
UserId:Requester's Slack user ID
Returns
----------
response
"""
response = boto3.client('lambda').invoke(
FunctionName='async-notice',
InvocationType='Event',
Payload=payload
)
return response
You don't have to use attachment, but if you want to do something elaborate The sample does that because it's done with attachments rather than messages.
This time, AWS Lambda_FullAccess is given to the execution Role of both lambdas. Also, since it is a sample, I got PrimaryMode from slack-request.py, but I did not include a branch.
** Send a message using attachment **
async-notice.py
import json
import datetime
import time
import boto3
import slack
from libs import mylib
from libs import slack_info
def lambda_handler(event, context):
"""
This lambda is slack-Delegate processing from request (asynchronous), and kicked only from scheduler
Parameters
----------
event :
PrimaryMode:First Key character string representing the request content
SecondaryMode:Second Key character string representing the request content (unused: for expansion)
UserId:Requester's Slack user ID
Returns
----------
httpResponseBody : statusCode:200 , body ->Just return 200 to send from SlackClient
ToDo
----------
"""
#Get user ID
user_id = ""
if 'user_id' in event:
user_id = event['user_id']
"""
No parameter is started from the scheduler, so general notification
Ephemeral because it is an asynchronous request with parameters(I can only see you)notification
"""
isEphemeral = True
if ('primaryMode' in event) == False:
isEphemeral = False
"""
Send a message notification
attachments is message JSON
"""
postDataMessage(attachments, False, isEphemeral, user_id, "Put the text message here.")
return {
'statusCode': 200,
'body': json.dumps('OK')
}
def postDataMessage(attachment_data, isSendMultiple=False, isEphemeral=True, userId="", textMessage=""):
"""
Send a message
Parameters
----------
attachment :Attachment data
isSendMultiple :Whether there are multiple attachment data
isEphemeral :Whether to send so that only the requesting user can see it
userId :Request user ID
textMessage :Text message
Returns
----------
ToDo
----------
"""
client = slack.WebClient(token="xoxb-XXXXXXXXX.....")
#Rewrap in a list even if it is a single transmission because it supports multiple transmissions
if isSendMultiple == False:
attachment_data = [attachment_data]
# TARGET_CHANNEL_For the ID, set the channel ID of the notification destination separately.
for attachment in attachment_data:
if isEphemeral == True:
response = client.chat_postEphemeral(
channel=TARGET_CHANNEL_ID,
attachments = attachment,
user=userId
)
else:
response = client.chat_postMessage(
text=textMessage,
channel=TARGET_CHANNEL_ID,
attachments = attachment
)
json example of attachment
{
"type": "home",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "This is a super simple app test."
}
},
{
"type": "divider"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Show current time"
},
"style": "primary",
"value": "click_from_home_tab"
}
]
}
]
}
Just enter the async-notice lambda and cron settings from your browser when creating rules for Amazon Event Bridge.
Runs from Monday to Friday at 9am (because it's GMT time)-Set in 9 hours)
cron(0 0 ? * MON-FRI *)
Just specify ** redshift-data ** in boto3 Data access is very easy because it is performed with role authority within the Redshift account specified when executing the query.
RedShift connection
import json
import time
import boto3
#Redshift connection information
CLUSTER_NAME='Enter the cluster name'
DATABASE_NAME='Enter the database name'
DB_USER='Enter your username'
def getDateTimeSample():
"""
Get time
"""
sql = '''
select getdate();
'''
return _getData(sql)
def _getData(sql):
"""
Issue a query to RedShift and return the result as it is
Parameters
----------
String :sql statement
Returns
----------
statement :The acquisition result of boto3 is as it is
"""
#Submit a query to Redshift. It's asynchronous so it will come back soon
data_client = boto3.client('redshift-data')
result = data_client.execute_statement(
ClusterIdentifier=CLUSTER_NAME,
Database=DATABASE_NAME,
DbUser=DB_USER,
Sql=sql,
)
#Get execution ID
id = result['Id']
#Wait for the query to finish
statement = ''
status = ''
while status != 'FINISHED' and status != 'FAILED' and status != 'ABORTED':
statement = data_client.describe_statement(Id=id)
#print(statement)
status = statement['Status']
time.sleep(1)
#View results
if status == 'FINISHED':
if int(statement['ResultSize']) > 0:
#If it is a select statement etc., the return value is displayed
statement = data_client.get_statement_result(Id=id)
else:
#If there is no return value, only FINISH is output and the process ends.
print('QUERY FINSHED')
elif status == 'FAILED':
#At the time of failure
print('QUERY FAILED\n{}'.format(statement))
elif status == 'ABORTED':
#When stopped by the user
print('QUERY ABORTED: The query run was stopped by the user.')
return statement
Give the created lambda API a URL so that you can access it.
** Created with REST API **
** Integrate lambda **
** Add application/x-www-form-urlencoded in mapping template ** The most important. In the open event of the home tab, it comes in this format, so it will not work unless you enter here. Also, since some other requests come in application/json, it is also a point to add them together.
** All you have to do is deploy the stage **
** Log settings ** This setting is sufficient for debugging. The log is output to CloudWatch as a log group with the stage name.
Well, given the nature of Slack, it's understandable, but sometimes it's fancy.
Perform processing including database access asynchronously Even if you simply return response 200, there are rare cases where it is not in time considering the cold start of lambda.
Lambda also has Provisioned Concurrency, which can also be used in some cases [https://aws.amazon.com/jp/lambda/pricing/].
I think there is a lot of demand. If you use a quote, the half-width space is faithfully reproduced in the quote, so if you use this and the division line, it will look like a table. However, the feeling cannot be wiped out by force.
Recommended Posts