123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990openPrintfopenExtLibopenSlack_jopenSlack_t(***************** General Slack Utilities for Handling Event Hooks *****************)(** [ validate_signature signing_key headers body ] validate the signature
from a Slack event API hook.
*)letvalidate_signature?(version="v0")?signing_key~headersbody=matchsigning_keywith|None->Ok()|Somekey->matchList.assoc_opt"x-slack-signature"headerswith|None->Error"unable to find header X-Slack-Signature"|Somesignature->matchList.assoc_opt"x-slack-request-timestamp"headerswith|None->Error"unable to find header X-Slack-Request-Timestamp"|Sometimestamp->letbasestring=Printf.sprintf"%s:%s:%s"versiontimestampbodyinletexpected_signature=Printf.sprintf"%s=%s"version(Common.sign_string_sha256~key~basestring)inifString.equalexpected_signaturesignaturethenOk()elseError"signatures don't match"(** [ process_slack_notification ] is a general handling function for Slack event callback
notification where it validates the signature of incoming hooks and then pass it to your
[ notification_handler ] to process the actual notification. It also has handlings for
the URL verification challenge so to support that, you need to make sure your handler
returns a [string Lwt.t] (and you always should return [200s] code otherwise,
Slack will retry).
*)letprocess_slack_notification(ctx:Context.t)headersbody~notification_handler=matchevent_notification_of_stringbodywith|Url_verificationpayload->Lwt.return_okpayload.challenge|Event_callbacknotification->matchvalidate_signature?signing_key:ctx.secrets.slack_signing_secret~headersbodywith|Errore->Lwt.return_error(sprintf"signature not validated: %s"e)|Ok()->notification_handlernotification(** [ process_slack_event ] is the same as [ process_slack_notification ] but will disregard
the notification detail and only process the notification event using your
[ event_handler ].
*)letprocess_slack_event(ctx:Context.t)headersbody~event_handler=process_slack_notificationctxheadersbody~notification_handler:(funnotification->event_handlernotification.event)(***************** Utilities over Slack API returns *****************)(** conversation types of a [Slack channel] *)typeconversation_type=|Channel|DirectMessage|Group(** [ channel_type_of_conversation ] returns a [conversation_type] of a some
[ conversation ] API result
*)letconversation_type_of_conversation=function|({is_channel=true;_}:conversation)->OkChannel|{is_im=true;_}->OkDirectMessage|{is_group=true;_}->OkGroup|conversation->Error(`Other(sprintf"did not get valid conversation info for channel %s"conversation.id))letshow_channel_type=function|Channel->"channel"|DirectMessage->"direct message"|Group->"group"(** [ ApiHelpers Api_Impl] is a functor that wraps Api for simple functionalities such as sending texts *)moduleApiHelpers(Api:Api.S)=structletsend_text_msg~ctx~channel~text=letmsg=make_post_message_req~channel~text()inApi.send_message~ctx~msgletupdate_text_msg~ctx~channel~update~ts=letmsg=make_update_message_req~channel~text:update~ts()inApi.update_message~ctx~msgletsend_text_msg_as_user~ctx~channel~text~username?icon_url?icon_emoji()=letmsg=make_post_message_req~channel~text~username?icon_url?icon_emoji()inApi.send_message~ctx~msgletget_channel_type~(ctx:Context.t)~channel=letconversation=make_conversations_info_req~channel()inmatch%lwtApi.get_conversations_info~ctx~conversationwith|Errore->Lwt.return_errore|Ok({channel;_}:conversations_info_res)->Lwt.return@@conversation_type_of_conversationchannelend