unisonweb / unison

A friendly programming language from the future
https://unison-lang.org
Other
5.78k stars 270 forks source link

Update generates unparsable code #4385

Open runarorama opened 11 months ago

runarorama commented 11 months ago
  1. Clone this from Share: https://share.unison-lang.org/@runarorama/slack/code/@runarorama/updatebug

  2. Put this in the scratch file:

events.api.reactionAdded.tests.test1 : [Result]
events.api.reactionAdded.tests.test1 = 
  verify do
    payload =
      """
      {
        "type": "reaction_added",
        "user": "U123ABC456",
        "reaction": "thumbsup",
        "item_user": "U222222222",
        "item": {
            "type": "message",
            "channel": "C123ABC456",
            "ts": "1360782400.498405"
        },
        "event_ts": "1360782804.083113",
        "ts": "1360782804.083113"
      }
      """
    event =
        EventCallbackReq
          ""
          ""
          ""
          (Decoder.run EventEnvelope.decoder payload)
          CallbackType.EventCallback
          ""
          ""
          epoch
    ev = Optional.toGenericException (SlackEvents.run event reactionAdded)
    ensureEqual (reaction ev) "thumbsup"

events.api.message.channels.doc : Doc
events.api.message.channels.doc =
  {{
  Handle ''message'' events sent to channels.

  # Example

    This bot reacts to any message sent to a channel if the message contains
    the word ''coffee'', by adding a coffee emoji reaction:

    @typecheck ```
    coffeeBot =
      do
        message = !channels
        messageText = Text.toLowercase (events.types.Message.text message)
        if lib.base.Text.contains "coffee" messageText then
          react message "coffee"
        else SlackEvents.skip

}}

SlackWeb.doc : Doc SlackWeb.doc = {{ The ability to make requests to the Slack Web API.

A number of functions are provided to make requests to the Slack Web API:

events.api.message.doc : Doc events.api.message.doc = {{ Handle ''message'' events.

Example

This bot reacts to any message that contains the word ''coffee'' by adding
a coffee emoji reaction:

@typecheck ```
coffeeBot =
  do
    message = !api.message
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "coffee" messageText then
      react message "coffee"
    else SlackEvents.skip
```

}}

runBot : Text -> Text -> '{cloud.Config, Exception, cloud.State, Http, Services, Remote, cloud.Log, Scratch, SlackWeb, SlackEvents} () -> HttpRequest ->{cloud.Config, Exception, cloud.State, Http, Services, Remote, cloud.Log, Scratch} HttpResponse runBot slackTokenConfigKey slackSigningSecretConfigKey bot = Route.run '(botRoute slackTokenConfigKey slackSigningSecretConfigKey bot)

SlackEvents.or : '{g1, SlackEvents} a -> '{g2, SlackEvents} a -> '{g1, g2, SlackEvents} a SlackEvents.or a b = do h = cases { currentEvent -> k } -> e = currentEvent handle k e with h { SlackEvents.skip -> _ } -> !b { a } -> a handle !a with h

events.api.eventOfType : Text ->{SlackEvents} EventEnvelope events.api.eventOfType typ = match currentEvent with EventCallbackReq token teamId apiAppId env@(EventEnvelope t timestamp user ts payload) cbtype ctx eventId eventTime | t Text.== typ -> env _ -> SlackEvents.skip

tests.demos.coffeeBot : '{Exception, cloud.Log, SlackWeb, SlackEvents} () tests.demos.coffeeBot = do message = !api.message messageText = Text.toLowercase (events.types.Message.text message) if lib.base.Text.contains "coffee" messageText then react message "coffee" else SlackEvents.skip

events.api.reactionAdded.doc : Doc events.api.reactionAdded.doc = {{ Handle ''reaction_added'' events.

Example

This bot replies to any message that has a ":robot_face:" reaction added to
it:

@typecheck ```
bot =
  do
    event = !reactionAdded
    match event with
      MessageReaction reaction user itemUser eventTs channel ts ->
        postMessage.toThread channel ts "Hello!"
      _ -> SlackEvents.skip
```

}}

events.api.message.channelsNamed.doc : Doc events.api.message.channelsNamed.doc = {{ Handle ''message'' events sent to a specific set of channels.

Takes the names of the channels to handle. Note that these are the names of the channels, not the channel IDs.

Your bot must be a member of the channels you want to handle.

Example

This bot reacts to any message sent to the ''coffee'' channel if the
message contains the word ''cup'', by adding a coffee emoji reaction:

@typecheck ```
coffeeBot =
  do
    message = channelsNamed ["coffee"]
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "cup" messageText then react message "coffee"
    else SlackEvents.skip
```

}}

runBot.doc : Doc runBot.doc = {{ Runs a bot in the cloud.

This function is intended to be used with {deployHttp}.

Arguments

1. The name of the {type cloud.Config} key that contains the Slack API
   token for your bot.
2. The name of the {type cloud.Config} key that contains the Slack signing
   secret.
3. The bot to run.

Example

@typecheck ```
coffeeBot =
  do
    message = !api.message
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "coffee" messageText then
      react message "coffee"
    else SlackEvents.skip
deployHttp
  !Environment.default
  (runBot "SLACK_TOKEN" "SLACK_SIGNING_SECRET" coffeeBot)
```

}}

events.api.messageOfType.doc : Doc events.api.messageOfType.doc = {{ Handle messages of a particular type.

Example

@typecheck ```
bot = do
  message = messageOfType "message"
  Log.info "Got a message" [("message", evalToText message)]
``` }}

SlackEvents.or.doc : Doc SlackEvents.or.doc = use SlackEvents skip use Text toLowercase use events.types.Message text use lib.base.Text contains {{ Combines two bots into a single bot that tries the first one and fails over to the second bot if the first one skips the event.

Example

This bot reacts to any message that contains the word ''coffee'' or ''tea''
by adding a coffee or tea emoji reaction. Note that the bot will only react
to the first word it finds, so if a message contains both ''coffee'' and
''tea'', it will only react to the ''coffee'' part.

@typecheck ```
coffeeBot = do
  message = !api.message
  messageText = toLowercase (text message)
  if contains "coffee" messageText then react message "coffee" else skip
teaBot = do
  message = !api.message
  messageText = toLowercase (text message)
  if contains "tea" messageText then react message "tea" else skip
SlackEvents.or coffeeBot teaBot
```

}}

events.api.appMention.doc : Doc events.api.appMention.doc = {{ Handle ''app_mention'' events. These are events that occur when the bot is mentioned in a message.

Example

@typecheck ```
bot = do
  message = !appMention
  reply message "Hello!"
```

}}

events.api.appMention : '{SlackEvents} events.types.Message events.api.appMention = do messageOfType "app_mention"

SlackEvents.oneOf : ['{g, SlackEvents} a] ->{SlackEvents} '{g, SlackEvents} a SlackEvents.oneOf ks = List.foldLeft SlackEvents.or SlackEvents.skip ks

(SlackEvents.<|>) : '{g1, SlackEvents} a -> '{g2, SlackEvents} a -> '{g1, g2, SlackEvents} a (SlackEvents.<|>) = SlackEvents.or

events.types.Message.doc : Doc events.types.Message.doc = {{ A message sent to a channel. This is the type received by the bot when Slack sends a ''message'' event. See the Slack API documentation for more information.

Fields

* {events.types.Message.text} - The text of the message.
* {Message.channel} - The channel the message was sent to.
* {subtype} - The subtype of the message, if any.
* {Message.ts} - The timestamp of the message.
* {events.types.Message.user} - The user that sent the message.
* {reactions} - The reactions to the message.
* {channelType} - The type of the channel the message was sent to.
* {botId} - The id of the bot that sent the message, if any.

See also

{api.message}, {appMention}, {channels}, {channelsNamed}, {message.groups},
{im}, {mpim}, {messageOfType}

}}

events.api.message.im : '{SlackEvents} events.types.Message events.api.message.im = do msg = messageOfType "message" match channelType msg with Some "im" -> msg _ -> SlackEvents.skip

web.api.reactions.react.doc : Doc web.api.reactions.react.doc = {{ Adds a reaction to a message, given the message and the reaction name. The type of message must be {type events.types.Message}, which is the type of message received by a bot from the {type SlackEvents} ability.

Example

@typecheck ```
message = !channels
if lib.base.Text.contains "coffee" (events.types.Message.text message) then
  react message "coffee"
else SlackEvents.skip
```

}}

events.api.message.mpim.doc : Doc events.api.message.mpim.doc = {{ Handle ''message'' events sent in multi-person direct messages.

Example

This bot replies to any multi-person direct message containing the word
''coffee'' with the text ''I'd love a cup!'':

@typecheck ```
helloBot =
  do
    message = !mpim
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "coffee" messageText then
      reply message "I'd love a cup!"
    else SlackEvents.skip
```

}}

README : Doc README = use Cloud main use SlackEvents oneOf skip use events.types.Message text use lib.base.Text contains use postMessage toChannel toThread use reactions add {{

Slack API

This library provides a Slack API client for Unison Cloud. It can be used
to build Slack bots and applications. It currently supports the Slack Web
API and the Slack Events API.

This is a work in progress. This library technically supports all of the
Slack Web and Slack Events APIs but doesn't yet provide ergonomic functions
for the full feature set. If you need a function that isn't provided or
would like to contribute, please contact @runarorama on Slack.

## Example

   The following is a simple Slack bot that responds to mentions of
   "coffee" with a coffee emoji reaction, in any channel it's a member of:

       @source{tests.demos.coffeeBot}

## Getting started

   To use this library, you will need to create a Slack app from your
   [Slack API dashboard](https://api.slack.com/apps).

   Once you've created your app in the Slack dashboard, grab the
   **Bot User OAuth Access Token** from the app's **OAuth & Permissions**
   page. This is the token that you will use to authenticate your bot with
   the Slack Web API.

   You will also need the **Signing Secret** from the app's
   **Basic Information** page. This is the secret that you will use to
   verify requests from the Slack Events API.

   {{
   docCallout
     (Some {{ ⚠️ }})
     {{
     Never put the OAuth Access Token or Signing Secret in your codebase.
     }} }}

   A relatively safe way to add these secrets to your Unison Cloud
   environment is to put them in your scratch file in code like this:

   @typecheck ```
   config = main do
     token = "xoxb-123456789012-123456789012-12345678901234567890"
     secret = "12345678901234567890123456789012"
     env = Environment.create "coffeebot"
     setValue env "slack.token" token
     setValue env "slack.secretKey" secret
   ```

   Then from UCM:

   ``` raw
   > run config
   ```

   Then delete the code from your scratch file.

   When you finally deploy your app, you will need to pass the environment
   to the {deployHttp} function and the names of the token and signing
   secret environment keys. For example:

   @typecheck ```
   coffeeBot.deploy = 
     main do
       env = Environment.create "coffeebot"
       hash =
         deployHttp
           env
           (runBot "slack.token" "slack.secretKey" tests.demos.coffeeBot)
       name = ServiceName.create "coffeebot"
       ServiceName.assign name hash
   ```

   ''run coffeeBot.deploy'' from the UCM prompt and you should be good to
   go! The bot will be available at the URL printed by the {deployHttp}
   function.

   Now you can add your bot to your Slack workspace by going to the
   **OAuth & Permissions** page of your app in the Slack API dashboard and
   clicking the **Install App to Workspace** button.

   ### Configuring your bot to receive events

       To receive events from Slack, you will need to configure your bot to
       receive events from the Slack Events API. Go to the
       **Event Subscriptions** page of your app in the Slack API dashboard
       and enable events. Then enter the URL of your bot's service as
       returned from {deployHttp}, in the **Request URL** field.

       You will also need to subscribe to the events you want to receive.
       For example, to receive messages, you will need to subscribe to the
       **message.channels** event. You can subscribe to events on the
       **Subscribe to Bot Events** page of your app in the Slack API
       dashboard.

       Unfortunately, Slack doesn't allow you to perform these
       configuration steps programmatically, so you will need to do them
       manually.

## API

   With that out of the way, here are the functions provided by this
   library.

   The {type SlackWeb} and {type SlackEvents} abilities are used to send
   requests to the Slack Web API and receive events from the Slack Events
   API respectively. To run a computation that uses these abilities, use
   {runBot}. Note that the full power of the Unison Cloud API is available
   to you when writing a Slack bot, so it can use {type cloud.State} to
   keep persistent state, {type Http} to make web requests, etc.

       @signature{runBot}

   ### Slack Web API

       The Slack Web API is used to send messages, add reactions, get
       information about conversations, etc.

       #### Sending messages

            The {toChannel} function can be used to send a message to a
            channel. For example:

            @typecheck ```
            toChannel (ChannelId "general") "Hello, world!"
            ```

            Use {toThread} to send a reply to a thread:

            @typecheck ```
            toThread
              (ChannelId "C123ABC456") (ThreadTs "123.456") "Hello, world!"
            ```

            The above methods can accept a channel ID or a channel name as
            the first argument.

            If you receive a {type events.types.Message} from the
            {type SlackEvents} API, you can use {reply} to reply to the
            message:

            @typecheck ```
            message = !appMention
            reply message "Hello, yes?"
            ```

       #### Getting conversation information

            The {conversations.info} function can be used to get
            information about a conversation, given the channel ID. For
            example:

            @typecheck ```
            conversations.info (ChannelId "C123ABC456")
            ```

       #### Getting replies to a message

            The {replies} function can be used to get the replies to a
            message, given the channel ID and the thread timestamp. It
            returns a lazy {type Stream} of replies. For example:

            @typecheck ```
            stream =
              do replies (ChannelId "C123ABC456") (ThreadTs "123.456")
            Log.info
              "Got the replies" [("replies", evalToText (toList! stream))]
            ```

       #### Adding reactions

            The {add} function can be used to add a reaction to a message,
            given the channel ID, the reaction name, and the thread
            timestamp. For example:

            @typecheck ```
            add (ChannelId "C123ABC456") "coffee" (ThreadTs "123.456")
            ```

            If you receive a {type events.types.Message} from the
            {type SlackEvents} API, you can use {react} to add a reaction
            to the message:

            @typecheck ```
            message = !channels
            if contains "coffee" (text message) then react message "coffee"
            else skip
            ```

   ### Slack Events API

       The Slack Events API is used to receive events from Slack, such as
       messages, reactions, etc.

       #### Receiving messages

            Use {api.message} to receive messages from Slack, in any
            conversations the bot is a member of (including private
            conversations):

            @typecheck ```
            msg = !api.message
            Log.info "Got a message" [("message", evalToText msg)]
            ```

            You can restrict the conversations to just channels, groups, or
            direct messages by using {channels}, {message.groups}, or {im}
            respectively. Use {mpim} to receive messages from multi-party
            direct message conversations.

            If you want to receive messages from a specific set of named
            channels, use {channelsNamed}:

            @typecheck ```
            msg = channelsNamed ["general", "random"]
            Log.info "Got a message" [("message", evalToText msg)]
            ```

       #### Receiving reactions

            Use {reactionAdded} to receive reactions added to messages,
            files, or file comments:

            @typecheck ```
            reaction = !reactionAdded
            Log.info "Got a reaction" [("reaction", evalToText reaction)]
            ```

       #### Low-level events API

            You can use the {eventOfType} function to receive any event
            from Slack of the given type (see the
            [Slack Events API documentation](https://api.slack.com/events)
            for a list of event types). For example:

            @typecheck ```
            event = eventOfType "message"
            Log.info "Got a message" [("message", evalToText event)]
            ```

            This returns an {type EventEnvelope} which contains the event
            payload as a {type Json} value. You can use the {type Decoder}
            ability to decode the event payload yourself.

       #### Skipping events

            If you want to ignore an event, you can use {skip}. For
            example, the ''coffeeBot'' example above uses this to ignore
            events that don't mention "coffee":

            @typecheck ```
            message = !channels
            if contains "coffee" (text message) then react message "coffee"
            else skip
            ```

       #### Combining event handlers

            You can use the {SlackEvents.or} and {oneOf} functions to
            combine multiple event handlers into one. For example, the
            following bot responds to messages that mention "coffee" with a
            coffee emoji and responds to messages that mention "tea" with a
            tea emoji:

            @typecheck ```
            coffeeBot =
              do
                message = !channels
                if contains "coffee" (text message) then
                  react message "coffee"
                else skip
            teaBot =
              do
                message = !channels
                if contains "tea" (text message) then react message "tea"
                else skip
            oneOf [coffeeBot, teaBot]
            ```

}}

events.api.message : '{SlackEvents} events.types.Message events.api.message = do messageOfType "message"

events.api.message.im.doc : Doc events.api.message.im.doc = {{ Handle ''message'' events sent in direct messages.

Example

This bot replies to any direct message with the text ''How interesting!'':

@typecheck ```
helloBot = do
  message = !im
  reply message "How interesting!"
``` }}

web.api.chat.reply.doc : Doc web.api.chat.reply.doc = {{ Posts a reply to a message, given the message and the reply text. The type of message must be {type events.types.Message}, which is the type of message received by a bot from the {type SlackEvents} ability.

Example

@typecheck ```
message = !appMention
reply message "Hello, yes?"
```

}}

botRoute.doc : Doc botRoute.doc = {{ Sets up a {type Route} that handles Slack events and requests to the Slack Web API.

This route is intended to be used with {deployHttp} after {Route.run}. It handles Slack events and requests to the Slack Web API and forwards them to your bot. It also handles the initial handshake request that Slack sends when the bot is first added to a workspace.

Arguments

1. The name of the {type cloud.Config} key that contains the Slack API
   token for your bot.
2. The name of the {type cloud.Config} key that contains the Slack signing
   secret.
3. The bot to run.

Example

@typecheck ```
coffeeBot =
  do
    message = !api.message
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "coffee" messageText then
      react message "coffee"
    else SlackEvents.skip
botRoute "SLACK_TOKEN" "SLACK_SIGNING_SECRET" coffeeBot
```

}}

events.api.message.channels : '{SlackEvents} events.types.Message events.api.message.channels = do msg = messageOfType "message" match channelType msg with Some "channel" -> msg _ -> SlackEvents.skip

SlackEvents.oneOf.doc : Doc SlackEvents.oneOf.doc = use SlackEvents skip use Text toLowercase use events.types.Message text use lib.base.Text contains {{ Combines a list of bots into a single bot that runs the first bot that doesn't skip.

Example

This bot reacts to any message that contains the word ''coffee'' or ''tea''
by adding a coffee or tea emoji reaction. Note that the bot will only react
to the first word it finds, so if a message contains both ''coffee'' and
''tea'', it will only react to the ''coffee'' part.

@typecheck ```
coffeeBot = do
  message = !api.message
  messageText = toLowercase (text message)
  if contains "coffee" messageText then react message "coffee" else skip
teaBot = do
  message = !api.message
  messageText = toLowercase (text message)
  if contains "tea" messageText then react message "tea" else skip
SlackEvents.oneOf [coffeeBot, teaBot]
```

}}

events.types.ReactionEvent.doc : Doc events.types.ReactionEvent.doc = {{ An event that occurs when a reaction is added to a message, file, or file comment.

A reaction event is either a {MessageReaction}, a {FileReaction}, or a {FileCommentReaction}.

Fields

* {reaction} - The name of the emoji or reaction.
* {ReactionEvent.user} - The user that added the reaction.
* {itemUser} - The user that the reaction was added to.
* {eventTs} - The timestamp of the reaction event.
* {ReactionEvent.channel} - The channel the reaction was added to, if any.
* {ReactionEvent.threadTs} - The timestamp of the message the reaction was
  added to, if any.
* {file} - The file the reaction was added to, if any.
* {fileComment} - The comment the reaction was added to, if any.

See also

{reactionAdded}

}}

events.api.message.groups : '{SlackEvents} events.types.Message events.api.message.groups = do msg = messageOfType "message" match channelType msg with Some "group" -> msg _ -> SlackEvents.skip

events.api.message.channelsNamed : [Text] ->{Exception, SlackWeb, SlackEvents} events.types.Message events.api.message.channelsNamed names = msg = !channels channelId = Message.channel msg channel = conversations.info channelId if elem (Conversation.name channel) names then msg else SlackEvents.skip

events.api.message.mpim : '{SlackEvents} events.types.Message events.api.message.mpim = do msg = messageOfType "message" match channelType msg with Some "mpim" -> msg _ -> SlackEvents.skip

events.api.eventOfType.doc : Doc events.api.eventOfType.doc = {{ Handle events of a particular type.

Example

@typecheck ```
bot = do
  message = eventOfType "message"
  Log.info "Got a message" [("message", evalToText message)]
``` }}

events.api.messageOfType : Text ->{SlackEvents} events.types.Message events.api.messageOfType typ = (EventEnvelope t timestamp user ts payload) = eventOfType typ match parsed Message.decoder payload with Right message -> message Left _ -> SlackEvents.skip

SlackEvents.run : EventCallbackReq -> '{g, SlackEvents} a ->{g} Optional a SlackEvents.run event bot = h = cases { currentEvent -> k } -> handle k event with h { SlackEvents.skip -> _ } -> Optional.None { a } -> Some a handle !bot with h

tests.demos.coffeeBot.deploy : '{IO, Exception} () tests.demos.coffeeBot.deploy = Cloud.main do env = Environment.create "coffeeBot" token = getEnv "SLACK_API_TOKEN" secret = getEnv "SLACK_SIGNING_SECRET" setValue env "slack.token" token setValue env "slack.secretKey" secret hash = deployHttp env (runBot "slack.token" "slack.secretKey" coffeeBot) name = ServiceName.create "coffeebot" ServiceName.assign name hash

events.api.message.groups.doc : Doc events.api.message.groups.doc = {{ Handle ''message'' events sent to groups.

Example

This bot reacts to any message sent to a group if the message contains the
word ''coffee'', by adding a coffee emoji reaction:

@typecheck ```
coffeeBot =
  do
    message = !message.groups
    messageText = Text.toLowercase (events.types.Message.text message)
    if lib.base.Text.contains "coffee" messageText then
      react message "coffee"
    else SlackEvents.skip
```

}}

botRoute : Text -> Text -> '{g, SlackWeb, SlackEvents} () ->{g, Route, cloud.Config, Exception, Http, Remote, cloud.Log} () botRoute slackTokenConfigKey slackSigningSecretConfigKey bot = use Route <|> sesh = makeSlackSession slackTokenConfigKey webHandled = do SlackWeb.run sesh bot evHandled = do eventRoute webHandled handshaker = internal.handshake <|> evHandled authAdded = authRoute slackSigningSecretConfigKey handshaker !authAdded

SlackEvents.doc : Doc SlackEvents.doc = use Log info {{ The ability to handle Slack events.

The basic usage is to use the {eventOfType} function to handle events of a particular type. For example, to handle all ''message'' events, you would write:

  @typecheck ```
  message = eventOfType "message"
  info "Got a message" [("message", evalToText message)]
  ```

{eventOfType} returns a {type EventEnvelope} value, which contains the original JSON of the event in the {EventEnvelope.payload} field. You can then use the {type Decoder} ability to decode the JSON into a more useful type. See below for a number of functions that do this for you.

The {eventOfType} function will skip any events that are not of the given type. If you want to handle all events, you can use the {currentEvent} ability constructor:

  @typecheck ```
  event = currentEvent
  info "Got an event" [("event", evalToText event)]
  ```

You can then do further pattern matching on the to handle different types of events, and you can use {SlackEvents.skip} to skip events that you don't want to handle.

The {currentEvent} constructor returns a {type EventCallbackReq} value, which contains a number of fields that are common to all events. The {type EventEnvelope} value is contained in the {events.types.EventCallbackReq.event} field.

Event handlers for specific event types

The following functions are provided to handle specific event types:

* {appMention} - Handle ''app_mention'' events.
* {api.message} - Handle ''message'' events.
* {channels} - Handle ''message'' events sent to channels.
* {channelsNamed} - Handle ''message'' events sent to a specific set of
  channels.
* {message.groups} - Handle ''message'' events sent to groups.
* {im} - Handle ''message'' events sent in direct messages.
* {mpim} - Handle ''message'' events sent in multi-person direct messages.
* {reactionAdded} - Handle ''reaction_added'' events.

}}

events.api.reactionAdded : '{Exception, SlackEvents} ReactionEvent events.api.reactionAdded = do event = eventOfType "reaction_added" decoder = do use Decoder text use Text ++ use object at! reaction = at! "reaction" text user = EventEnvelope.user event itemUser = UserId (at! "item_user" text) eventTs = eventTimestamp event at! "item" do itemType = at! "type" text match itemType with "message" -> channel = ChannelId (at! "channel" text) ts = ThreadTs (at! "ts" text) MessageReaction reaction user itemUser eventTs channel ts "file" -> file = FileId (at! "file" text) FileReaction reaction user itemUser eventTs file "file_comment" -> file = FileId (at! "file" text) comment = FileCommentId (at! "filecomment" text) FileCommentReaction reaction user itemUser eventTs file comment -> Decoder.fail ("unknown item type: " ++ itemType) Decoder.reraise (parsed decoder (EventEnvelope.payload event))

SlackEvents.run.doc : Doc SlackEvents.run.doc = {{ Runs a bot on a particular event.

Example

@typecheck ```
bot = do
  message = !api.message
  Log.info "Got a message" [("message", evalToText message)]
event =
    EventCallbackReq
      ""
      ""
      ""
      (EventEnvelope
        "message" (EventTs "123") (UserId "U123") (EventTs "123") value!)
      CallbackType.EventCallback
      ""
      ""
      epoch
SlackEvents.run event bot
```

}}

unique ability SlackEvents where skip : {SlackEvents} a currentEvent : {SlackEvents} EventCallbackReq

events.internal.eventRoute : '{g, SlackEvents} () ->{g, Route, Exception, cloud.Log} () events.internal.eventRoute k = use Log error use Optional None use badRequest text Route.noCapture POST top requestBody = !bodyUtf8 contentType = List.head (request.header "Content-Type") |> Optional.getOrElse "" event = if Text.startsWith "application/json" contentType then Log.info "Got an event" [("body", requestBody)] Some (logException '(Decoder.run EventCallbackReq.decoder requestBody)) else error "Got an unknown content type" [("Content-Type", contentType)] text "Unsupported content type" None match event with Some ev -> h = cases { SlackEvents.currentEvent -> k } -> handle k ev with h { SlackEvents.skip -> _ } -> () { a } -> a handle !k with h None -> error "Couldn't parse event" [("body", requestBody)] text "Couldn't parse event"



3. `update`

Generates a parse error and leaves code in the scratch file that it can't parse.
pchiusano commented 11 months ago

@runarorama can you make a branch for this since by the time someone looks at this, /main might be in a different state?

runarorama commented 11 months ago

The error is:


  offset=180:
  unexpected =
     50 |           messageText = Text.toLowercase (events.types.Message.text message)
runarorama commented 11 months ago

The definition that can't be parsed is:

events.api.message.channels.doc : Doc
events.api.message.channels.doc =
  {{
  Handle ''message'' events sent to channels.

  # Example

    This bot reacts to any message sent to a channel if the message contains
    the word ''coffee'', by adding a coffee emoji reaction:

    {{ docExample 0 do
          coffeeBot =
            do
              message = !channels
              messageText = Text.toLowercase (events.types.Message.text message)
              if lib.base.Text.contains "coffee" messageText then
                react message "coffee"
              else SlackEvents.skip
    }}
  }}

Note that this definition is pretty-printed without the @typecheck fence and instead has a docExample call in a quasiquote, so this is ultimately running into #4384

runarorama commented 11 months ago

This is now on a branch: https://share.unison-lang.org/@runarorama/slack/code/@runarorama/updatebug

aryairani commented 6 months ago

I did clone @runarorama/slack/@runarorama/updatebug and then loaded the scratch file in the original description https://gist.github.com/aryairani/2b4589b3de90523e9043e823bdb4864f, but got:

  Loading changes detected in ~/work/unison/trunk/scratch.u.

  I found and typechecked the definitions in ~/work/unison/trunk/scratch.u. This file has been
  previously added to the codebase.

Did something maybe change on the @runarorama/updatebug branch?

I also did update, but it's expected to be a no-op, and seemed to be, apart from losing metadata, which I think update always does now, as we are no longer actively supporting it.