From 35e10923522846374be46e0d436fe8aaa5630175 Mon Sep 17 00:00:00 2001 From: Marek Kubica Date: Thu, 25 Jan 2024 16:30:46 +0100 Subject: [PATCH] Reformat with ocamlformat & jq --- .ocamlformat | 2 + src/cli/dune | 6 +- src/cli/slack_notify.ml | 50 +- src/lib/dune | 13 +- src/lib/slacko.ml | 2091 ++++++++++++++++-------------------- src/lib/slacko.mli | 1203 +++++++++++++-------- src/lib/timestamp.ml | 35 +- src/lib/timestamp.mli | 3 - test/abbrtypes.ml | 571 +++++----- test/dune | 25 +- test/fake_slack.ml | 48 +- test/files.json | 262 ++--- test/groups.json | 44 +- test/ims.json | 32 +- test/random_history.json | 58 +- test/seekrit_history.json | 86 +- test/slackbot_history.json | 18 +- test/test_slacko.ml | 312 +++--- test/users.json | 142 +-- 19 files changed, 2532 insertions(+), 2469 deletions(-) create mode 100644 .ocamlformat diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..9c00f44 --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,2 @@ +version=0.26.1 +profile=conventional diff --git a/src/cli/dune b/src/cli/dune index c835954..72b47eb 100644 --- a/src/cli/dune +++ b/src/cli/dune @@ -1,4 +1,4 @@ (executable - (name slack_notify) - (public_name slack-notify) - (libraries slacko cmdliner)) + (name slack_notify) + (public_name slack-notify) + (libraries slacko cmdliner)) diff --git a/src/cli/slack_notify.ml b/src/cli/slack_notify.ml index 6ea1c62..a9d85f0 100644 --- a/src/cli/slack_notify.ml +++ b/src/cli/slack_notify.ml @@ -20,27 +20,35 @@ let base_url = let doc = "The Slack API base URL" in - Cmdliner.Arg.(value & opt (some string) None & info ["base-url"] ~docv:"URL" ~doc) + Cmdliner.Arg.( + value & opt (some string) None & info [ "base-url" ] ~docv:"URL" ~doc) let token = let doc = "The Slack API access token" in - Cmdliner.Arg.(required & opt (some string) None & info ["t"; "token"] ~docv:"TOKEN" ~doc) + Cmdliner.Arg.( + required & opt (some string) None & info [ "t"; "token" ] ~docv:"TOKEN" ~doc) let username = let doc = "Name of the bot in the chat" in - Cmdliner.Arg.(value & opt (some string) None & info ["u"; "username"] ~docv:"USERNAME" ~doc) + Cmdliner.Arg.( + value + & opt (some string) None + & info [ "u"; "username" ] ~docv:"USERNAME" ~doc) let icon_url = let doc = "URL to an image to use as the icon for this message" in - Cmdliner.Arg.(value & opt (some string) None & info ["icon-url"] ~docv:"URL" ~doc) + Cmdliner.Arg.( + value & opt (some string) None & info [ "icon-url" ] ~docv:"URL" ~doc) let icon_emoji = let doc = "Emoji to use as the icon for this message. Overrides icon-url" in - Cmdliner.Arg.(value & opt (some string) None & info ["icon-emoji"] ~docv:"EMOJI" ~doc) + Cmdliner.Arg.( + value & opt (some string) None & info [ "icon-emoji" ] ~docv:"EMOJI" ~doc) let channel = let doc = "Name of the channel to post to" in - Cmdliner.Arg.(required & pos 0 (some string) None & info [] ~docv:"CHANNEL" ~doc) + Cmdliner.Arg.( + required & pos 0 (some string) None & info [] ~docv:"CHANNEL" ~doc) let message = let doc = "Message to send" in @@ -48,16 +56,18 @@ let message = let attachment = let doc = "Attachment text" in - Cmdliner.Arg.(value & opt (some string) None & info ["attachment"] ~docv:"MSG" ~doc) + Cmdliner.Arg.( + value & opt (some string) None & info [ "attachment" ] ~docv:"MSG" ~doc) let info = let doc = "Posts messages to Slack" in Cmdliner.Cmd.info "slack-notify" ~doc -let execute base_url token username channel icon_url icon_emoji attachment_text msg = +let execute base_url token username channel icon_url icon_emoji attachment_text + msg = "Your token is " ^ token ^ ", the channel is " ^ channel - ^ " and the message is '" ^ msg ^ "'." - |> print_endline; + ^ " and the message is '" ^ msg ^ "'." + |> print_endline; let string_or_bust = function | `Success _ -> "Message posted" @@ -77,21 +87,17 @@ let execute base_url token username channel icon_url icon_emoji attachment_text let attachments = match attachment_text with | None -> None - | Some text -> Some [Slacko.attachment ~text ()] + | Some text -> Some [ Slacko.attachment ~text () ] in - Slacko.chat_post_message session chat - ?username:(username) - ?icon_emoji:(icon_emoji) - ?icon_url:(icon_url) - ?attachments:(attachments) - msg - >|= (fun c -> - print_endline @@ string_or_bust c) + Slacko.chat_post_message session chat ?username ?icon_emoji ?icon_url + ?attachments msg + >|= (fun c -> print_endline @@ string_or_bust c) |> Lwt_main.run -let execute_t = Cmdliner.Term.( - const execute $ base_url $ token $ username $ channel $ icon_url $ icon_emoji - $ attachment $ message) +let execute_t = + Cmdliner.Term.( + const execute $ base_url $ token $ username $ channel $ icon_url + $ icon_emoji $ attachment $ message) let () = let cmd = Cmdliner.Cmd.v info execute_t in diff --git a/src/lib/dune b/src/lib/dune index 61ebba3..3c9b714 100644 --- a/src/lib/dune +++ b/src/lib/dune @@ -1,7 +1,8 @@ (library - (name slacko) - (public_name slacko) - (synopsis "A neat interface for Slack") - (private_modules Timestamp) - (libraries lwt cohttp-lwt-unix yojson ppx_deriving_yojson.runtime ptime) - (preprocess (pps ppx_deriving_yojson))) + (name slacko) + (public_name slacko) + (synopsis "A neat interface for Slack") + (private_modules Timestamp) + (libraries lwt cohttp-lwt-unix yojson ppx_deriving_yojson.runtime ptime) + (preprocess + (pps ppx_deriving_yojson))) diff --git a/src/lib/slacko.ml b/src/lib/slacko.ml index a901bab..9d2562a 100644 --- a/src/lib/slacko.ml +++ b/src/lib/slacko.ml @@ -23,193 +23,81 @@ open Lwt.Syntax module Cohttp_unix = Cohttp_lwt_unix module Cohttp_body = Cohttp_lwt.Body -type api_error = [ - | `Unhandled_error of string - | `Unknown_error -] - -type parsed_api_error = [ - | `ParseFailure of string - | api_error -] - -type auth_error = [ - | `Not_authed - | `Invalid_auth - | `Account_inactive -] - -type timestamp_error = [ - | `Invalid_ts_latest - | `Invalid_ts_oldest -] - -type channel_error = [ - | `Channel_not_found -] - -type user_error = [ - | `User_not_found -] - -type invite_error = [ - | `Cant_invite_self - | `Cant_invite -] - -type not_in_channel_error = [ - | `Not_in_channel -] - -type already_in_channel_error = [ - | `Already_in_channel -] - -type archive_error = [ - | `Is_archived -] - -type name_error = [ - | `Name_taken -] - -type kick_error = [ - | `Cant_kick_self -] - -type channel_kick_error = [ - | kick_error - | `Cant_kick_from_general - | `Cant_kick_from_last_channel -] - -type restriction_error = [ - | `Restricted_action -] - -type leave_general_error = [ - | `Cant_leave_general -] - -type message_error = [ - | `Cant_delete_message - | `Message_not_found -] - -type message_length_error = [ - | `Msg_too_long -] - -type attachments_error = [ - | `Too_many_attachments -] - -type rate_error = [ - | `Rate_limited -] - -type message_update_error = [ - | `Message_not_found - | `Cant_update_message - | `Edit_window_closed -] - -type file_error = [ - | `File_not_found - | `File_deleted -] - -type unknown_type_error = [ - | `Unknown_type -] - -type already_archived_error = [ - | `Already_archived -] - -type not_in_group_error = [ - | `Not_in_group -] - -type leave_last_channel_error = [ - | `Cant_leave_last_channel -] - -type last_member_error = [ - | `Last_member -] - -type oauth_error = [ - | `Invalid_client_id +type api_error = [ `Unhandled_error of string | `Unknown_error ] +type parsed_api_error = [ `ParseFailure of string | api_error ] +type auth_error = [ `Not_authed | `Invalid_auth | `Account_inactive ] +type timestamp_error = [ `Invalid_ts_latest | `Invalid_ts_oldest ] +type channel_error = [ `Channel_not_found ] +type user_error = [ `User_not_found ] +type invite_error = [ `Cant_invite_self | `Cant_invite ] +type not_in_channel_error = [ `Not_in_channel ] +type already_in_channel_error = [ `Already_in_channel ] +type archive_error = [ `Is_archived ] +type name_error = [ `Name_taken ] +type kick_error = [ `Cant_kick_self ] + +type channel_kick_error = + [ kick_error | `Cant_kick_from_general | `Cant_kick_from_last_channel ] + +type restriction_error = [ `Restricted_action ] +type leave_general_error = [ `Cant_leave_general ] +type message_error = [ `Cant_delete_message | `Message_not_found ] +type message_length_error = [ `Msg_too_long ] +type attachments_error = [ `Too_many_attachments ] +type rate_error = [ `Rate_limited ] + +type message_update_error = + [ `Message_not_found | `Cant_update_message | `Edit_window_closed ] + +type file_error = [ `File_not_found | `File_deleted ] +type unknown_type_error = [ `Unknown_type ] +type already_archived_error = [ `Already_archived ] +type not_in_group_error = [ `Not_in_group ] +type leave_last_channel_error = [ `Cant_leave_last_channel ] +type last_member_error = [ `Last_member ] + +type oauth_error = + [ `Invalid_client_id | `Bad_client_secret | `Invalid_code | `Bad_redirect_uri - | `Unknown_error -] + | `Unknown_error ] -type presence_error = [ - | `Invalid_presence -] +type presence_error = [ `Invalid_presence ] +type user_visibility_error = [ `User_not_visible ] +type invalid_name_error = [ `Invalid_name ] +type bot_error = [ `User_is_bot ] +type parsed_auth_error = [ parsed_api_error | auth_error ] -type user_visibility_error = [ - | `User_not_visible -] - -type invalid_name_error = [ - | `Invalid_name -] - -type bot_error = [ - | `User_is_bot -] - -type parsed_auth_error = [ - | parsed_api_error - | auth_error -] - -type topic_result = [ - | `Success of string +type topic_result = + [ `Success of string | parsed_auth_error | channel_error | archive_error | not_in_channel_error - | `User_is_restricted -] + | `User_is_restricted ] type timestamp = Ptime.t type session = { - base_url: string; - token: string; - (* Mutable id cache goes here? *) + base_url : string; + token : string; (* Mutable id cache goes here? *) } type token = session - type topic = string - type message = string - type channel = ChannelId of string | ChannelName of string - type conversation = string - type im = string - type user = UserId of string | UserName of string - type bot = BotId of string - type group = GroupId of string | GroupName of string (* TODO: Sure about user? *) type chat = Channel of channel | Im of im | User of user | Group of group - type sort_criterion = Score | Timestamp - type sort_direction = Ascending | Descending - type presence = Auto | Away let user_of_yojson = function @@ -223,7 +111,6 @@ let bot_of_yojson = function | `String x -> Ok (bot_of_string x) | _ -> Error "Couldn't parse bot type" - let channel_of_yojson = function | `String x -> Ok (ChannelId x) | _ -> Error "Couldn't parse channel type" @@ -240,495 +127,475 @@ let im_of_yojson = function | `String x -> Ok x | _ -> Error "Couldn't parse im type" -type topic_obj = { - value: string; - creator: user; - last_set: Timestamp.t; -} [@@deriving of_yojson] +type topic_obj = { value : string; creator : user; last_set : Timestamp.t } +[@@deriving of_yojson] type channel_obj = { - id: channel; - name: string; - is_channel: bool; - created: Timestamp.t; - creator: user; - is_archived: bool; - is_general: bool; - name_normalized: string; - is_member: bool; - members: user list; - topic: topic_obj; - purpose: topic_obj; - last_read: Timestamp.t option [@default None]; - latest: Yojson.Safe.t option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - num_members: int option [@default None]; -} [@@deriving of_yojson { strict = false }] + id : channel; + name : string; + is_channel : bool; + created : Timestamp.t; + creator : user; + is_archived : bool; + is_general : bool; + name_normalized : string; + is_member : bool; + members : user list; + topic : topic_obj; + purpose : topic_obj; + last_read : Timestamp.t option; [@default None] + latest : Yojson.Safe.t option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + num_members : int option; [@default None] +} +[@@deriving of_yojson { strict = false }] type conversation_obj = { - id: conversation; - name: string; - is_channel: bool; - created: Timestamp.t; - creator: user; - is_archived: bool; - is_general: bool; - name_normalized: string; - is_member: bool; - topic: topic_obj; - purpose: topic_obj; - last_read: Timestamp.t option [@default None]; - latest: string option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - num_members: int option [@default None]; -} [@@deriving of_yojson { strict = false }] - + id : conversation; + name : string; + is_channel : bool; + created : Timestamp.t; + creator : user; + is_archived : bool; + is_general : bool; + name_normalized : string; + is_member : bool; + topic : topic_obj; + purpose : topic_obj; + last_read : Timestamp.t option; [@default None] + latest : string option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + num_members : int option; [@default None] +} +[@@deriving of_yojson { strict = false }] type user_obj = { - id: user; - name: string; - deleted: bool; - color: string option [@default None]; - real_name: string option [@default None]; - tz: string option [@default None]; - tz_label: string option [@default None]; - tz_offset: int [@default 0]; - profile: Yojson.Safe.t; - is_admin: bool [@default false]; - is_owner: bool [@default false]; - is_primary_owner: bool [@default false]; - is_restricted: bool [@default false]; - is_ultra_restricted: bool [@default false]; - is_bot: bool; - has_files: bool [@default false]; -} [@@deriving of_yojson { strict = false } ] + id : user; + name : string; + deleted : bool; + color : string option; [@default None] + real_name : string option; [@default None] + tz : string option; [@default None] + tz_label : string option; [@default None] + tz_offset : int; [@default 0] + profile : Yojson.Safe.t; + is_admin : bool; [@default false] + is_owner : bool; [@default false] + is_primary_owner : bool; [@default false] + is_restricted : bool; [@default false] + is_ultra_restricted : bool; [@default false] + is_bot : bool; + has_files : bool; [@default false] +} +[@@deriving of_yojson { strict = false }] type group_obj = { - id: group; - name: string; - is_group: bool; - created: Timestamp.t; - creator: user; - is_archived: bool; - members: user list; - topic: topic_obj; - purpose: topic_obj; - is_open: bool option [@default None]; - last_read: Timestamp.t option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - latest: Yojson.Safe.t option [@default None]; -} [@@deriving of_yojson { strict = false }] + id : group; + name : string; + is_group : bool; + created : Timestamp.t; + creator : user; + is_archived : bool; + members : user list; + topic : topic_obj; + purpose : topic_obj; + is_open : bool option; [@default None] + last_read : Timestamp.t option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + latest : Yojson.Safe.t option; [@default None] +} +[@@deriving of_yojson { strict = false }] type file_obj = { (* TODO file id type *) - id: string; - created: Timestamp.t; + id : string; + created : Timestamp.t; (* deprecated *) - timestamp: Timestamp.t; - - name: string option [@default None]; - title: string; - mimetype: string; - pretty_type: string; - user: user; - - mode: string; - editable: bool; - is_external: bool; - external_type: string; - - size: int; - - url_private: string; - url_private_download: string; - - thumb_64: string option [@default None]; - thunb_80: string option [@default None]; - thumb_360: string option [@default None]; - thumb_360_gif: string option [@default None]; - thumb_360_w: int option [@default None]; - thumb_360_h: int option [@default None]; - - permalink: string; - edit_link: string option [@default None]; - preview: string option [@default None]; - preview_highlight: string option [@default None]; - lines: int option [@default None]; - lines_more: int option [@default None]; - - is_public: bool; + timestamp : Timestamp.t; + name : string option; [@default None] + title : string; + mimetype : string; + pretty_type : string; + user : user; + mode : string; + editable : bool; + is_external : bool; + external_type : string; + size : int; + url_private : string; + url_private_download : string; + thumb_64 : string option; [@default None] + thunb_80 : string option; [@default None] + thumb_360 : string option; [@default None] + thumb_360_gif : string option; [@default None] + thumb_360_w : int option; [@default None] + thumb_360_h : int option; [@default None] + permalink : string; + edit_link : string option; [@default None] + preview : string option; [@default None] + preview_highlight : string option; [@default None] + lines : int option; [@default None] + lines_more : int option; [@default None] + is_public : bool; (*public_url_shared: ???;*) - channels: channel list; - groups: group list; - ims: im list; - initial_comment: Yojson.Safe.t option [@default None]; - num_stars: int option [@default None]; -} [@@deriving of_yojson { strict = false }] + channels : channel list; + groups : group list; + ims : im list; + initial_comment : Yojson.Safe.t option; [@default None] + num_stars : int option; [@default None] +} +[@@deriving of_yojson { strict = false }] type field_obj = { - title: string option [@default None]; - value: string [@default ""]; - short: bool [@default false]; -} [@@deriving to_yojson { strict = false }] - -let field ?title ?(short=false) value = { - title; - value; - short; + title : string option; [@default None] + value : string; [@default ""] + short : bool; [@default false] } +[@@deriving to_yojson { strict = false }] + +let field ?title ?(short = false) value = { title; value; short } type attachment_obj = { - fallback: string option [@default None]; - color: string option [@default None]; - pretext: string option [@default None]; - author_name: string option [@default None]; - author_link: string option [@default None]; - author_icon: string option [@default None]; - title: string option [@default None]; - title_link: string option [@default None]; - text: string option [@default None]; - fields: field_obj list option [@default None]; - image_url: string option [@default None]; - thumb_url: string option [@default None]; - footer: string option [@default None]; - footer_icon: string option [@default None]; - ts: Timestamp.t option [@default None]; - mrkdwn_in: string list option [@default None]; -} [@@deriving to_yojson { strict = false }] - -let if_none a b = - match a with - | Some v -> Some v - | None -> b - -let attachment - ?fallback ?color ?pretext - ?author_name ?author_link ?author_icon - ?title ?title_link - ?text ?fields - ?image_url ?thumb_url - ?footer ?footer_icon - ?ts ?mrkdwn_in - () = { - fallback = if_none fallback text; - color; - pretext; - author_name; - author_link; - author_icon; - title; - title_link; - text; - fields; - image_url; - thumb_url; - footer; - footer_icon; - ts; - mrkdwn_in; + fallback : string option; [@default None] + color : string option; [@default None] + pretext : string option; [@default None] + author_name : string option; [@default None] + author_link : string option; [@default None] + author_icon : string option; [@default None] + title : string option; [@default None] + title_link : string option; [@default None] + text : string option; [@default None] + fields : field_obj list option; [@default None] + image_url : string option; [@default None] + thumb_url : string option; [@default None] + footer : string option; [@default None] + footer_icon : string option; [@default None] + ts : Timestamp.t option; [@default None] + mrkdwn_in : string list option; [@default None] } +[@@deriving to_yojson { strict = false }] + +let if_none a b = match a with Some v -> Some v | None -> b + +let attachment ?fallback ?color ?pretext ?author_name ?author_link ?author_icon + ?title ?title_link ?text ?fields ?image_url ?thumb_url ?footer ?footer_icon + ?ts ?mrkdwn_in () = + { + fallback = if_none fallback text; + color; + pretext; + author_name; + author_link; + author_icon; + title; + title_link; + text; + fields; + image_url; + thumb_url; + footer; + footer_icon; + ts; + mrkdwn_in; + } type message_obj = { - type': string [@key "type"]; - ts: Timestamp.t; - user: user option [@default None]; - bot_id: bot option [@default None]; - text: string option; - is_starred: bool option [@default None]; -} [@@deriving of_yojson { strict = false }] + type' : string; [@key "type"] + ts : Timestamp.t; + user : user option; [@default None] + bot_id : bot option; [@default None] + text : string option; + is_starred : bool option; [@default None] +} +[@@deriving of_yojson { strict = false }] type history_obj = { - latest: Timestamp.t option [@default None]; - messages: message_obj list; - has_more: bool; -} [@@deriving of_yojson { strict = false }] + latest : Timestamp.t option; [@default None] + messages : message_obj list; + has_more : bool; +} +[@@deriving of_yojson { strict = false }] type authed_obj = { - url: string; - team: string; - user: string; - team_id: string; - user_id: user; -} [@@deriving of_yojson { strict = false }] + url : string; + team : string; + user : string; + team_id : string; + user_id : user; +} +[@@deriving of_yojson { strict = false }] -type channel_leave_obj = { - not_in_channel: bool option [@default None]; -} [@@deriving of_yojson { strict = false }] +type channel_leave_obj = { not_in_channel : bool option [@default None] } +[@@deriving of_yojson { strict = false }] type channel_rename_obj = { - id: channel; - is_channel: bool; - name: string; - created: Timestamp.t; -} [@@deriving of_yojson { strict = false }] + id : channel; + is_channel : bool; + name : string; + created : Timestamp.t; +} +[@@deriving of_yojson { strict = false }] let chat_of_yojson = function - | `String c -> (match c.[0] with - | 'C' -> Ok (Channel (ChannelId c)) - | 'D' -> Ok (Im c) - | 'G' -> Ok (Group (GroupId c)) - | _ -> Error "Failed to parse chat") + | `String c -> ( + match c.[0] with + | 'C' -> Ok (Channel (ChannelId c)) + | 'D' -> Ok (Im c) + | 'G' -> Ok (Group (GroupId c)) + | _ -> Error "Failed to parse chat") | _ -> Error "Failed to parse chat" type chat_obj = { - ts: Timestamp.t; - chat: chat [@key "channel"]; - text: string option [@default None]; -} [@@deriving of_yojson { strict = false }] + ts : Timestamp.t; + chat : chat; [@key "channel"] + text : string option; [@default None] +} +[@@deriving of_yojson { strict = false }] -type emoji = (string * string) -type emoji_list_obj = { - emoji: (string * string) list; -} [@@deriving of_yojson] +type emoji = string * string +type emoji_list_obj = { emoji : (string * string) list } [@@deriving of_yojson] type chat_close_obj = { - no_op: bool option [@default None]; - already_closed: bool option [@default None]; -} [@@deriving of_yojson { strict = false }] + no_op : bool option; [@default None] + already_closed : bool option; [@default None] +} +[@@deriving of_yojson { strict = false }] type groups_invite_obj = { - already_in_group: bool option [@default None]; - group: group_obj; -} [@@deriving of_yojson { strict = false }] + already_in_group : bool option; [@default None] + group : group_obj; +} +[@@deriving of_yojson { strict = false }] type groups_open_obj = { - no_op: bool option [@default None]; - already_open: bool option [@default None]; -} [@@deriving of_yojson { strict = false }] + no_op : bool option; [@default None] + already_open : bool option; [@default None] +} +[@@deriving of_yojson { strict = false }] type groups_rename_obj = { - id: channel; - is_group: bool; - name: string; - created: Timestamp.t -} [@@deriving of_yojson { strict = false }] + id : channel; + is_group : bool; + name : string; + created : Timestamp.t; +} +[@@deriving of_yojson { strict = false }] type im_obj = { - id: string; - is_im: bool; - user: user; - created: Timestamp.t; - is_user_deleted: bool; - is_open: bool option [@default None]; - last_read: Timestamp.t option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; -} [@@deriving of_yojson { strict = false }] - -type im_channel_obj = { - id: string; -} [@@deriving of_yojson { strict = false }] + id : string; + is_im : bool; + user : user; + created : Timestamp.t; + is_user_deleted : bool; + is_open : bool option; [@default None] + last_read : Timestamp.t option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] +} +[@@deriving of_yojson { strict = false }] + +type im_channel_obj = { id : string } [@@deriving of_yojson { strict = false }] type im_open_obj = { - no_op: bool option [@default None]; - already_open: bool option [@default None]; - channel: im_channel_obj; -} [@@deriving of_yojson { strict = false }] + no_op : bool option; [@default None] + already_open : bool option; [@default None] + channel : im_channel_obj; +} +[@@deriving of_yojson { strict = false }] -type oauth_obj = { - access_token: string; - scope: string; -} [@@deriving of_yojson { strict = false }] +type oauth_obj = { access_token : string; scope : string } +[@@deriving of_yojson { strict = false }] type comment_obj = { - id: string; - timestamp: Timestamp.t; - user: user; - comment: string; -} [@@deriving of_yojson { strict = false }] - -type paging_obj = { - count: int; - total: int; - page: int; - pages: int; -} [@@deriving of_yojson { strict = false }] + id : string; + timestamp : Timestamp.t; + user : user; + comment : string; +} +[@@deriving of_yojson { strict = false }] + +type paging_obj = { count : int; total : int; page : int; pages : int } +[@@deriving of_yojson { strict = false }] type files_info_obj = { - file: file_obj; - comments: comment_obj list; - paging: paging_obj; -} [@@deriving of_yojson { strict = false }] + file : file_obj; + comments : comment_obj list; + paging : paging_obj; +} +[@@deriving of_yojson { strict = false }] -type files_list_obj = { - files: file_obj list; - paging: paging_obj; -} [@@deriving of_yojson { strict = false }] +type files_list_obj = { files : file_obj list; paging : paging_obj } +[@@deriving of_yojson { strict = false }] type stars_list_obj = { (* TODO proper types *) - items: Yojson.Safe.t list; - paging: paging_obj; -} [@@deriving of_yojson { strict = false }] + items : Yojson.Safe.t list; + paging : paging_obj; +} +[@@deriving of_yojson { strict = false }] type message_search_obj = { - total: int; - paging: paging_obj; - matches: message_obj list; -} [@@deriving of_yojson { strict = false }] + total : int; + paging : paging_obj; + matches : message_obj list; +} +[@@deriving of_yojson { strict = false }] type file_search_obj = { - total: int; - paging: paging_obj; - matches: file_obj list; -} [@@deriving of_yojson { strict = false }] + total : int; + paging : paging_obj; + matches : file_obj list; +} +[@@deriving of_yojson { strict = false }] type search_obj = { - query: string; - messages: message_search_obj option [@default None]; - files: file_search_obj option [@default None]; -} [@@deriving of_yojson { strict = false }] + query : string; + messages : message_search_obj option; [@default None] + files : file_search_obj option; [@default None] +} +[@@deriving of_yojson { strict = false }] type team_obj = { (* TODO team id *) - id: string; - name: string; - domain: string; - email_domain: string; - icon: Yojson.Safe.t; -} [@@deriving of_yojson { strict = false }] + id : string; + name : string; + domain : string; + email_domain : string; + icon : Yojson.Safe.t; +} +[@@deriving of_yojson { strict = false }] type login_obj = { - user_id: user; - username: string; - date_first: Timestamp.t; - date_last: Timestamp.t; - count: int; - ip: string; - user_agent: string; - isp: string; - country: string; - region: string; -} [@@deriving of_yojson { strict = false }] - -type team_access_log_obj = { - logins: login_obj list; - paging: paging_obj; -} [@@deriving of_yojson { strict = false }] - -type history_result = [ - | `Success of history_obj + user_id : user; + username : string; + date_first : Timestamp.t; + date_last : Timestamp.t; + count : int; + ip : string; + user_agent : string; + isp : string; + country : string; + region : string; +} +[@@deriving of_yojson { strict = false }] + +type team_access_log_obj = { logins : login_obj list; paging : paging_obj } +[@@deriving of_yojson { strict = false }] + +type history_result = + [ `Success of history_obj | parsed_auth_error | channel_error - | timestamp_error -] + | timestamp_error ] (* internal *) let default_base_url = "https://slack.com/api/" -type api_request = { - method': string; - arguments: (string * string) list -} +type api_request = { method' : string; arguments : (string * string) list } let api_request method' = { method'; arguments = [] } -let optionally_add key value request = match value with +let optionally_add key value request = + match value with | None -> request - | Some value -> { request with arguments = (key, value)::request.arguments } + | Some value -> { request with arguments = (key, value) :: request.arguments } let definitely_add key value = optionally_add key (Some value) (* private API return type *) (* the strict is important here, because we just match ok & error and * deliberately ignore the rest *) -type api_answer = { - ok: bool; - error: string option [@default None] -} [@@deriving of_yojson { strict = false }] +type api_answer = { ok : bool; error : string option [@default None] } +[@@deriving of_yojson { strict = false }] let validate json = match api_answer_of_yojson json with | Error str -> `ParseFailure str - | Ok parsed -> match parsed.ok, parsed.error with - | true, _ -> `Json_response json - | _, Some "account_inactive" -> `Account_inactive - | _, Some "already_archived" -> `Already_archived - | _, Some "already_in_channel" -> `Already_in_channel - | _, Some "bad_client_secret" -> `Bad_client_secret - | _, Some "bad_redirect_uri" -> `Bad_redirect_uri - | _, Some "cant_archive_general" -> `Cant_archive_general - | _, Some "cant_invite" -> `Cant_invite - | _, Some "cant_invite_self" -> `Cant_invite_self - | _, Some "cant_delete_file" -> `Cant_delete_file - | _, Some "cant_delete_message" -> `Cant_delete_message - | _, Some "cant_kick_from_general" -> `Cant_kick_from_general - | _, Some "cant_kick_from_last_channel" -> `Cant_kick_from_last_channel - | _, Some "cant_kick_self" -> `Cant_kick_self - | _, Some "cant_leave_general" -> `Cant_leave_general - | _, Some "cant_leave_last_channel" -> `Cant_leave_last_channel - | _, Some "cant_update_message" -> `Cant_update_message - | _, Some "channel_not_found" -> `Channel_not_found - | _, Some "edit_window_closed" -> `Edit_window_closed - | _, Some "file_deleted" -> `File_deleted - | _, Some "file_not_found" -> `File_not_found - | _, Some "invalid_auth" -> `Invalid_auth - | _, Some "invalid_client_id" -> `Invalid_client_id - | _, Some "invalid_code" -> `Invalid_code - | _, Some "invalid_name" -> `Invalid_name - | _, Some "invalid_presence" -> `Invalid_presence - | _, Some "invalid_ts_latest" -> `Invalid_ts_latest - | _, Some "invalid_ts_oldest" -> `Invalid_ts_oldest - | _, Some "is_archived" -> `Is_archived - | _, Some "last_member" -> `Last_member - | _, Some "last_ra_channel" -> `Last_restricted_channel - | _, Some "message_not_found" -> `Message_not_found - (* not supposed to happen *) - | _, Some "msg_too_long" -> `Msg_too_long - | _, Some "too_many_attachments" -> `Too_many_attachments - | _, Some "name_taken" -> `Name_taken - (* can't really happen *) - | _, Some "no_channel" -> `No_channel - (* can't really happen either *) - | _, Some "no_text" -> `No_text - | _, Some "not_archived" -> `Not_archived - | _, Some "not_authed" -> `Not_authed - | _, Some "not_authorized" -> `Not_authorized - | _, Some "not_in_channel" -> `Not_in_channel - | _, Some "paid_only" -> `Paid_only - | _, Some "rate_limited" -> `Rate_limited - | _, Some "restricted_action" -> `Restricted_action - | _, Some "too_long" -> `Too_long - | _, Some "unknown_type" -> `Unknown_type - | _, Some "user_does_not_own_channel" -> `User_does_not_own_channel - | _, Some "user_is_bot" -> `User_is_bot - | _, Some "user_is_restricted" -> `User_is_restricted - (* lolwat, I'm not making this up *) - | _, Some "user_is_ultra_restricted" -> `User_is_ultra_restricted - | _, Some "user_not_found" -> `User_not_found - | _, Some "user_not_visible" -> `User_not_visible - (* when the API changes and introduces new, yet unhandled error types *) - | _, Some err -> `Unhandled_error err - | _ -> `Unknown_error + | Ok parsed -> ( + match (parsed.ok, parsed.error) with + | true, _ -> `Json_response json + | _, Some "account_inactive" -> `Account_inactive + | _, Some "already_archived" -> `Already_archived + | _, Some "already_in_channel" -> `Already_in_channel + | _, Some "bad_client_secret" -> `Bad_client_secret + | _, Some "bad_redirect_uri" -> `Bad_redirect_uri + | _, Some "cant_archive_general" -> `Cant_archive_general + | _, Some "cant_invite" -> `Cant_invite + | _, Some "cant_invite_self" -> `Cant_invite_self + | _, Some "cant_delete_file" -> `Cant_delete_file + | _, Some "cant_delete_message" -> `Cant_delete_message + | _, Some "cant_kick_from_general" -> `Cant_kick_from_general + | _, Some "cant_kick_from_last_channel" -> `Cant_kick_from_last_channel + | _, Some "cant_kick_self" -> `Cant_kick_self + | _, Some "cant_leave_general" -> `Cant_leave_general + | _, Some "cant_leave_last_channel" -> `Cant_leave_last_channel + | _, Some "cant_update_message" -> `Cant_update_message + | _, Some "channel_not_found" -> `Channel_not_found + | _, Some "edit_window_closed" -> `Edit_window_closed + | _, Some "file_deleted" -> `File_deleted + | _, Some "file_not_found" -> `File_not_found + | _, Some "invalid_auth" -> `Invalid_auth + | _, Some "invalid_client_id" -> `Invalid_client_id + | _, Some "invalid_code" -> `Invalid_code + | _, Some "invalid_name" -> `Invalid_name + | _, Some "invalid_presence" -> `Invalid_presence + | _, Some "invalid_ts_latest" -> `Invalid_ts_latest + | _, Some "invalid_ts_oldest" -> `Invalid_ts_oldest + | _, Some "is_archived" -> `Is_archived + | _, Some "last_member" -> `Last_member + | _, Some "last_ra_channel" -> `Last_restricted_channel + | _, Some "message_not_found" -> `Message_not_found + (* not supposed to happen *) + | _, Some "msg_too_long" -> `Msg_too_long + | _, Some "too_many_attachments" -> `Too_many_attachments + | _, Some "name_taken" -> `Name_taken + (* can't really happen *) + | _, Some "no_channel" -> `No_channel + (* can't really happen either *) + | _, Some "no_text" -> `No_text + | _, Some "not_archived" -> `Not_archived + | _, Some "not_authed" -> `Not_authed + | _, Some "not_authorized" -> `Not_authorized + | _, Some "not_in_channel" -> `Not_in_channel + | _, Some "paid_only" -> `Paid_only + | _, Some "rate_limited" -> `Rate_limited + | _, Some "restricted_action" -> `Restricted_action + | _, Some "too_long" -> `Too_long + | _, Some "unknown_type" -> `Unknown_type + | _, Some "user_does_not_own_channel" -> `User_does_not_own_channel + | _, Some "user_is_bot" -> `User_is_bot + | _, Some "user_is_restricted" -> `User_is_restricted + (* lolwat, I'm not making this up *) + | _, Some "user_is_ultra_restricted" -> `User_is_ultra_restricted + | _, Some "user_not_found" -> `User_not_found + | _, Some "user_not_visible" -> `User_not_visible + (* when the API changes and introduces new, yet unhandled error types *) + | _, Some err -> `Unhandled_error err + | _ -> `Unknown_error) (* filter out "ok" and "error" keys *) let filter_useless = function - | `Json_response `Assoc items -> `Json_response ( - `Assoc (List.filter (fun (k, _) -> k <> "ok" && k <> "error") items)) + | `Json_response (`Assoc items) -> + `Json_response + (`Assoc (List.filter (fun (k, _) -> k <> "ok" && k <> "error") items)) | otherwise -> otherwise let process request = - request - >|= snd - >>= Cohttp_body.to_string - >|= Yojson.Safe.from_string - >|= validate - >|= filter_useless + request >|= snd >>= Cohttp_body.to_string >|= Yojson.Safe.from_string + >|= validate >|= filter_useless let auth_header token = Cohttp.Header.init_with "Authorization" ("Bearer " ^ token) let endpoint base_url request = let url = Uri.of_string (base_url ^ request.method') in - List.fold_left (Uri.add_query_param') url request.arguments + List.fold_left Uri.add_query_param' url request.arguments -let unauthed_query ?(base_url=default_base_url) request = - endpoint base_url request - |> Cohttp_unix.Client.get - |> process +let unauthed_query ?(base_url = default_base_url) request = + endpoint base_url request |> Cohttp_unix.Client.get |> process let query session request = endpoint session.base_url request @@ -742,64 +609,53 @@ let query_post session body request = |> process let identity x = x - -let maybe fn = function - | Some v -> Some (fn v) - | None -> None +let maybe fn = function Some v -> Some (fn v) | None -> None (* nonpublic types for conversion in list types *) -type conversations_list_obj = { - channels: conversation_obj list -} [@@deriving of_yojson { strict = false }] - -type users_list_obj = { - members: user_obj list -} [@@deriving of_yojson { strict = false }] +type conversations_list_obj = { channels : conversation_obj list } +[@@deriving of_yojson { strict = false }] -type groups_list_obj = { - groups: group_obj list; -} [@@deriving of_yojson] +type users_list_obj = { members : user_obj list } +[@@deriving of_yojson { strict = false }] -type im_list_obj = { - ims: im_obj list; -} [@@deriving of_yojson] +type groups_list_obj = { groups : group_obj list } [@@deriving of_yojson] +type im_list_obj = { ims : im_obj list } [@@deriving of_yojson] let conversations_list ?exclude_archived session = api_request "conversations.list" - |> optionally_add "exclude_archived" @@ maybe string_of_bool @@ exclude_archived - |> query session - >|= function - | `Json_response d -> - (match d |> conversations_list_obj_of_yojson with - | Ok x -> `Success x.channels - | Error e -> `ParseFailure e) - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + |> optionally_add "exclude_archived" + @@ maybe string_of_bool @@ exclude_archived + |> query session + >|= function + | `Json_response d -> ( + match d |> conversations_list_obj_of_yojson with + | Ok x -> `Success x.channels + | Error e -> `ParseFailure e) + | #parsed_auth_error as res -> res + | _ -> `Unknown_error let users_list session = - api_request "users.list" - |> query session - >|= function - | `Json_response d -> - (match d |> users_list_obj_of_yojson with - | Ok x -> `Success x.members - | Error x -> `ParseFailure x) - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + api_request "users.list" |> query session >|= function + | `Json_response d -> ( + match d |> users_list_obj_of_yojson with + | Ok x -> `Success x.members + | Error x -> `ParseFailure x) + | #parsed_auth_error as res -> res + | _ -> `Unknown_error let groups_list ?exclude_archived session = api_request "groups.list" - |> optionally_add "exclude_archived" @@ maybe string_of_bool exclude_archived - |> query session - >|= function - | `Json_response d -> - (match d |> groups_list_obj_of_yojson with - | Ok x -> `Success x.groups - | Error x -> `ParseFailure x) - | #parsed_auth_error as res -> res - | _ -> `Unknown_error - -type 'a listfn = session -> [`Success of 'a list | parsed_auth_error] Lwt.t + |> optionally_add "exclude_archived" @@ maybe string_of_bool exclude_archived + |> query session + >|= function + | `Json_response d -> ( + match d |> groups_list_obj_of_yojson with + | Ok x -> `Success x.groups + | Error x -> `ParseFailure x) + | #parsed_auth_error as res -> res + | _ -> `Unknown_error + +type 'a listfn = session -> [ `Success of 'a list | parsed_auth_error ] Lwt.t (* look up the id of query from results provided by the listfn *) let lookupk session (listfn : 'a listfn) filterfn k = @@ -810,34 +666,36 @@ let lookupk session (listfn : 'a listfn) filterfn k = let id_of_channel session = function | ChannelId id -> Lwt.return @@ `Found id - | ChannelName name -> - lookupk session conversations_list (fun (x:conversation_obj) -> x.name = name || x.name_normalized = name) @@ function - | [] -> `Channel_not_found - | [{id = s; _}] -> `Found s - | _::_::_ -> failwith "Too many results from channel id lookup." + | ChannelName name -> ( + lookupk session conversations_list (fun (x : conversation_obj) -> + x.name = name || x.name_normalized = name) + @@ function + | [] -> `Channel_not_found + | [ { id = s; _ } ] -> `Found s + | _ :: _ :: _ -> failwith "Too many results from channel id lookup.") (* like id_of_channel but does not resolve names to ids *) -let string_of_channel = function - | ChannelId id -> id - | ChannelName name -> name +let string_of_channel = function ChannelId id -> id | ChannelName name -> name let id_of_user session = function | UserId id -> Lwt.return @@ `Found id - | UserName name -> - lookupk session users_list (fun (x:user_obj) -> x.name = name) @@ function - | [] -> `User_not_found - | [{id = UserId s; _}] -> `Found s - | [_] -> failwith "Bad result from user id lookup." - | _::_::_ -> failwith "Too many results from user id lookup." + | UserName name -> ( + lookupk session users_list (fun (x : user_obj) -> x.name = name) + @@ function + | [] -> `User_not_found + | [ { id = UserId s; _ } ] -> `Found s + | [ _ ] -> failwith "Bad result from user id lookup." + | _ :: _ :: _ -> failwith "Too many results from user id lookup.") let id_of_group session = function | GroupId id -> Lwt.return @@ `Found id - | GroupName name -> - lookupk session groups_list (fun (x:group_obj) -> x.name = name) @@ function - | [] -> `Channel_not_found - | [{id = GroupId s; _}] -> `Found s - | [_] -> failwith "Bad result from group id lookup." - | _::_::_ -> failwith "Too many results from group id lookup." + | GroupName name -> ( + lookupk session groups_list (fun (x : group_obj) -> x.name = name) + @@ function + | [] -> `Channel_not_found + | [ { id = GroupId s; _ } ] -> `Found s + | [ _ ] -> failwith "Bad result from group id lookup." + | _ :: _ :: _ -> failwith "Too many results from group id lookup.") let id_of_chat session = function | Channel c -> id_of_channel session c @@ -849,27 +707,13 @@ let name_of_group = function | GroupId _ -> failwith "Need to specify a name" | GroupName name -> name -let string_of_bool = function - | true -> "1" - | false -> "0" - -let string_of_criterion = function - | Score -> "score" - | Timestamp -> "timestamp" - -let string_of_direction = function - | Ascending -> "asc" - | Descending -> "desc" - -let string_of_presence = function - | Auto -> "auto" - | Away -> "away" +let string_of_bool = function true -> "1" | false -> "0" +let string_of_criterion = function Score -> "score" | Timestamp -> "timestamp" +let string_of_direction = function Ascending -> "asc" | Descending -> "desc" +let string_of_presence = function Auto -> "auto" | Away -> "away" (* Slacko API helper methods *) -let start_session ?(base_url=default_base_url) token = { - base_url; - token; -} +let start_session ?(base_url = default_base_url) token = { base_url; token } let token_of_string token = start_session token let message_of_string = identity @@ -877,42 +721,36 @@ let message_of_string = identity let utf8_codepoints text = (* convert string to int list *) let explode s = - let rec exp i l = - if i < 0 then l else exp (i - 1) (Char.code s.[i] :: l) in - exp (String.length s - 1) [] in + let rec exp i l = if i < 0 then l else exp (i - 1) (Char.code s.[i] :: l) in + exp (String.length s - 1) [] + in (* * http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html * http://porg.es/blog/counting-characters-in-utf-8-strings-is-faster *) let rec codepoints = function | [] -> 0 - | x::xs when x < 0x7F -> 1 + codepoints xs - | x::_::xs when x >= 0xC0 && x <= 0xDF -> 1 + codepoints xs - | x::_::_::xs when x >= 0xE0 && x <= 0xEF -> 1 + codepoints xs - | x::_::_::_::xs when x >= 0xF0 && x <= 0xFF -> 1 + codepoints xs + | x :: xs when x < 0x7F -> 1 + codepoints xs + | x :: _ :: xs when x >= 0xC0 && x <= 0xDF -> 1 + codepoints xs + | x :: _ :: _ :: xs when x >= 0xE0 && x <= 0xEF -> 1 + codepoints xs + | x :: _ :: _ :: _ :: xs when x >= 0xF0 && x <= 0xFF -> 1 + codepoints xs (* you are bad and should feel bad *) - | x::_ -> failwith @@ Printf.sprintf "Invalid UTF-8 byte: 0x%X" x in + | x :: _ -> failwith @@ Printf.sprintf "Invalid UTF-8 byte: 0x%X" x + in codepoints @@ explode text -let topic_of_string text = if utf8_codepoints text <= 250 then Some text else None +let topic_of_string text = + if utf8_codepoints text <= 250 then Some text else None let topic_of_string_exn text = - match topic_of_string text with - | Some t -> t - | None -> failwith "Too long" - -let channel_of_string s = - if s.[0] = 'C' then ChannelId s else ChannelName s + match topic_of_string text with Some t -> t | None -> failwith "Too long" -let user_of_string s = - if s.[0] = 'U' then UserId s else UserName s - -let group_of_string s = - if s.[0] = 'G' then GroupId s else GroupName s +let channel_of_string s = if s.[0] = 'C' then ChannelId s else ChannelName s +let user_of_string s = if s.[0] = 'U' then UserId s else UserName s +let group_of_string s = if s.[0] = 'G' then GroupId s else GroupName s (* TODO Create a im if im does not exist? *) -let im_of_string s = - if s.[0] = 'D' then s else failwith "Not an IM channel" +let im_of_string s = if s.[0] = 'D' then s else failwith "Not an IM channel" let translate_parsing_error = function | Error a -> `ParseFailure a @@ -920,110 +758,100 @@ let translate_parsing_error = function (* Slack API begins here *) -let api_test ?(base_url=default_base_url) ?foo ?error () = - api_request "api.test" - |> optionally_add "foo" foo - |> optionally_add "error" error - |> unauthed_query ~base_url - >|= function - | `Json_response x -> `Success x - | #api_error as res -> res - | _ -> `Unknown_error +let api_test ?(base_url = default_base_url) ?foo ?error () = + api_request "api.test" |> optionally_add "foo" foo + |> optionally_add "error" error + |> unauthed_query ~base_url + >|= function + | `Json_response x -> `Success x + | #api_error as res -> res + | _ -> `Unknown_error let auth_test session = - api_request "auth.test" - |> query session - >|= function - | `Json_response d -> d |> authed_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + api_request "auth.test" |> query session >|= function + | `Json_response d -> d |> authed_obj_of_yojson |> translate_parsing_error + | #parsed_auth_error as res -> res + | _ -> `Unknown_error (* Operator for unwrapping channel_ids *) -let (|->) m f = +let ( |-> ) m f = let* m = m in match m with - | `Channel_not_found - | #parsed_auth_error as e -> Lwt.return e + | (`Channel_not_found | #parsed_auth_error) as e -> Lwt.return e | `User_not_found -> Lwt.return `Unknown_error | `Found v -> f v (* Operator for unwrapping user_ids *) -let (|+>) m f = +let ( |+> ) m f = let* m = m in match m with | `Channel_not_found -> Lwt.return `Unknown_error - | `User_not_found - | #parsed_auth_error as e -> Lwt.return e + | (`User_not_found | #parsed_auth_error) as e -> Lwt.return e | `Found v -> f v let channels_archive session channel = id_of_channel session channel |-> fun channel_id -> - api_request "channels.archive" - |> definitely_add "channel" channel_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + api_request "channels.archive" + |> definitely_add "channel" channel_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #bot_error | #already_archived_error - | `Cant_archive_general - | `Last_restricted_channel + | `Cant_archive_general | `Last_restricted_channel | #restriction_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let channels_create session name = - api_request "channels.create" - |> definitely_add "name" name - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> - d |> channel_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error - | #name_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error - -let channels_history session - ?latest ?oldest ?count ?inclusive channel = + api_request "channels.create" |> definitely_add "name" name |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> + d |> channel_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error | #name_error | `User_is_restricted) as res + -> + res + | _ -> `Unknown_error + +let channels_history session ?latest ?oldest ?count ?inclusive channel = id_of_channel session channel |-> fun channel_id -> - api_request "channels.history" - |> definitely_add "channel" channel_id - |> optionally_add "latest" @@ maybe Timestamp.to_string latest - |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "inclusive" @@ maybe string_of_bool inclusive - |> query session - >|= function - | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error - | #history_result as res -> res - | _ -> `Unknown_error + api_request "channels.history" + |> definitely_add "channel" channel_id + |> optionally_add "latest" @@ maybe Timestamp.to_string latest + |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "inclusive" @@ maybe string_of_bool inclusive + |> query session + >|= function + | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error + | #history_result as res -> res + | _ -> `Unknown_error let channels_info session channel = id_of_channel session channel |-> fun channel_id -> - api_request "channels.info" - |> definitely_add "channel" channel_id - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> - d |> channel_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #channel_error as res -> res - | _ -> `Unknown_error + api_request "channels.info" + |> definitely_add "channel" channel_id + |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> + d |> channel_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #channel_error) as res -> res + | _ -> `Unknown_error let channels_invite session channel user = id_of_channel session channel |-> fun channel_id -> id_of_user session user |+> fun user_id -> - api_request "channels.invite" - |> definitely_add "channel" channel_id - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> - d |> channel_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + api_request "channels.invite" + |> definitely_add "channel" channel_id + |> definitely_add "user" user_id + |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> + d |> channel_obj_of_yojson |> translate_parsing_error + | ( #parsed_auth_error | #channel_error | #user_error | #bot_error @@ -1031,577 +859,551 @@ let channels_invite session channel user = | #not_in_channel_error | #already_in_channel_error | #archive_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let channels_join session name = api_request "channels.join" - |> definitely_add "name" @@ string_of_channel name - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> - d |> channel_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + |> definitely_add "name" @@ string_of_channel name + |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> + d |> channel_obj_of_yojson |> translate_parsing_error + | ( #parsed_auth_error | #channel_error | #name_error | #archive_error | #bot_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let channels_kick session channel user = id_of_channel session channel |-> fun channel_id -> id_of_user session user |+> fun user_id -> - api_request "channels.kick" - |> definitely_add "channel" channel_id - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + api_request "channels.kick" + |> definitely_add "channel" channel_id + |> definitely_add "user" user_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #user_error | #bot_error | #channel_kick_error | #not_in_channel_error | #restriction_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let channels_leave session channel = id_of_channel session channel |-> fun channel_id -> api_request "channels.leave" - |> definitely_add "channel" channel_id - |> query session - >|= function - | `Json_response d -> + |> definitely_add "channel" channel_id + |> query session + >|= function + | `Json_response d -> d |> channel_leave_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + | ( #parsed_auth_error | #channel_error | #bot_error | #archive_error | #leave_general_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let channels_mark session channel ts = id_of_channel session channel |-> fun channel_id -> api_request "channels.mark" - |> definitely_add "channel" channel_id - |> definitely_add "ts" @@ Timestamp.to_string ts - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" channel_id + |> definitely_add "ts" @@ Timestamp.to_string ts + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #archive_error - | #not_in_channel_error as res -> res - | _ -> `Unknown_error + | #not_in_channel_error ) as res -> + res + | _ -> `Unknown_error let channels_rename session channel name = id_of_channel session channel |-> fun channel_id -> api_request "channels.rename" - |> definitely_add "channel" channel_id - |> definitely_add "name" name - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> - d |> channel_rename_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + |> definitely_add "channel" channel_id + |> definitely_add "name" name |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> + d |> channel_rename_obj_of_yojson |> translate_parsing_error + | ( #parsed_auth_error | #channel_error | #bot_error | #not_in_channel_error | #name_error | #invalid_name_error - | `Not_authorized - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `Not_authorized | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let channels_set_purpose session channel purpose = id_of_channel session channel |-> fun channel_id -> api_request "channels.setPurpose" - |> definitely_add "channel" channel_id - |> definitely_add "purpose" purpose - |> query session - >|= function - | `Json_response (`Assoc [("purpose", `String d)]) -> - `Success d - | #topic_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" channel_id + |> definitely_add "purpose" purpose + |> query session + >|= function + | `Json_response (`Assoc [ ("purpose", `String d) ]) -> `Success d + | #topic_result as res -> res + | _ -> `Unknown_error let channels_set_topic session channel topic = id_of_channel session channel |-> fun channel_id -> api_request "channels.setTopic" - |> definitely_add "channel" channel_id - |> definitely_add "topic" topic - |> query session - >|= function - | `Json_response (`Assoc [("topic", `String d)]) -> - `Success d - | #topic_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" channel_id + |> definitely_add "topic" topic + |> query session + >|= function + | `Json_response (`Assoc [ ("topic", `String d) ]) -> `Success d + | #topic_result as res -> res + | _ -> `Unknown_error let channels_unarchive session channel = id_of_channel session channel |-> fun channel_id -> api_request "channels.unarchive" - |> definitely_add "channel" channel_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" channel_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #bot_error - | `Not_archived - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `Not_archived | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let chat_delete session ts chat = id_of_chat session chat |-> fun chat_id -> api_request "chat.delete" - |> definitely_add "channel" chat_id - |> definitely_add "ts" @@ Timestamp.to_string ts - |> query session - >|= function - | `Json_response d -> d |> chat_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #channel_error - | #message_error as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" chat_id + |> definitely_add "ts" @@ Timestamp.to_string ts + |> query session + >|= function + | `Json_response d -> d |> chat_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #channel_error | #message_error) as res -> res + | _ -> `Unknown_error let jsonify_attachments attachments = `List (List.map (fun a -> attachment_obj_to_yojson a) attachments) |> Yojson.Safe.to_string -let chat_post_message session chat - ?as_user ?link_names ?mrkdwn - ?reply_broadcast ?thread_ts ?unfurl_links ?unfurl_media - ?username ?parse ?icon_url ?icon_emoji ?(attachments=[]) text = +let chat_post_message session chat ?as_user ?link_names ?mrkdwn ?reply_broadcast + ?thread_ts ?unfurl_links ?unfurl_media ?username ?parse ?icon_url + ?icon_emoji ?(attachments = []) text = id_of_chat session chat |-> fun chat_id -> api_request "chat.postMessage" - |> definitely_add "channel" chat_id - |> definitely_add "text" text - |> optionally_add "username" username - |> optionally_add "parse" parse - |> optionally_add "icon_url" icon_url - |> optionally_add "icon_emoji" icon_emoji - |> definitely_add "attachments" @@ jsonify_attachments attachments - |> optionally_add "as_user" @@ maybe string_of_bool as_user - |> optionally_add "link_names" @@ maybe string_of_bool link_names - |> optionally_add "mrkdwn" @@ maybe string_of_bool mrkdwn - |> optionally_add "reply_broadcast" @@ maybe string_of_bool reply_broadcast - |> optionally_add "thread_ts" @@ maybe Timestamp.to_string thread_ts - |> optionally_add "unfurl_links" @@ maybe string_of_bool unfurl_links - |> optionally_add "unfurl_media" @@ maybe string_of_bool unfurl_media - |> query session - >|= function - | `Json_response d -> - d |> chat_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + |> definitely_add "channel" chat_id + |> definitely_add "text" text + |> optionally_add "username" username + |> optionally_add "parse" parse + |> optionally_add "icon_url" icon_url + |> optionally_add "icon_emoji" icon_emoji + |> definitely_add "attachments" @@ jsonify_attachments attachments + |> optionally_add "as_user" @@ maybe string_of_bool as_user + |> optionally_add "link_names" @@ maybe string_of_bool link_names + |> optionally_add "mrkdwn" @@ maybe string_of_bool mrkdwn + |> optionally_add "reply_broadcast" @@ maybe string_of_bool reply_broadcast + |> optionally_add "thread_ts" @@ maybe Timestamp.to_string thread_ts + |> optionally_add "unfurl_links" @@ maybe string_of_bool unfurl_links + |> optionally_add "unfurl_media" @@ maybe string_of_bool unfurl_media + |> query session + >|= function + | `Json_response d -> d |> chat_obj_of_yojson |> translate_parsing_error + | ( #parsed_auth_error | #channel_error | #bot_error | #archive_error | #message_length_error | #attachments_error - | #rate_error as res -> res - | _ -> `Unknown_error + | #rate_error ) as res -> + res + | _ -> `Unknown_error let chat_update session ts chat ?as_user ?attachments ?link_names ?parse text = id_of_chat session chat |-> fun chat_id -> api_request "chat.update" - |> definitely_add "channel" chat_id - |> definitely_add "ts" @@ Timestamp.to_string ts - |> definitely_add "text" text - |> optionally_add "as_user" @@ maybe string_of_bool as_user - |> optionally_add "attachments" @@ maybe jsonify_attachments attachments - |> optionally_add "link_names" @@ maybe string_of_bool link_names - |> optionally_add "parse" parse - |> query session - >|= function - | `Json_response d -> - d |> chat_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + |> definitely_add "channel" chat_id + |> definitely_add "ts" @@ Timestamp.to_string ts + |> definitely_add "text" text + |> optionally_add "as_user" @@ maybe string_of_bool as_user + |> optionally_add "attachments" @@ maybe jsonify_attachments attachments + |> optionally_add "link_names" @@ maybe string_of_bool link_names + |> optionally_add "parse" parse + |> query session + >|= function + | `Json_response d -> d |> chat_obj_of_yojson |> translate_parsing_error + | ( #parsed_auth_error | #channel_error | #message_update_error | #message_length_error - | #attachments_error as res -> res - | _ -> `Unknown_error + | #attachments_error ) as res -> + res + | _ -> `Unknown_error let emoji_list session = - api_request "emoji.list" - |> query session - >|= function - | `Json_response d -> - (match d |> emoji_list_obj_of_yojson with + api_request "emoji.list" |> query session >|= function + | `Json_response d -> ( + match d |> emoji_list_obj_of_yojson with | Ok x -> `Success x.emoji | Error x -> `ParseFailure x) - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + | #parsed_auth_error as res -> res + | _ -> `Unknown_error let files_delete session file = - api_request "files.delete" - |> definitely_add "file" file - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error - | #bot_error - | `Cant_delete_file - | #file_error as res -> res - | _ -> `Unknown_error + api_request "files.delete" |> definitely_add "file" file |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | (#parsed_auth_error | #bot_error | `Cant_delete_file | #file_error) as res + -> + res + | _ -> `Unknown_error let files_info session ?count ?page file = - api_request "files.info" - |> definitely_add "file" file - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "page" @@ maybe string_of_int page - |> query session - >|= function - | `Json_response d -> - d |> files_info_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error - | #file_error as res -> res - | _ -> `Unknown_error + api_request "files.info" |> definitely_add "file" file + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "page" @@ maybe string_of_int page + |> query session + >|= function + | `Json_response d -> d |> files_info_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error | #file_error) as res -> res + | _ -> `Unknown_error let maybe_with_user session user f = match user with - | Some u -> id_of_user session u >>= (function - | `Found id -> f @@ Some id - | _ -> Lwt.return `User_not_found) - | None -> f None + | Some u -> ( + id_of_user session u >>= function + | `Found id -> f @@ Some id + | _ -> Lwt.return `User_not_found) + | None -> f None let files_list ?user ?ts_from ?ts_to ?types ?count ?page session = maybe_with_user session user @@ fun user_id -> api_request "files.list" - |> optionally_add "user" user_id - |> optionally_add "ts_from" @@ maybe Timestamp.to_string ts_from - |> optionally_add "ts_to" @@ maybe Timestamp.to_string ts_to - |> optionally_add "types" types - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "page" @@ maybe string_of_int page - |> query session - >|= function - | `Json_response d -> - d |> files_list_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #user_error - | #bot_error - | #unknown_type_error as res -> res - | _ -> `Unknown_error - -let files_upload session - ?filetype ?filename ?title ?initial_comment ?channels content = + |> optionally_add "user" user_id + |> optionally_add "ts_from" @@ maybe Timestamp.to_string ts_from + |> optionally_add "ts_to" @@ maybe Timestamp.to_string ts_to + |> optionally_add "types" types + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "page" @@ maybe string_of_int page + |> query session + >|= function + | `Json_response d -> d |> files_list_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #user_error | #bot_error | #unknown_type_error) as res + -> + res + | _ -> `Unknown_error + +let files_upload session ?filetype ?filename ?title ?initial_comment ?channels + content = api_request "files.upload" - |> optionally_add "filetype" filetype - |> optionally_add "filename" filename - |> optionally_add "title" title - |> optionally_add "initial_comment" initial_comment - |> optionally_add "channels" channels - |> query_post session content - >|= function - | `Json_response `Assoc [("file", d)] -> - d |> file_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error as res -> res - | _ -> `Unknown_error + |> optionally_add "filetype" filetype + |> optionally_add "filename" filename + |> optionally_add "title" title + |> optionally_add "initial_comment" initial_comment + |> optionally_add "channels" channels + |> query_post session content + >|= function + | `Json_response (`Assoc [ ("file", d) ]) -> + d |> file_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error) as res -> res + | _ -> `Unknown_error let groups_archive session group = id_of_group session group |-> fun group_id -> api_request "groups.archive" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #bot_error | #already_archived_error - | `Group_contains_others - | `Last_restricted_channel + | `Group_contains_others | `Last_restricted_channel | #restriction_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let groups_close session group = id_of_group session group |-> fun group_id -> api_request "groups.close" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response d -> - d |> chat_close_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #channel_error as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response d -> d |> chat_close_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #channel_error) as res -> res + | _ -> `Unknown_error let groups_create session name = api_request "groups.create" - |> definitely_add "name" @@ name_of_group name - |> query session - >|= function - | `Json_response (`Assoc [("group", d)]) -> + |> definitely_add "name" @@ name_of_group name + |> query session + >|= function + | `Json_response (`Assoc [ ("group", d) ]) -> d |> group_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + | ( #parsed_auth_error | #bot_error | #name_error | #restriction_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let groups_create_child session group = id_of_group session group |-> fun group_id -> api_request "groups.createChild" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response (`Assoc [("group", d)]) -> + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response (`Assoc [ ("group", d) ]) -> d |> group_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + | ( #parsed_auth_error | #channel_error | #bot_error | #already_archived_error | #restriction_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let groups_history session ?latest ?oldest ?count ?inclusive group = id_of_group session group |-> fun group_id -> api_request "groups.history" - |> definitely_add "channel" group_id - |> optionally_add "latest" @@ maybe Timestamp.to_string latest - |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "inclusive" @@ maybe string_of_bool inclusive - |> query session - >|= function - | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error - | #history_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" group_id + |> optionally_add "latest" @@ maybe Timestamp.to_string latest + |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "inclusive" @@ maybe string_of_bool inclusive + |> query session + >|= function + | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error + | #history_result as res -> res + | _ -> `Unknown_error let groups_invite session group user = id_of_group session group |-> fun group_id -> id_of_user session user |+> fun user_id -> api_request "groups.invite" - |> definitely_add "channel" group_id - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response d -> + |> definitely_add "channel" group_id + |> definitely_add "user" user_id + |> query session + >|= function + | `Json_response d -> d |> groups_invite_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + | ( #parsed_auth_error | #channel_error | #user_error | #bot_error | #invite_error | #archive_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let groups_kick session group user = id_of_group session group |-> fun group_id -> id_of_user session user |+> fun user_id -> api_request "groups.kick" - |> definitely_add "channel" group_id - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" group_id + |> definitely_add "user" user_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #user_error | #bot_error | #kick_error | #not_in_group_error | #restriction_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let groups_leave session group = id_of_group session group |-> fun group_id -> api_request "groups.leave" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #bot_error | #archive_error | #leave_last_channel_error | #last_member_error - | `User_is_ultra_restricted as res -> res - | _ -> `Unknown_error + | `User_is_ultra_restricted ) as res -> + res + | _ -> `Unknown_error let groups_mark session group ts = id_of_group session group |-> fun group_id -> api_request "groups.mark" - |> definitely_add "channel" group_id - |> definitely_add "ts" @@ Timestamp.to_string ts - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" group_id + |> definitely_add "ts" @@ Timestamp.to_string ts + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #archive_error - | #not_in_channel_error as res -> res - | _ -> `Unknown_error + | #not_in_channel_error ) as res -> + res + | _ -> `Unknown_error let groups_open session group = id_of_group session group |-> fun group_id -> api_request "groups.open" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response d -> + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response d -> d |> groups_open_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #channel_error as res -> res - | _ -> `Unknown_error + | (#parsed_auth_error | #channel_error) as res -> res + | _ -> `Unknown_error let groups_rename session group name = id_of_group session group |-> fun group_id -> api_request "groups.rename" - |> definitely_add "channel" group_id - |> definitely_add "name" name - |> query session - >|= function - | `Json_response (`Assoc [("channel", d)]) -> + |> definitely_add "channel" group_id + |> definitely_add "name" name |> query session + >|= function + | `Json_response (`Assoc [ ("channel", d) ]) -> d |> groups_rename_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error + | ( #parsed_auth_error | #channel_error | #bot_error | #name_error | #invalid_name_error - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let groups_set_purpose session group purpose = id_of_group session group |-> fun group_id -> api_request "groups.setPurpose" - |> definitely_add "channel" group_id - |> definitely_add "purpose" purpose - |> query session - >|= function - | `Json_response (`Assoc [("purpose", `String d)]) -> - `Success d - | #topic_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" group_id + |> definitely_add "purpose" purpose + |> query session + >|= function + | `Json_response (`Assoc [ ("purpose", `String d) ]) -> `Success d + | #topic_result as res -> res + | _ -> `Unknown_error let groups_set_topic session group topic = id_of_group session group |-> fun group_id -> api_request "groups.setTopic" - |> definitely_add "channel" group_id - |> definitely_add "topic" topic - |> query session - >|= function - | `Json_response (`Assoc [("topic", `String d)]) -> - `Success d - | #topic_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" group_id + |> definitely_add "topic" topic + |> query session + >|= function + | `Json_response (`Assoc [ ("topic", `String d) ]) -> `Success d + | #topic_result as res -> res + | _ -> `Unknown_error let groups_unarchive session group = id_of_group session group |-> fun group_id -> api_request "groups.unarchive" - |> definitely_add "channel" group_id - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error + |> definitely_add "channel" group_id + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | ( #parsed_auth_error | #channel_error | #bot_error - | `Not_archived - | `User_is_restricted as res -> res - | _ -> `Unknown_error + | `Not_archived | `User_is_restricted ) as res -> + res + | _ -> `Unknown_error let im_close session channel = - api_request "im.close" - |> definitely_add "channel" channel - |> query session - >|= function - | `Json_response d -> - d |> chat_close_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #channel_error - | `User_does_not_own_channel as res -> res - | _ -> `Unknown_error + api_request "im.close" |> definitely_add "channel" channel |> query session + >|= function + | `Json_response d -> d |> chat_close_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #channel_error | `User_does_not_own_channel) as res -> + res + | _ -> `Unknown_error let im_history session ?latest ?oldest ?count ?inclusive channel = api_request "im.history" - |> definitely_add "channel" channel - |> optionally_add "latest" @@ maybe Timestamp.to_string latest - |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "inclusive" @@ maybe string_of_bool inclusive - |> query session - >|= function - | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error - | #history_result as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" channel + |> optionally_add "latest" @@ maybe Timestamp.to_string latest + |> optionally_add "oldest" @@ maybe Timestamp.to_string oldest + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "inclusive" @@ maybe string_of_bool inclusive + |> query session + >|= function + | `Json_response d -> d |> history_obj_of_yojson |> translate_parsing_error + | #history_result as res -> res + | _ -> `Unknown_error let im_list session = - api_request "im.list" - |> query session - >|= function - | `Json_response d -> - (match d |> im_list_obj_of_yojson with - | Ok x -> `Success x.ims - | Error x -> `ParseFailure x) - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + api_request "im.list" |> query session >|= function + | `Json_response d -> ( + match d |> im_list_obj_of_yojson with + | Ok x -> `Success x.ims + | Error x -> `ParseFailure x) + | #parsed_auth_error as res -> res + | _ -> `Unknown_error let im_mark session channel ts = api_request "im.mark" - |> definitely_add "channel" channel - |> definitely_add "ts" @@ Timestamp.to_string ts - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error - | #channel_error - | #not_in_channel_error as res -> res - | _ -> `Unknown_error + |> definitely_add "channel" channel + |> definitely_add "ts" @@ Timestamp.to_string ts + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | (#parsed_auth_error | #channel_error | #not_in_channel_error) as res -> res + | _ -> `Unknown_error let im_open session user = id_of_user session user |+> fun user_id -> - api_request "im.open" - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response d -> - d |> im_open_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #user_error - | #user_visibility_error as res -> res - | _ -> `Unknown_error - -let oauth_access ?(base_url=default_base_url) client_id client_secret ?redirect_url code = + api_request "im.open" |> definitely_add "user" user_id |> query session + >|= function + | `Json_response d -> d |> im_open_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #user_error | #user_visibility_error) as res -> res + | _ -> `Unknown_error + +let oauth_access ?(base_url = default_base_url) client_id client_secret + ?redirect_url code = api_request "oauth.access" - |> definitely_add "client_id" client_id - |> definitely_add "client_secret" client_secret - |> definitely_add "code" code - |> optionally_add "redirect_url" redirect_url - |> unauthed_query ~base_url - >|= function - | `Json_response d -> - d |> oauth_obj_of_yojson |> translate_parsing_error - | #oauth_error as res -> res - | _ -> `Unknown_error + |> definitely_add "client_id" client_id + |> definitely_add "client_secret" client_secret + |> definitely_add "code" code + |> optionally_add "redirect_url" redirect_url + |> unauthed_query ~base_url + >|= function + | `Json_response d -> d |> oauth_obj_of_yojson |> translate_parsing_error + | #oauth_error as res -> res + | _ -> `Unknown_error let search method' session ?sort ?sort_dir ?highlight ?count ?page query_ = api_request method' - |> definitely_add "query" @@ query_ - |> optionally_add "sort" @@ maybe string_of_criterion sort - |> optionally_add "sort_dir" @@ maybe string_of_direction sort_dir - |> optionally_add "highlight" @@ maybe string_of_bool highlight - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "page" @@ maybe string_of_int page - |> query session - >|= function - | `Json_response d -> - d |> search_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error as res -> res - | _ -> `Unknown_error + |> definitely_add "query" @@ query_ + |> optionally_add "sort" @@ maybe string_of_criterion sort + |> optionally_add "sort_dir" @@ maybe string_of_direction sort_dir + |> optionally_add "highlight" @@ maybe string_of_bool highlight + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "page" @@ maybe string_of_int page + |> query session + >|= function + | `Json_response d -> d |> search_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error) as res -> res + | _ -> `Unknown_error let search_all = search "search.all" let search_files = search "search.files" @@ -1610,84 +1412,67 @@ let search_messages = search "search.messages" let stars_list ?user ?count ?page session = maybe_with_user session user @@ fun user_id -> api_request "stars.list" - |> optionally_add "user" user_id - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "page" @@ maybe string_of_int page - |> query session - >|= function - | `Json_response d -> - d |> stars_list_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error - | #user_error as res -> res - | _ -> `Unknown_error + |> optionally_add "user" user_id + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "page" @@ maybe string_of_int page + |> query session + >|= function + | `Json_response d -> d |> stars_list_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error | #user_error) as res -> res + | _ -> `Unknown_error let team_access_logs ?count ?page session = api_request "team.accessLogs" - |> optionally_add "count" @@ maybe string_of_int count - |> optionally_add "page" @@ maybe string_of_int page - |> query session - >|= function - | `Json_response d -> + |> optionally_add "count" @@ maybe string_of_int count + |> optionally_add "page" @@ maybe string_of_int page + |> query session + >|= function + | `Json_response d -> d |> team_access_log_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | `Paid_only - | #bot_error as res -> res - | _ -> `Unknown_error + | (#parsed_auth_error | `Paid_only | #bot_error) as res -> res + | _ -> `Unknown_error let team_info session = - api_request "team.info" - |> query session - >|= function - | `Json_response d -> - d |> team_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #bot_error as res -> res - | _ -> `Unknown_error + api_request "team.info" |> query session >|= function + | `Json_response d -> d |> team_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #bot_error) as res -> res + | _ -> `Unknown_error let users_get_presence session user = id_of_user session user |+> fun user_id -> api_request "users.getPresence" - |> definitely_add "user" user_id - |> query session - >|= function - (* TODO parse more out of this *) - | `Json_response (`Assoc [("presence", `String d)]) -> (match d with + |> definitely_add "user" user_id + |> query session + >|= function + (* TODO parse more out of this *) + | `Json_response (`Assoc [ ("presence", `String d) ]) -> ( + match d with | "active" -> `Success Auto | "away" -> `Success Away | _ -> `ParseFailure "Invalid presence") - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + | #parsed_auth_error as res -> res + | _ -> `Unknown_error let users_info session user = - id_of_user session user - |+> fun user_id -> - api_request "users.info" - |> definitely_add "user" user_id - |> query session - >|= function - | `Json_response (`Assoc [("user", d)]) -> - d |> user_obj_of_yojson |> translate_parsing_error - | #parsed_auth_error - | #user_error - | #user_visibility_error as res -> res - | _ -> `Unknown_error + id_of_user session user |+> fun user_id -> + api_request "users.info" |> definitely_add "user" user_id |> query session + >|= function + | `Json_response (`Assoc [ ("user", d) ]) -> + d |> user_obj_of_yojson |> translate_parsing_error + | (#parsed_auth_error | #user_error | #user_visibility_error) as res -> res + | _ -> `Unknown_error let users_set_active session = - api_request "users.setActive" - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #bot_error - | #parsed_auth_error as res -> res - | _ -> `Unknown_error + api_request "users.setActive" |> query session >|= function + | `Json_response (`Assoc []) -> `Success + | (#bot_error | #parsed_auth_error) as res -> res + | _ -> `Unknown_error let users_set_presence session presence = api_request "users.setPresence" - |> definitely_add "presence" @@ string_of_presence presence - |> query session - >|= function - | `Json_response (`Assoc []) -> `Success - | #parsed_auth_error - | #presence_error as res -> res - | _ -> `Unknown_error + |> definitely_add "presence" @@ string_of_presence presence + |> query session + >|= function + | `Json_response (`Assoc []) -> `Success + | (#parsed_auth_error | #presence_error) as res -> res + | _ -> `Unknown_error diff --git a/src/lib/slacko.mli b/src/lib/slacko.mli index ce31c3c..fe36017 100644 --- a/src/lib/slacko.mli +++ b/src/lib/slacko.mli @@ -38,261 +38,185 @@ the exact error types. *) -type api_error = [ - | `Unhandled_error of string - | `Unknown_error -] - -type parsed_api_error = [ - | `ParseFailure of string - | api_error -] +type api_error = [ `Unhandled_error of string | `Unknown_error ] +type parsed_api_error = [ `ParseFailure of string | api_error ] +type auth_error = [ `Not_authed | `Invalid_auth | `Account_inactive ] (** API calls that require authentication (a {!session}) might fail with one of these errors, so functions that take {!session} arguments will return {e at least} these error variants. *) -type auth_error = [ - | `Not_authed - | `Invalid_auth - | `Account_inactive -] +type timestamp_error = [ `Invalid_ts_latest | `Invalid_ts_oldest ] (** API calls that take {!timestamp} arguments can signal errors when the timestamp is invalid. The binding does its best to make sure that invalid timestamps are avoided. *) -type timestamp_error = [ - | `Invalid_ts_latest - | `Invalid_ts_oldest -] +type channel_error = [ `Channel_not_found ] (** API calls that require {!channel} inputs can signal this error if the channel does not exist. *) -type channel_error = [ - | `Channel_not_found -] +type user_error = [ `User_not_found ] (** API calls that require {!user} inputs signal this error if the user was not found. *) -type user_error = [ - | `User_not_found -] +type invite_error = [ `Cant_invite_self | `Cant_invite ] (** Inviting might fail because invitation is impossible for some reason or because attempting to invite oneself. *) -type invite_error = [ - | `Cant_invite_self - | `Cant_invite -] +type not_in_channel_error = [ `Not_in_channel ] (** Some API calls require the {!user} to be in {!channel} for the action to succeed, not meeting this requirement can raise this error. *) -type not_in_channel_error = [ - | `Not_in_channel -] +type already_in_channel_error = [ `Already_in_channel ] (** Some API calls require the {!user} not to be in {!channel} for the action to suceed. The opposite of {!not_in_channel_error}. *) -type already_in_channel_error = [ - | `Already_in_channel -] +type archive_error = [ `Is_archived ] (** Channels might be archived, so modification attempts will fail with this error. *) -type archive_error = [ - | `Is_archived -] +type name_error = [ `Name_taken ] (** When creating channels, names have to be unique, an attempt to create a duplicate one will result in this error. *) -type name_error = [ - | `Name_taken -] +type kick_error = [ `Cant_kick_self ] (** Kick (in general) might fail, because kicking oneself is not supported. *) -type kick_error = [ - | `Cant_kick_self -] +type channel_kick_error = + [ kick_error | `Cant_kick_from_general | `Cant_kick_from_last_channel ] (** Kicking users from channels might fail, because channels have additional restrictions on kicking: users can't be kicked from the #general channel and they cannot be kicked from the last channel they are in. *) -type channel_kick_error = [ - | kick_error - | `Cant_kick_from_general - | `Cant_kick_from_last_channel -] +type restriction_error = [ `Restricted_action ] (** If an action was attempted that the user does not have permission to, this error is returned. *) -type restriction_error = [ - | `Restricted_action -] +type leave_general_error = [ `Cant_leave_general ] (** Leaving the #general channel is not supported by Slack, an attempt do do so will trigger this error. *) -type leave_general_error = [ - | `Cant_leave_general -] +type message_error = [ `Cant_delete_message | `Message_not_found ] (** The {!message} might not exist or be impossible to delete for other reasons. *) -type message_error = [ - | `Cant_delete_message - | `Message_not_found -] +type message_length_error = [ `Msg_too_long ] (** {!message} types, like {!topic} types might be too long to post. The Slack API does not specify the maximum message length, so Slacko can't make sure your messages stay below this limit, so everytime you post, this error can realistically happen. *) -type message_length_error = [ - | `Msg_too_long -] +type attachments_error = [ `Too_many_attachments ] (** When posting a message with attachments, you may receive this error if you post too many. The Slack API documentation states that attempting to post a message with more than 100 attachments will fail with this error, but also that no message should ever have more than 20 attachments. Slacko doesn't check the number of attachments sent. *) -type attachments_error = [ - | `Too_many_attachments -] +type rate_error = [ `Rate_limited ] (** Doing too many API requests in a certain timespan might cause a rate limitation to be applied by Slack. This is the error that results in that case. *) -type rate_error = [ - | `Rate_limited -] +type message_update_error = + [ `Message_not_found | `Cant_update_message | `Edit_window_closed ] (** Updating a {!message} might fail because the message was not found, couldn't be updated for some reason or because the time in which a message can be edited has passed. *) -type message_update_error = [ - | `Message_not_found - | `Cant_update_message - | `Edit_window_closed -] +type file_error = [ `File_not_found | `File_deleted ] (** Handling files can result in multiple problems: the file wasn't found in the first place or it might have been deleted in the maintime. *) -type file_error = [ - | `File_not_found - | `File_deleted -] +type unknown_type_error = [ `Unknown_type ] (** This error shouldn't ever be returned but serves as a catch-all in case the Slack API returns a new, unknown error type that Slacko doesn't yet understand. *) -type unknown_type_error = [ - | `Unknown_type -] +type already_archived_error = [ `Already_archived ] (** When trying to archive something that was already archived before, this error is returned. *) -type already_archived_error = [ - | `Already_archived -] +type not_in_group_error = [ `Not_in_group ] (** Doing an action in a {!group} when not being part of the group can fail. *) -type not_in_group_error = [ - | `Not_in_group -] (* Leaving the last {!channel} is not supported by Slack. *) -type leave_last_channel_error = [ - | `Cant_leave_last_channel -] +type leave_last_channel_error = [ `Cant_leave_last_channel ] +type last_member_error = [ `Last_member ] (** An error when the user is the last member and can't do what he planned to do because that would cause the {!channel} not to have members anymore. *) -type last_member_error = [ - | `Last_member -] -(** These errors might be returned when the exchange of oauth authorization - for a token has failed. *) -type oauth_error = [ - | `Invalid_client_id +type oauth_error = + [ `Invalid_client_id | `Bad_client_secret | `Invalid_code | `Bad_redirect_uri - | `Unknown_error -] + | `Unknown_error ] +(** These errors might be returned when the exchange of oauth authorization + for a token has failed. *) +type presence_error = [ `Invalid_presence ] (** Setting an invalid presence information is not supported. *) -type presence_error = [ - | `Invalid_presence -] +type user_visibility_error = [ `User_not_visible ] (** User is not visible, so action cannot be performed on them. *) -type user_visibility_error = [ - | `User_not_visible -] - -type invalid_name_error = [ - | `Invalid_name -] -type bot_error = [ - | `User_is_bot -] +type invalid_name_error = [ `Invalid_name ] +type bot_error = [ `User_is_bot ] +type parsed_auth_error = [ parsed_api_error | auth_error ] (** API calls which require authentication will always return (at least) these error types. *) -type parsed_auth_error = [ - | parsed_api_error - | auth_error -] -(** Setting topics or purposes will result either in a success or one of these - errors. Convenience type composed of subtypes. *) -type topic_result = [ - | `Success of string +type topic_result = + [ `Success of string | parsed_auth_error | channel_error | archive_error | not_in_channel_error - | `User_is_restricted -] + | `User_is_restricted ] +(** Setting topics or purposes will result either in a success or one of these + errors. Convenience type composed of subtypes. *) +type timestamp = Ptime.t (** Slack uses 6 decimal digit fixed point seconds since Epoch to represent message timestamps, which are also used to identify messages within a channel. Ptime provides an exact representation, allowing precise history queries and message identification. *) -type timestamp = Ptime.t +type session (** Sessions are required in the API for all actions that interact with *) -type session -(** {!token} is an alias for {!session} for backwards compatibility reasons. *) + type token = session +(** {!token} is an alias for {!session} for backwards compatibility reasons. *) +type topic (** The topic type represents a topic or a purpose message. Both are limited deliberately to have at most 250 UTF-8 encoded codepoints. *) -type topic -(** The message represents a message to be posted. *) type message +(** The message represents a message to be posted. *) +type channel (** A channel type, can be either a channel name (starting with a #) or a channel id. *) -type channel -(** A channel-like container for a conversation used by the Conversations API. *) type conversation +(** A channel-like container for a conversation used by the Conversations API. *) -(** A type of an IM conversation *) type im +(** A type of an IM conversation *) -(** An user, represented by either a user name or a user id. *) type user +(** An user, represented by either a user name or a user id. *) -(** A bot user, represented by a bot id *) type bot +(** A bot user, represented by a bot id *) -(** A group, a private subset of users chatting together. *) type group +(** A group, a private subset of users chatting together. *) (** A place one can post messages to. *) type chat = Channel of channel | Im of im | User of user | Group of group @@ -306,438 +230,430 @@ type sort_direction = Ascending | Descending (** Presence can either be active or away. *) type presence = Auto | Away +type topic_obj = { value : string; creator : user; last_set : timestamp } (** A topic or purpose object. *) -type topic_obj = { - value: string; - creator: user; - last_set: timestamp; -} -(** Object representing lots of information about a Slack user. *) type user_obj = { - id: user; - name: string; - deleted: bool; - color: string option; - real_name: string option; - tz: string option; - tz_label: string option; - tz_offset: int; - profile: Yojson.Safe.t; - is_admin: bool; - is_owner: bool; - is_primary_owner: bool; - is_restricted: bool; - is_ultra_restricted: bool; - is_bot: bool; - has_files: bool; + id : user; + name : string; + deleted : bool; + color : string option; + real_name : string option; + tz : string option; + tz_label : string option; + tz_offset : int; + profile : Yojson.Safe.t; + is_admin : bool; + is_owner : bool; + is_primary_owner : bool; + is_restricted : bool; + is_ultra_restricted : bool; + is_bot : bool; + has_files : bool; } +(** Object representing lots of information about a Slack user. *) -(** Object representing information about a Slack group. *) type group_obj = { - id: group; - name: string; - is_group: bool; - created: timestamp; - creator: user; - is_archived: bool; - members: user list; - topic: topic_obj; - purpose: topic_obj; - is_open: bool option; - last_read: timestamp option; - unread_count: int option; - unread_count_display: int option; - latest: Yojson.Safe.t option; + id : group; + name : string; + is_group : bool; + created : timestamp; + creator : user; + is_archived : bool; + members : user list; + topic : topic_obj; + purpose : topic_obj; + is_open : bool option; + last_read : timestamp option; + unread_count : int option; + unread_count_display : int option; + latest : Yojson.Safe.t option; } +(** Object representing information about a Slack group. *) -(** Object representing information about a Slack channel. *) type channel_obj = { - id: channel; - name: string; - is_channel: bool; - created: timestamp; - creator: user; - is_archived: bool; - is_general: bool; - name_normalized: string; - is_member: bool; - members: user list; - topic: topic_obj; - purpose: topic_obj; - last_read: timestamp option; - latest: Yojson.Safe.t option; - unread_count: int option; - unread_count_display: int option; - num_members: int option; + id : channel; + name : string; + is_channel : bool; + created : timestamp; + creator : user; + is_archived : bool; + is_general : bool; + name_normalized : string; + is_member : bool; + members : user list; + topic : topic_obj; + purpose : topic_obj; + last_read : timestamp option; + latest : Yojson.Safe.t option; + unread_count : int option; + unread_count_display : int option; + num_members : int option; } - (** Object representing information about a Slack channel. *) + type conversation_obj = { - id: conversation; - name: string; - is_channel: bool; - created: timestamp; - creator: user; - is_archived: bool; - is_general: bool; - name_normalized: string; - is_member: bool; - topic: topic_obj; - purpose: topic_obj; - last_read: timestamp option; - latest: string option; - unread_count: int option; - unread_count_display: int option; - num_members: int option; + id : conversation; + name : string; + is_channel : bool; + created : timestamp; + creator : user; + is_archived : bool; + is_general : bool; + name_normalized : string; + is_member : bool; + topic : topic_obj; + purpose : topic_obj; + last_read : timestamp option; + latest : string option; + unread_count : int option; + unread_count_display : int option; + num_members : int option; } +(** Object representing information about a Slack channel. *) +type field_obj = { title : string option; value : string; short : bool } (** Object representing a message attachment field. *) -type field_obj = { - title: string option; - value: string; - short: bool; -} -(** Object representing a message attachment. *) type attachment_obj = { - fallback: string option; - color: string option; - pretext: string option; - author_name: string option; - author_link: string option; - author_icon: string option; - title: string option; - title_link: string option; - text: string option; - fields: field_obj list option; - image_url: string option; - thumb_url: string option; - footer: string option; - footer_icon: string option; - ts: timestamp option; - mrkdwn_in: string list option; + fallback : string option; + color : string option; + pretext : string option; + author_name : string option; + author_link : string option; + author_icon : string option; + title : string option; + title_link : string option; + text : string option; + fields : field_obj list option; + image_url : string option; + thumb_url : string option; + footer : string option; + footer_icon : string option; + ts : timestamp option; + mrkdwn_in : string list option; } +(** Object representing a message attachment. *) -(** Object representing a message. Can be of a number of types. *) type message_obj = { - type': string; - ts: timestamp; - user: user option; - bot_id: bot option; - text: string option; - is_starred: bool option; + type' : string; + ts : timestamp; + user : user option; + bot_id : bot option; + text : string option; + is_starred : bool option; } +(** Object representing a message. Can be of a number of types. *) (* The message history of a channel or group conversation. *) type history_obj = { - latest: timestamp option; - messages: message_obj list; - has_more: bool; + latest : timestamp option; + messages : message_obj list; + has_more : bool; } -(** Authentication information from the current user. *) type authed_obj = { - url: string; - team: string; - user: string; - team_id: string; - user_id: user; + url : string; + team : string; + user : string; + team_id : string; + user_id : user; } +(** Authentication information from the current user. *) +type channel_leave_obj = { not_in_channel : bool option } (** Response to a channel leave request. *) -type channel_leave_obj = { - not_in_channel: bool option -} -(** Response to renaming of a channel. *) type channel_rename_obj = { - id: channel; - is_channel: bool; - name: string; - created: timestamp; + id : channel; + is_channel : bool; + name : string; + created : timestamp; } +(** Response to renaming of a channel. *) -type chat_obj = { - ts: timestamp; - chat: chat; - text: string option; -} +type chat_obj = { ts : timestamp; chat : chat; text : string option } +type emoji = string * string (** A single emoji. *) -type emoji = (string * string) -type chat_close_obj = { - no_op: bool option; - already_closed: bool option; -} +type chat_close_obj = { no_op : bool option; already_closed : bool option } +type groups_invite_obj = { already_in_group : bool option; group : group_obj } (** Response to a channel invite. *) -type groups_invite_obj = { - already_in_group: bool option; - group: group_obj; -} +type groups_open_obj = { no_op : bool option; already_open : bool option } (** Response to opening a group. *) -type groups_open_obj = { - no_op: bool option; - already_open: bool option; -} -(** Response to rename of a group *) type groups_rename_obj = { - id: channel; - is_group: bool; - name: string; - created: timestamp + id : channel; + is_group : bool; + name : string; + created : timestamp; } +(** Response to rename of a group *) -(** Information about a direct im with a person. *) type im_obj = { - id: string; - is_im: bool; - user: user; - created: timestamp; - is_user_deleted: bool; - is_open: bool option; - last_read: timestamp option; - unread_count: int option; - unread_count_display: int option; + id : string; + is_im : bool; + user : user; + created : timestamp; + is_user_deleted : bool; + is_open : bool option; + last_read : timestamp option; + unread_count : int option; + unread_count_display : int option; } +(** Information about a direct im with a person. *) -type im_channel_obj = { - id: string; -} +type im_channel_obj = { id : string } -(** Information about an direct im channel. *) type im_open_obj = { - no_op: bool option; - already_open: bool option; - channel: im_channel_obj; + no_op : bool option; + already_open : bool option; + channel : im_channel_obj; } +(** Information about an direct im channel. *) +type oauth_obj = { access_token : string; scope : string } (** When requesting an OAuth token, you get a token and the scope for which this token is valid. *) -type oauth_obj = { - access_token: string; - scope: string; -} -(** Represents a comment on an item. *) type comment_obj = { - id: string; - timestamp: timestamp; - user: user; - comment: string; + id : string; + timestamp : timestamp; + user : user; + comment : string; } +(** Represents a comment on an item. *) +type paging_obj = { count : int; total : int; page : int; pages : int } (** Paging information for requests that might have multi page results. *) -type paging_obj = { - count: int; - total: int; - page: int; - pages: int; -} -(** Information about a file. *) type file_obj = { (* TODO file id type *) - id: string; - created: timestamp; + id : string; + created : timestamp; (* deprecated *) - timestamp: timestamp; - - name: string option; - title: string; - mimetype: string; - pretty_type: string; - user: user; - - mode: string; - editable: bool; - is_external: bool; - external_type: string; - - size: int; - - url_private: string; - url_private_download: string; - - thumb_64: string option; - thunb_80: string option; - thumb_360: string option; - thumb_360_gif: string option; - thumb_360_w: int option; - thumb_360_h: int option; - - permalink: string; - edit_link: string option; - preview: string option; - preview_highlight: string option; - lines: int option; - lines_more: int option; - - is_public: bool; + timestamp : timestamp; + name : string option; + title : string; + mimetype : string; + pretty_type : string; + user : user; + mode : string; + editable : bool; + is_external : bool; + external_type : string; + size : int; + url_private : string; + url_private_download : string; + thumb_64 : string option; + thunb_80 : string option; + thumb_360 : string option; + thumb_360_gif : string option; + thumb_360_w : int option; + thumb_360_h : int option; + permalink : string; + edit_link : string option; + preview : string option; + preview_highlight : string option; + lines : int option; + lines_more : int option; + is_public : bool; (*public_url_shared: ???;*) - channels: channel list; - groups: group list; - ims: im list; - initial_comment: Yojson.Safe.t option; - num_stars: int option; + channels : channel list; + groups : group list; + ims : im list; + initial_comment : Yojson.Safe.t option; + num_stars : int option; } +(** Information about a file. *) -(** Metainformation about a file. *) type files_info_obj = { - file: file_obj; - comments: comment_obj list; - paging: paging_obj; + file : file_obj; + comments : comment_obj list; + paging : paging_obj; } +(** Metainformation about a file. *) +type files_list_obj = { files : file_obj list; paging : paging_obj } (** A list of files. *) -type files_list_obj = { - files: file_obj list; - paging: paging_obj; -} +type stars_list_obj = { items : Yojson.Safe.t list; paging : paging_obj } (** Information about starred items. *) -type stars_list_obj = { - items: Yojson.Safe.t list; - paging: paging_obj; -} type message_search_obj = { - total: int; - paging: paging_obj; - matches: message_obj list; + total : int; + paging : paging_obj; + matches : message_obj list; } type file_search_obj = { - total: int; - paging: paging_obj; - matches: file_obj list; + total : int; + paging : paging_obj; + matches : file_obj list; } type search_obj = { - query: string; - messages: message_search_obj option; - files: file_search_obj option; + query : string; + messages : message_search_obj option; + files : file_search_obj option; } type team_obj = { - id: string; - name: string; - domain: string; - email_domain: string; - icon: Yojson.Safe.t; + id : string; + name : string; + domain : string; + email_domain : string; + icon : Yojson.Safe.t; } type login_obj = { - user_id: user; - username: string; - date_first: timestamp; - date_last: timestamp; - count: int; - ip: string; - user_agent: string; - isp: string; - country: string; - region: string; + user_id : user; + username : string; + date_first : timestamp; + date_last : timestamp; + count : int; + ip : string; + user_agent : string; + isp : string; + country : string; + region : string; } -type team_access_log_obj = { - logins: login_obj list; - paging: paging_obj; -} +type team_access_log_obj = { logins : login_obj list; paging : paging_obj } -(** Return value of a history related request. *) -type history_result = [ - | `Success of history_obj +type history_result = + [ `Success of history_obj | parsed_auth_error | channel_error - | timestamp_error -] + | timestamp_error ] +(** Return value of a history related request. *) (** {2 Type construction helper functions} *) (** To build the types required in the API calls, you can use these helper functions. *) +val start_session : ?base_url:string -> string -> session (** Create a session from a token string and an optional base_url. *) -val start_session: ?base_url:string -> string -> session -(** Deprecated wrapper for backcompat. *) -val token_of_string: string -> session +val token_of_string : string -> session [@@ocaml.deprecated "Please use 'start_session' instead."] +(** Deprecated wrapper for backcompat. *) -val field: ?title:string -> ?short:bool -> string -> field_obj - -val attachment: - ?fallback:string -> ?color:string -> ?pretext:string -> - ?author_name:string -> ?author_link:string -> ?author_icon:string -> - ?title:string -> ?title_link:string -> - ?text:string -> ?fields:field_obj list -> - ?image_url:string -> ?thumb_url:string -> - ?footer:string -> ?footer_icon:string -> - ?ts:timestamp -> ?mrkdwn_in:string list -> +val field : ?title:string -> ?short:bool -> string -> field_obj + +val attachment : + ?fallback:string -> + ?color:string -> + ?pretext:string -> + ?author_name:string -> + ?author_link:string -> + ?author_icon:string -> + ?title:string -> + ?title_link:string -> + ?text:string -> + ?fields:field_obj list -> + ?image_url:string -> + ?thumb_url:string -> + ?footer:string -> + ?footer_icon:string -> + ?ts:timestamp -> + ?mrkdwn_in:string list -> unit -> attachment_obj +val message_of_string : string -> message (** Build a message from a string. *) -val message_of_string: string -> message +val topic_of_string : string -> topic option (** Build a topic out of a string. {!topic} types are also used to set purposes. Also validates the length of the topic, since Slack has a 250 UTF-8 codepoint length limit on purposes and topics. *) -val topic_of_string: string -> topic option +val topic_of_string_exn : string -> topic (** Same as {!topic_of_string} but throws an exception if it fails to convert the text data into a {!topic}. *) -val topic_of_string_exn: string -> topic +val group_of_string : string -> group (** Construct a group out of a given string. This can be either a group id, starting with capital 'G' character which is the preferred way or it can be a group name for convenience. In the latter case, each API call with requires a group will perform an additional request to determine the group id from the name. *) -val group_of_string: string -> group +val user_of_string : string -> user (** Constructs a user out of a given string. The string can either be an user id starting with a capital 'U' which is the preferred way or it can be a simple user name in which case every API call will look up the user name to an id in an additional request. *) -val user_of_string: string -> user val bot_of_string : string -> bot +val channel_of_string : string -> channel (** Constructs a channel out of a given string. Can either be a channel id starting with a capital 'C' which is the preferred way or a channel name starting with a '#'. If a channel name was provided, each consecutive API call using it will first need to resolve the channel name into a channel id by means of an additional request. *) -val channel_of_string: string -> channel +val im_of_string : string -> im (** Create a im type out of a given string. The string is usually starting with a capital 'D' and represents an IM im channel. *) -val im_of_string: string -> im (** {2 Slack API calls} *) +val api_test : + ?base_url:string -> + ?foo:string -> + ?error:string -> + unit -> + [ `Success of Yojson.Safe.t | api_error ] Lwt.t (** Checks API calling code. @param base_url If set, overrides the Slack API base URL. @param foo A dummy value that will be returned by the API. @param error If set, will return a specific kind of error. *) -val api_test: ?base_url:string -> ?foo:string -> ?error:string -> unit -> [ `Success of Yojson.Safe.t | api_error ] Lwt.t +val auth_test : session -> [ `Success of authed_obj | parsed_auth_error ] Lwt.t (** Checks authentication & identity. @param session The session containing the authentication token. *) -val auth_test: session -> [ `Success of authed_obj | parsed_auth_error ] Lwt.t +val channels_archive : + session -> + channel -> + [ `Success + | parsed_auth_error + | channel_error + | already_archived_error + | `Cant_archive_general + | `Last_restricted_channel + | restriction_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Archives a channel. *) -val channels_archive: session -> channel -> [ `Success | parsed_auth_error | channel_error | already_archived_error | `Cant_archive_general | `Last_restricted_channel | restriction_error | `User_is_restricted | bot_error ] Lwt.t +val channels_create : + session -> + string -> + [ `Success of channel_obj + | parsed_auth_error + | name_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Creates a channel. *) -val channels_create: session -> string -> [ `Success of channel_obj | parsed_auth_error | name_error | `User_is_restricted | bot_error ] Lwt.t +val channels_history : + session -> + ?latest:timestamp -> + ?oldest:timestamp -> + ?count:int -> + ?inclusive:bool -> + channel -> + history_result Lwt.t (** Fetches history of messages and events from a channel. @param session The session containing the authentication token. @param latest The newest message from history to be returned. @@ -745,156 +661,509 @@ val channels_create: session -> string -> [ `Success of channel_obj | parsed_aut @param count The number of messages to be returned. @param inclusive Include messages with latest or oldest timestamp in results. @param channel The Slack channel from which to get the history. *) -val channels_history: session -> ?latest:timestamp -> ?oldest:timestamp -> ?count:int -> ?inclusive:bool -> channel -> history_result Lwt.t +val channels_info : + session -> + channel -> + [ `Success of channel_obj | parsed_auth_error | channel_error ] Lwt.t (** Gets information about a channel. *) -val channels_info: session -> channel -> [ `Success of channel_obj | parsed_auth_error | channel_error ] Lwt.t +val channels_invite : + session -> + channel -> + user -> + [ `Success of channel_obj + | parsed_auth_error + | channel_error + | user_error + | invite_error + | not_in_channel_error + | already_in_channel_error + | archive_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Invites a user to a channel. *) -val channels_invite: session -> channel -> user -> [ `Success of channel_obj | parsed_auth_error | channel_error | user_error | invite_error | not_in_channel_error | already_in_channel_error | archive_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val channels_join : + session -> + channel -> + [ `Success of channel_obj + | parsed_auth_error + | channel_error + | name_error + | archive_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Joins a channel, creating it if needed. *) -val channels_join: session -> channel -> [ `Success of channel_obj | parsed_auth_error | channel_error | name_error | archive_error | `User_is_restricted | bot_error ] Lwt.t +val channels_kick : + session -> + channel -> + user -> + [ `Success + | parsed_auth_error + | channel_error + | user_error + | channel_kick_error + | not_in_channel_error + | restriction_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Removes a user from a channel. *) -val channels_kick: session -> channel -> user -> [ `Success | parsed_auth_error | channel_error | user_error | channel_kick_error | not_in_channel_error | restriction_error | `User_is_restricted | bot_error ] Lwt.t +val channels_leave : + session -> + channel -> + [ `Success of channel_leave_obj + | parsed_auth_error + | channel_error + | archive_error + | leave_general_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Leaves a channel. *) -val channels_leave: session -> channel -> [ `Success of channel_leave_obj | parsed_auth_error | channel_error | archive_error | leave_general_error | `User_is_restricted | bot_error ] Lwt.t +val conversations_list : + ?exclude_archived:bool -> + session -> + [ `Success of conversation_obj list | parsed_auth_error ] Lwt.t (** Lists all channels in a Slack team. *) -val conversations_list: ?exclude_archived:bool -> session -> [ `Success of conversation_obj list | parsed_auth_error ] Lwt.t +val channels_mark : + session -> + channel -> + timestamp -> + [ `Success + | parsed_auth_error + | channel_error + | archive_error + | not_in_channel_error ] + Lwt.t (** Sets the read cursor in a channel. *) -val channels_mark: session -> channel -> timestamp -> [ `Success | parsed_auth_error | channel_error | archive_error | not_in_channel_error ] Lwt.t +val channels_rename : + session -> + channel -> + string -> + [ `Success of channel_rename_obj + | parsed_auth_error + | channel_error + | not_in_channel_error + | name_error + | invalid_name_error + | `Not_authorized + | `User_is_restricted + | bot_error ] + Lwt.t (** Renames a team channel. *) -val channels_rename: session -> channel -> string -> [ `Success of channel_rename_obj | parsed_auth_error | channel_error | not_in_channel_error | name_error | invalid_name_error | `Not_authorized | `User_is_restricted | bot_error ] Lwt.t +val channels_set_purpose : session -> channel -> topic -> topic_result Lwt.t (** Sets the purpose for a channel. *) -val channels_set_purpose: session -> channel -> topic -> topic_result Lwt.t +val channels_set_topic : session -> channel -> topic -> topic_result Lwt.t (** Sets the topic for a channel. *) -val channels_set_topic: session -> channel -> topic -> topic_result Lwt.t +val channels_unarchive : + session -> + channel -> + [ `Success + | parsed_auth_error + | channel_error + | `Not_archived + | `User_is_restricted + | bot_error ] + Lwt.t (** Unarchives a channel. *) -val channels_unarchive: session -> channel -> [ `Success | parsed_auth_error | channel_error | `Not_archived | `User_is_restricted | bot_error ] Lwt.t +val chat_delete : + session -> + timestamp -> + chat -> + [ `Success of chat_obj | parsed_auth_error | channel_error | message_error ] + Lwt.t (** Deletes a message. *) -val chat_delete: session -> timestamp -> chat -> [ `Success of chat_obj | parsed_auth_error | channel_error | message_error ] Lwt.t +val chat_post_message : + session -> + chat -> + ?as_user:bool -> + ?link_names:bool -> + ?mrkdwn:bool -> + ?reply_broadcast:bool -> + ?thread_ts:timestamp -> + ?unfurl_links:bool -> + ?unfurl_media:bool -> + ?username:string -> + ?parse:string -> + ?icon_url:string -> + ?icon_emoji:string -> + ?attachments:attachment_obj list -> + message -> + [ `Success of chat_obj + | parsed_auth_error + | channel_error + | archive_error + | message_length_error + | attachments_error + | rate_error + | bot_error ] + Lwt.t (** Sends a message to a channel. *) -val chat_post_message: session -> chat -> ?as_user:bool -> ?link_names:bool -> ?mrkdwn:bool -> ?reply_broadcast:bool -> ?thread_ts:timestamp -> ?unfurl_links:bool -> ?unfurl_media:bool -> ?username:string -> ?parse:string -> ?icon_url:string -> ?icon_emoji:string -> ?attachments:attachment_obj list -> message -> [ `Success of chat_obj | parsed_auth_error | channel_error | archive_error | message_length_error | attachments_error | rate_error | bot_error ] Lwt.t +val chat_update : + session -> + timestamp -> + chat -> + ?as_user:bool -> + ?attachments:attachment_obj list -> + ?link_names:bool -> + ?parse:string -> + message -> + [ `Success of chat_obj + | parsed_auth_error + | channel_error + | message_update_error + | message_length_error + | attachments_error ] + Lwt.t (** Updates a message. *) -val chat_update: session -> timestamp -> chat -> ?as_user:bool -> ?attachments:attachment_obj list -> ?link_names:bool -> ?parse:string -> message -> [ `Success of chat_obj | parsed_auth_error | channel_error | message_update_error | message_length_error | attachments_error ] Lwt.t +val emoji_list : session -> [ `Success of emoji list | parsed_auth_error ] Lwt.t (** Lists custom emoji for a team. *) -val emoji_list: session -> [ `Success of emoji list | parsed_auth_error ] Lwt.t - -val files_delete: session -> string -> [ `Success | parsed_auth_error | `Cant_delete_file | file_error | bot_error ] Lwt.t +val files_delete : + session -> + string -> + [ `Success | parsed_auth_error | `Cant_delete_file | file_error | bot_error ] + Lwt.t + +val files_info : + session -> + ?count:int -> + ?page:int -> + string -> + [ `Success of files_info_obj | parsed_auth_error | file_error | bot_error ] + Lwt.t (** Gets information about a team file. *) -val files_info: session -> ?count:int -> ?page:int -> string -> [ `Success of files_info_obj | parsed_auth_error | file_error | bot_error ] Lwt.t +val files_list : + ?user:user -> + ?ts_from:timestamp -> + ?ts_to:timestamp -> + ?types:string -> + ?count:int -> + ?page:int -> + session -> + [ `Success of files_list_obj + | parsed_auth_error + | user_error + | unknown_type_error + | bot_error ] + Lwt.t (** Lists & filters team files. *) -val files_list: ?user:user -> ?ts_from:timestamp -> ?ts_to:timestamp -> ?types:string -> ?count:int -> ?page:int -> session -> [ `Success of files_list_obj | parsed_auth_error | user_error | unknown_type_error | bot_error ] Lwt.t +val files_upload : + session -> + ?filetype:string -> + ?filename:string -> + ?title:string -> + ?initial_comment:string -> + ?channels:string -> + Cohttp_lwt.Body.t -> + [ `Success of file_obj | parsed_auth_error | bot_error ] Lwt.t (** Uploads or creates a file. *) -val files_upload: session -> ?filetype:string -> ?filename:string -> ?title:string -> ?initial_comment:string -> ?channels:string -> Cohttp_lwt.Body.t -> [ `Success of file_obj | parsed_auth_error | bot_error ] Lwt.t +val groups_archive : + session -> + group -> + [ `Success + | parsed_auth_error + | channel_error + | already_archived_error + | `Group_contains_others + | `Last_restricted_channel + | restriction_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Archives a private group. *) -val groups_archive: session -> group -> [ `Success | parsed_auth_error | channel_error | already_archived_error | `Group_contains_others | `Last_restricted_channel | restriction_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val groups_close : + session -> + group -> + [ `Success of chat_close_obj | parsed_auth_error | channel_error ] Lwt.t (** Closes a private group. *) -val groups_close: session -> group -> [ `Success of chat_close_obj | parsed_auth_error | channel_error ] Lwt.t +val groups_create : + session -> + group -> + [ `Success of group_obj + | parsed_auth_error + | name_error + | restriction_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Creates a private group. *) -val groups_create: session -> group -> [ `Success of group_obj | parsed_auth_error | name_error | restriction_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val groups_create_child : + session -> + group -> + [ `Success of group_obj + | parsed_auth_error + | channel_error + | already_archived_error + | restriction_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Clones and archives a private group. *) -val groups_create_child: session -> group -> [ `Success of group_obj | parsed_auth_error | channel_error | already_archived_error | restriction_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val groups_history : + session -> + ?latest:timestamp -> + ?oldest:timestamp -> + ?count:int -> + ?inclusive:bool -> + group -> + history_result Lwt.t (** Fetches history of messages and events from a private group. *) -val groups_history: session -> ?latest:timestamp -> ?oldest:timestamp -> ?count:int -> ?inclusive:bool -> group -> history_result Lwt.t +val groups_invite : + session -> + group -> + user -> + [ `Success of groups_invite_obj + | parsed_auth_error + | channel_error + | user_error + | invite_error + | archive_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Invites a user to a private group. *) -val groups_invite: session -> group -> user -> [ `Success of groups_invite_obj | parsed_auth_error | channel_error | user_error | invite_error | archive_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val groups_kick : + session -> + group -> + user -> + [ `Success + | parsed_auth_error + | channel_error + | user_error + | kick_error + | not_in_group_error + | restriction_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Removes a user from a private group. *) -val groups_kick: session -> group -> user -> [ `Success | parsed_auth_error | channel_error | user_error | kick_error | not_in_group_error | restriction_error | `User_is_restricted | bot_error ] Lwt.t +val groups_leave : + session -> + group -> + [ `Success + | parsed_auth_error + | channel_error + | archive_error + | leave_last_channel_error + | last_member_error + | `User_is_ultra_restricted + | bot_error ] + Lwt.t (** Leaves a private group. *) -val groups_leave: session -> group -> [ `Success | parsed_auth_error | channel_error | archive_error | leave_last_channel_error | last_member_error | `User_is_ultra_restricted | bot_error ] Lwt.t +val groups_list : + ?exclude_archived:bool -> + session -> + [ `Success of group_obj list | parsed_auth_error ] Lwt.t (** Lists private groups that the calling user has access to. *) -val groups_list: ?exclude_archived:bool -> session -> [ `Success of group_obj list | parsed_auth_error ] Lwt.t +val groups_mark : + session -> + group -> + timestamp -> + [ `Success + | parsed_auth_error + | channel_error + | archive_error + | not_in_channel_error ] + Lwt.t (** Sets the read cursor in a private group. *) -val groups_mark: session -> group -> timestamp -> [ `Success | parsed_auth_error | channel_error | archive_error | not_in_channel_error ] Lwt.t +val groups_open : + session -> + group -> + [ `Success of groups_open_obj | parsed_auth_error | channel_error ] Lwt.t (** Opens a private group. *) -val groups_open: session -> group -> [ `Success of groups_open_obj | parsed_auth_error | channel_error ] Lwt.t +val groups_rename : + session -> + group -> + string -> + [ `Success of groups_rename_obj + | parsed_auth_error + | channel_error + | name_error + | invalid_name_error + | `User_is_restricted + | bot_error ] + Lwt.t (** Renames a private group. *) -val groups_rename: session -> group -> string -> [ `Success of groups_rename_obj | parsed_auth_error | channel_error | name_error | invalid_name_error | `User_is_restricted | bot_error ] Lwt.t +val groups_set_purpose : session -> group -> topic -> topic_result Lwt.t (** Sets the purpose for a private group. *) -val groups_set_purpose: session -> group -> topic -> topic_result Lwt.t +val groups_set_topic : session -> group -> topic -> topic_result Lwt.t (** Sets the topic for a private group. *) -val groups_set_topic: session -> group -> topic -> topic_result Lwt.t +val groups_unarchive : + session -> + group -> + [ `Success + | parsed_auth_error + | channel_error + | `Not_archived + | `User_is_restricted + | bot_error ] + Lwt.t (** Unarchives a private group. *) -val groups_unarchive: session -> group -> [ `Success | parsed_auth_error | channel_error | `Not_archived | `User_is_restricted | bot_error ] Lwt.t +val im_close : + session -> + im -> + [ `Success of chat_close_obj + | parsed_auth_error + | channel_error + | `User_does_not_own_channel ] + Lwt.t (** Close a direct message channel. *) -val im_close: session -> im -> [ `Success of chat_close_obj | parsed_auth_error | channel_error | `User_does_not_own_channel ] Lwt.t +val im_history : + session -> + ?latest:timestamp -> + ?oldest:timestamp -> + ?count:int -> + ?inclusive:bool -> + im -> + history_result Lwt.t (** Fetches history of messages and events from direct message channel. *) -val im_history: session -> ?latest:timestamp -> ?oldest:timestamp -> ?count:int -> ?inclusive:bool -> im -> history_result Lwt.t +val im_list : session -> [ `Success of im_obj list | parsed_auth_error ] Lwt.t (** Lists direct message channels for the calling user. *) -val im_list: session -> [ `Success of im_obj list | parsed_auth_error ] Lwt.t +val im_mark : + session -> + im -> + timestamp -> + [ `Success | parsed_auth_error | channel_error | not_in_channel_error ] Lwt.t (** Sets the read cursor in a direct message channel. *) -val im_mark: session -> im -> timestamp -> [ `Success | parsed_auth_error | channel_error | not_in_channel_error ] Lwt.t +val im_open : + session -> + user -> + [ `Success of im_open_obj + | parsed_auth_error + | user_error + | user_visibility_error ] + Lwt.t (** Opens a direct message channel. *) -val im_open: session -> user -> [ `Success of im_open_obj | parsed_auth_error | user_error | user_visibility_error ] Lwt.t +val oauth_access : + ?base_url:string -> + string -> + string -> + ?redirect_url:string -> + string -> + [ `Success of oauth_obj | `ParseFailure of string | oauth_error ] Lwt.t (** Exchanges a temporary OAuth code for an API session. *) -val oauth_access: ?base_url:string -> string -> string -> ?redirect_url:string -> string -> [ `Success of oauth_obj | `ParseFailure of string | oauth_error ] Lwt.t +val search_all : + session -> + ?sort:sort_criterion -> + ?sort_dir:sort_direction -> + ?highlight:bool -> + ?count:int -> + ?page:int -> + string -> + [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t (** Searches for messages and files matching a query. *) -val search_all: session -> ?sort:sort_criterion -> ?sort_dir:sort_direction -> ?highlight:bool -> ?count:int -> ?page:int -> string -> [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t +val search_files : + session -> + ?sort:sort_criterion -> + ?sort_dir:sort_direction -> + ?highlight:bool -> + ?count:int -> + ?page:int -> + string -> + [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t (** Searches for files matching a query. *) -val search_files: session -> ?sort:sort_criterion -> ?sort_dir:sort_direction -> ?highlight:bool -> ?count:int -> ?page:int -> string -> [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t +val search_messages : + session -> + ?sort:sort_criterion -> + ?sort_dir:sort_direction -> + ?highlight:bool -> + ?count:int -> + ?page:int -> + string -> + [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t (** Searches for messages matching a query. *) -val search_messages: session -> ?sort:sort_criterion -> ?sort_dir:sort_direction -> ?highlight:bool -> ?count:int -> ?page:int -> string -> [ `Success of search_obj | parsed_auth_error | bot_error ] Lwt.t +val stars_list : + ?user:user -> + ?count:int -> + ?page:int -> + session -> + [ `Success of stars_list_obj | parsed_auth_error | user_error | bot_error ] + Lwt.t (** Lists stars for a user. *) -val stars_list: ?user:user -> ?count:int -> ?page:int -> session -> [ `Success of stars_list_obj | parsed_auth_error | user_error | bot_error ] Lwt.t +val team_access_logs : + ?count:int -> + ?page:int -> + session -> + [ `Success of team_access_log_obj + | parsed_auth_error + | `Paid_only + | bot_error ] + Lwt.t (** Gets the access logs for the current team. *) -val team_access_logs: ?count:int -> ?page:int -> session -> [ `Success of team_access_log_obj | parsed_auth_error | `Paid_only | bot_error ] Lwt.t +val team_info : + session -> [ `Success of team_obj | parsed_auth_error | bot_error ] Lwt.t (** Gets information about the current team. *) -val team_info: session -> [ `Success of team_obj | parsed_auth_error | bot_error ] Lwt.t +val users_get_presence : + session -> + user -> + [ `Success of presence | user_error | parsed_auth_error ] Lwt.t (** Gets user presence information. *) -val users_get_presence: session -> user -> [ `Success of presence | user_error | parsed_auth_error ] Lwt.t +val users_info : + session -> + user -> + [ `Success of user_obj + | parsed_auth_error + | user_error + | user_visibility_error ] + Lwt.t (** Gets information about a user. *) -val users_info: session -> user -> [ `Success of user_obj | parsed_auth_error | user_error | user_visibility_error ] Lwt.t +val users_list : + session -> [ `Success of user_obj list | parsed_auth_error ] Lwt.t (** Lists all users in a Slack team. *) -val users_list: session -> [ `Success of user_obj list | parsed_auth_error ] Lwt.t +val users_set_active : + session -> [ `Success | parsed_auth_error | bot_error ] Lwt.t (** Marks a user as active. *) -val users_set_active: session -> [ `Success | parsed_auth_error | bot_error ] Lwt.t +val users_set_presence : + session -> presence -> [ `Success | parsed_auth_error | presence_error ] Lwt.t (** Manually sets user presence. *) -val users_set_presence: session -> presence -> [ `Success | parsed_auth_error | presence_error ] Lwt.t diff --git a/src/lib/timestamp.ml b/src/lib/timestamp.ml index 2c60b13..9d439cf 100644 --- a/src/lib/timestamp.ml +++ b/src/lib/timestamp.ml @@ -20,12 +20,17 @@ let int64_pow b n = let rec loop b n acc = - if n = 0 then acc else - loop Int64.(mul b b) (n lsr 1) - (if n land 1 = 0 then acc else Int64.(mul b acc)) in + if n = 0 then acc + else + loop + Int64.(mul b b) + (n lsr 1) + (if n land 1 = 0 then acc else Int64.(mul b acc)) + in loop b n 1L type t = Ptime.t + let pp = Ptime.pp_human ~frac_s:6 () let of_string x = @@ -33,20 +38,20 @@ let of_string x = let sec = Int64.of_string intlit in let d = Int64.div sec 86_400L in let ps = Int64.(mul (rem sec 86_400L) 1_000_000_000_000L) in - (Int64.to_int d, ps) in + (Int64.to_int d, ps) + in match match String.split_on_char '.' x with - | [_] -> - Ptime.Span.of_d_ps (d_ps_of_intlit x) - | [sec_lit; subsec_lit] -> - let (d, ps_int) = d_ps_of_intlit sec_lit in - let ps_frac = - if String.length subsec_lit <= 12 then - let scale = int64_pow 10L (12 - String.length subsec_lit) in - Int64.mul scale (Int64.of_string subsec_lit) - else - Int64.of_string (String.sub subsec_lit 0 12) in - Ptime.Span.of_d_ps (d, Int64.add ps_int ps_frac) + | [ _ ] -> Ptime.Span.of_d_ps (d_ps_of_intlit x) + | [ sec_lit; subsec_lit ] -> + let d, ps_int = d_ps_of_intlit sec_lit in + let ps_frac = + if String.length subsec_lit <= 12 then + let scale = int64_pow 10L (12 - String.length subsec_lit) in + Int64.mul scale (Int64.of_string subsec_lit) + else Int64.of_string (String.sub subsec_lit 0 12) + in + Ptime.Span.of_d_ps (d, Int64.add ps_int ps_frac) | _ -> None with | exception Failure _ -> None diff --git a/src/lib/timestamp.mli b/src/lib/timestamp.mli index 583a55c..2ddc1ea 100644 --- a/src/lib/timestamp.mli +++ b/src/lib/timestamp.mli @@ -21,9 +21,6 @@ type t = Ptime.t val to_string : t -> string - val of_yojson : Yojson.Safe.t -> (t, string) result - val to_yojson : t -> Yojson.Safe.t - val pp : Format.formatter -> t -> unit diff --git a/test/abbrtypes.ml b/test/abbrtypes.ml index 42c46ef..798f6a0 100644 --- a/test/abbrtypes.ml +++ b/test/abbrtypes.ml @@ -4,354 +4,353 @@ skip the problematic fields. *) (* Wrap Yojson.Safe.t so we don't have to keep providing printers for it. *) -type json = Yojson.Safe.t -[@@deriving yojson] +type json = Yojson.Safe.t [@@deriving yojson] + let pp_json fmt json = Format.pp_print_string fmt (Yojson.Safe.to_string json) type abbr_authed_obj = { - url: string; - team: string; - user: string; - team_id: string; - (* user_id: user; *) -} [@@deriving make, show, yojson { strict = false }] - -let abbr_authed_obj (authed : Slacko.authed_obj) = { - url = authed.Slacko.url; - team = authed.Slacko.team; - user = authed.Slacko.user; - team_id = authed.Slacko.team_id; + url : string; + team : string; + user : string; + team_id : string; (* user_id: user; *) } +[@@deriving make, show, yojson { strict = false }] + +let abbr_authed_obj (authed : Slacko.authed_obj) = + { + url = authed.Slacko.url; + team = authed.Slacko.team; + user = authed.Slacko.user; + team_id = authed.Slacko.team_id; + } type abbr_topic_obj = { - value: string; + value : string; (* creator: user; *) - last_set: Timestamp.t; -} [@@deriving show, yojson { strict = false }] - -let abbr_topic_obj (topic : Slacko.topic_obj) = { - value = topic.Slacko.value; - last_set = topic.Slacko.last_set; + last_set : Timestamp.t; } +[@@deriving show, yojson { strict = false }] + +let abbr_topic_obj (topic : Slacko.topic_obj) = + { value = topic.Slacko.value; last_set = topic.Slacko.last_set } type abbr_channel_obj = { (* id: channel; *) - name: string; - is_channel: bool; - created: Timestamp.t; + name : string; + is_channel : bool; + created : Timestamp.t; (* creator: user; *) - is_archived: bool; - is_general: bool; - is_member: bool; + is_archived : bool; + is_general : bool; + is_member : bool; (* members: user list; *) - topic: abbr_topic_obj; - purpose: abbr_topic_obj; - last_read: Timestamp.t option [@default None]; - latest: json option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - num_members: int option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_channel_obj (chan : Slacko.channel_obj) = { - name = chan.Slacko.name; - is_channel = chan.Slacko.is_channel; - created = chan.Slacko.created; - is_archived = chan.Slacko.is_archived; - is_general = chan.Slacko.is_general; - is_member = chan.Slacko.is_member; - topic = abbr_topic_obj chan.Slacko.topic; - purpose = abbr_topic_obj chan.Slacko.purpose; - last_read = chan.Slacko.last_read; - latest = chan.Slacko.latest; - unread_count = chan.Slacko.unread_count; - unread_count_display = chan.Slacko.unread_count_display; - num_members = chan.Slacko.num_members; + topic : abbr_topic_obj; + purpose : abbr_topic_obj; + last_read : Timestamp.t option; [@default None] + latest : json option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + num_members : int option; [@default None] } - -type abbr_channel_obj_list = abbr_channel_obj list -[@@deriving show, yojson] +[@@deriving show, yojson { strict = false }] + +let abbr_channel_obj (chan : Slacko.channel_obj) = + { + name = chan.Slacko.name; + is_channel = chan.Slacko.is_channel; + created = chan.Slacko.created; + is_archived = chan.Slacko.is_archived; + is_general = chan.Slacko.is_general; + is_member = chan.Slacko.is_member; + topic = abbr_topic_obj chan.Slacko.topic; + purpose = abbr_topic_obj chan.Slacko.purpose; + last_read = chan.Slacko.last_read; + latest = chan.Slacko.latest; + unread_count = chan.Slacko.unread_count; + unread_count_display = chan.Slacko.unread_count_display; + num_members = chan.Slacko.num_members; + } + +type abbr_channel_obj_list = abbr_channel_obj list [@@deriving show, yojson] type abbr_conversation_obj = { (* id: conversation; *) - name: string; - is_channel: bool; - created: Timestamp.t; + name : string; + is_channel : bool; + created : Timestamp.t; (* creator: user; *) - is_archived: bool; - is_general: bool; - is_member: bool; + is_archived : bool; + is_general : bool; + is_member : bool; (* members: user list; *) - topic: abbr_topic_obj; - purpose: abbr_topic_obj; - last_read: Timestamp.t option [@default None]; + topic : abbr_topic_obj; + purpose : abbr_topic_obj; + last_read : Timestamp.t option; [@default None] (* latest: json option [@default None]; *) - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - num_members: int option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_conversation_obj (conversation : Slacko.conversation_obj) = { - name = conversation.Slacko.name; - is_channel = conversation.Slacko.is_channel; - created = conversation.Slacko.created; - is_archived = conversation.Slacko.is_archived; - is_general = conversation.Slacko.is_general; - is_member = conversation.Slacko.is_member; - topic = abbr_topic_obj conversation.Slacko.topic; - purpose = abbr_topic_obj conversation.Slacko.purpose; - last_read = conversation.Slacko.last_read; - unread_count = conversation.Slacko.unread_count; - unread_count_display = conversation.Slacko.unread_count_display; - num_members = conversation.Slacko.num_members; + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + num_members : int option; [@default None] } - -type abbr_conversation_obj_list = abbr_conversation_obj list -[@@deriving show] - -type abbr_conversation_list_obj = { - channels: abbr_conversation_obj list -} [@@deriving show, yojson { strict = false }] +[@@deriving show, yojson { strict = false }] + +let abbr_conversation_obj (conversation : Slacko.conversation_obj) = + { + name = conversation.Slacko.name; + is_channel = conversation.Slacko.is_channel; + created = conversation.Slacko.created; + is_archived = conversation.Slacko.is_archived; + is_general = conversation.Slacko.is_general; + is_member = conversation.Slacko.is_member; + topic = abbr_topic_obj conversation.Slacko.topic; + purpose = abbr_topic_obj conversation.Slacko.purpose; + last_read = conversation.Slacko.last_read; + unread_count = conversation.Slacko.unread_count; + unread_count_display = conversation.Slacko.unread_count_display; + num_members = conversation.Slacko.num_members; + } + +type abbr_conversation_obj_list = abbr_conversation_obj list [@@deriving show] + +type abbr_conversation_list_obj = { channels : abbr_conversation_obj list } +[@@deriving show, yojson { strict = false }] let abbr_conversation_obj_list_of_yojson json = match abbr_conversation_list_obj_of_yojson json with | Ok obj -> Ok obj.channels - | (Error _) as err -> err + | Error _ as err -> err type abbr_message_obj = { - type': string [@key "type"]; - ts: Timestamp.t; + type' : string; [@key "type"] + ts : Timestamp.t; (* user: user; *) - text: string option; - is_starred: bool option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_message_obj (message : Slacko.message_obj) = { - type' = message.Slacko.type'; - ts = message.Slacko.ts; - text = message.Slacko.text; - is_starred = message.Slacko.is_starred; + text : string option; + is_starred : bool option; [@default None] } +[@@deriving show, yojson { strict = false }] + +let abbr_message_obj (message : Slacko.message_obj) = + { + type' = message.Slacko.type'; + ts = message.Slacko.ts; + text = message.Slacko.text; + is_starred = message.Slacko.is_starred; + } type abbr_history_obj = { - latest: Timestamp.t option [@default None]; - messages: abbr_message_obj list; - has_more: bool; -} [@@deriving show, yojson { strict = false }] - -let abbr_history_obj (history : Slacko.history_obj) = { - latest = history.Slacko.latest; - messages = List.map abbr_message_obj history.Slacko.messages; - has_more = history.Slacko.has_more; + latest : Timestamp.t option; [@default None] + messages : abbr_message_obj list; + has_more : bool; } +[@@deriving show, yojson { strict = false }] + +let abbr_history_obj (history : Slacko.history_obj) = + { + latest = history.Slacko.latest; + messages = List.map abbr_message_obj history.Slacko.messages; + has_more = history.Slacko.has_more; + } type abbr_user_obj = { (* id: user; *) - name: string; - deleted: bool; - color: string option [@default None]; - real_name: string option [@default None]; - tz: string option [@default None]; - tz_label: string option [@default None]; - tz_offset: int [@default 0]; - profile: json; - is_admin: bool [@default false]; - is_owner: bool [@default false]; - is_primary_owner: bool [@default false]; - is_restricted: bool [@default false]; - is_ultra_restricted: bool [@default false]; - is_bot: bool; - has_files: bool [@default false]; -} [@@deriving show, yojson { strict = false } ] - -let abbr_user_obj (user : Slacko.user_obj) = { - name = user.Slacko.name; - deleted = user.Slacko.deleted; - color = user.Slacko.color; - real_name = user.Slacko.real_name; - tz = user.Slacko.tz; - tz_label = user.Slacko.tz_label; - tz_offset = user.Slacko.tz_offset; - profile = user.Slacko.profile; - is_admin = user.Slacko.is_admin; - is_owner = user.Slacko.is_owner; - is_primary_owner = user.Slacko.is_primary_owner; - is_restricted = user.Slacko.is_restricted; - is_ultra_restricted = user.Slacko.is_ultra_restricted; - is_bot = user.Slacko.is_bot; - has_files = user.Slacko.has_files; + name : string; + deleted : bool; + color : string option; [@default None] + real_name : string option; [@default None] + tz : string option; [@default None] + tz_label : string option; [@default None] + tz_offset : int; [@default 0] + profile : json; + is_admin : bool; [@default false] + is_owner : bool; [@default false] + is_primary_owner : bool; [@default false] + is_restricted : bool; [@default false] + is_ultra_restricted : bool; [@default false] + is_bot : bool; + has_files : bool; [@default false] } - -type abbr_users_list_obj = { - members: abbr_user_obj list -} [@@deriving of_yojson { strict = false }] - -type abbr_user_obj_list = abbr_user_obj list -[@@deriving show] +[@@deriving show, yojson { strict = false }] + +let abbr_user_obj (user : Slacko.user_obj) = + { + name = user.Slacko.name; + deleted = user.Slacko.deleted; + color = user.Slacko.color; + real_name = user.Slacko.real_name; + tz = user.Slacko.tz; + tz_label = user.Slacko.tz_label; + tz_offset = user.Slacko.tz_offset; + profile = user.Slacko.profile; + is_admin = user.Slacko.is_admin; + is_owner = user.Slacko.is_owner; + is_primary_owner = user.Slacko.is_primary_owner; + is_restricted = user.Slacko.is_restricted; + is_ultra_restricted = user.Slacko.is_ultra_restricted; + is_bot = user.Slacko.is_bot; + has_files = user.Slacko.has_files; + } + +type abbr_users_list_obj = { members : abbr_user_obj list } +[@@deriving of_yojson { strict = false }] + +type abbr_user_obj_list = abbr_user_obj list [@@deriving show] let abbr_user_obj_list_of_yojson json = match abbr_users_list_obj_of_yojson json with | Ok obj -> Ok obj.members - | (Error _) as err -> err - + | Error _ as err -> err type abbr_file_obj = { (* TODO file id type *) - id: string; - created: Timestamp.t; + id : string; + created : Timestamp.t; (* deprecated *) - timestamp: Timestamp.t; - - name: string option [@default None]; - title: string; - mimetype: string; - pretty_type: string; - (* user: user; *) - - mode: string; - editable: bool; - is_external: bool; - external_type: string; - - size: int; - + timestamp : Timestamp.t; + name : string option; [@default None] + title : string; + mimetype : string; + pretty_type : string; (* user: user; *) + mode : string; + editable : bool; + is_external : bool; + external_type : string; + size : int; (* These two are deprecated and appear to be gone. *) (* url: string; *) (* url_download: string; *) - url_private: string; - url_private_download: string; - - thumb_64: string option [@default None]; - thunb_80: string option [@default None]; - thumb_360: string option [@default None]; - thumb_360_gif: string option [@default None]; - thumb_360_w: int option [@default None]; - thumb_360_h: int option [@default None]; - - permalink: string; - edit_link: string option [@default None]; - preview: string option [@default None]; - preview_highlight: string option [@default None]; - lines: int option [@default None]; - lines_more: int option [@default None]; - - is_public: bool; + url_private : string; + url_private_download : string; + thumb_64 : string option; [@default None] + thunb_80 : string option; [@default None] + thumb_360 : string option; [@default None] + thumb_360_gif : string option; [@default None] + thumb_360_w : int option; [@default None] + thumb_360_h : int option; [@default None] + permalink : string; + edit_link : string option; [@default None] + preview : string option; [@default None] + preview_highlight : string option; [@default None] + lines : int option; [@default None] + lines_more : int option; [@default None] + is_public : bool; (*public_url_shared: ???;*) (* channels: channel list; *) (* groups: group list; *) (* ims: conversation list; *) - initial_comment: json option [@default None]; - num_stars: int option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_file_obj (file : Slacko.file_obj) = { - id = file.Slacko.id; - created = file.Slacko.created; - timestamp = file.Slacko.timestamp; - name = file.Slacko.name; - title = file.Slacko.title; - mimetype = file.Slacko.mimetype; - pretty_type = file.Slacko.pretty_type; - mode = file.Slacko.mode; - editable = file.Slacko.editable; - is_external = file.Slacko.is_external; - external_type = file.Slacko.external_type; - size = file.Slacko.size; - url_private = file.Slacko.url_private; - url_private_download = file.Slacko.url_private_download; - thumb_64 = file.Slacko.thumb_64; - thunb_80 = file.Slacko.thunb_80; - thumb_360 = file.Slacko.thumb_360; - thumb_360_gif = file.Slacko.thumb_360_gif; - thumb_360_w = file.Slacko.thumb_360_w; - thumb_360_h = file.Slacko.thumb_360_h; - permalink = file.Slacko.permalink; - edit_link = file.Slacko.edit_link; - preview = file.Slacko.preview; - preview_highlight = file.Slacko.preview_highlight; - lines = file.Slacko.lines; - lines_more = file.Slacko.lines_more; - is_public = file.Slacko.is_public; - initial_comment = file.Slacko.initial_comment; - num_stars = file.Slacko.num_stars; -} - -type abbr_paging_obj = { - count: int; - total: int; - page: int; - pages: int; -} [@@deriving show, yojson { strict = false }] - -let abbr_paging_obj (paging : Slacko.paging_obj) = { - count = paging.Slacko.count; - total = paging.Slacko.total; - page = paging.Slacko.page; - pages = paging.Slacko.pages; + initial_comment : json option; [@default None] + num_stars : int option; [@default None] } +[@@deriving show, yojson { strict = false }] + +let abbr_file_obj (file : Slacko.file_obj) = + { + id = file.Slacko.id; + created = file.Slacko.created; + timestamp = file.Slacko.timestamp; + name = file.Slacko.name; + title = file.Slacko.title; + mimetype = file.Slacko.mimetype; + pretty_type = file.Slacko.pretty_type; + mode = file.Slacko.mode; + editable = file.Slacko.editable; + is_external = file.Slacko.is_external; + external_type = file.Slacko.external_type; + size = file.Slacko.size; + url_private = file.Slacko.url_private; + url_private_download = file.Slacko.url_private_download; + thumb_64 = file.Slacko.thumb_64; + thunb_80 = file.Slacko.thunb_80; + thumb_360 = file.Slacko.thumb_360; + thumb_360_gif = file.Slacko.thumb_360_gif; + thumb_360_w = file.Slacko.thumb_360_w; + thumb_360_h = file.Slacko.thumb_360_h; + permalink = file.Slacko.permalink; + edit_link = file.Slacko.edit_link; + preview = file.Slacko.preview; + preview_highlight = file.Slacko.preview_highlight; + lines = file.Slacko.lines; + lines_more = file.Slacko.lines_more; + is_public = file.Slacko.is_public; + initial_comment = file.Slacko.initial_comment; + num_stars = file.Slacko.num_stars; + } + +type abbr_paging_obj = { count : int; total : int; page : int; pages : int } +[@@deriving show, yojson { strict = false }] + +let abbr_paging_obj (paging : Slacko.paging_obj) = + { + count = paging.Slacko.count; + total = paging.Slacko.total; + page = paging.Slacko.page; + pages = paging.Slacko.pages; + } type abbr_files_list_obj = { - files: abbr_file_obj list; - paging: abbr_paging_obj; -} [@@deriving show, yojson { strict = false }] - -let abbr_files_list_obj (files : Slacko.files_list_obj) = { - files = List.map abbr_file_obj files.Slacko.files; - paging = abbr_paging_obj files.Slacko.paging; + files : abbr_file_obj list; + paging : abbr_paging_obj; } +[@@deriving show, yojson { strict = false }] + +let abbr_files_list_obj (files : Slacko.files_list_obj) = + { + files = List.map abbr_file_obj files.Slacko.files; + paging = abbr_paging_obj files.Slacko.paging; + } type abbr_group_obj = { (* id: group; *) - name: string; - is_group: bool; - created: Timestamp.t; + name : string; + is_group : bool; + created : Timestamp.t; (* creator: user; *) - is_archived: bool; + is_archived : bool; (* members: user list; *) - topic: abbr_topic_obj; - purpose: abbr_topic_obj; - is_open: bool option [@default None]; - last_read: Timestamp.t option [@default None]; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; - latest: json option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_group_obj (group : Slacko.group_obj) = { - name = group.Slacko.name; - is_group = group.Slacko.is_group; - created = group.Slacko.created; - is_archived = group.Slacko.is_archived; - topic = abbr_topic_obj group.Slacko.topic; - purpose = abbr_topic_obj group.Slacko.purpose; - is_open = group.Slacko.is_open; - last_read = group.Slacko.last_read; - unread_count = group.Slacko.unread_count; - unread_count_display = group.Slacko.unread_count_display; - latest = group.Slacko.latest; + topic : abbr_topic_obj; + purpose : abbr_topic_obj; + is_open : bool option; [@default None] + last_read : Timestamp.t option; [@default None] + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] + latest : json option; [@default None] } - -type abbr_group_obj_list = abbr_group_obj list -[@@deriving show, yojson] +[@@deriving show, yojson { strict = false }] + +let abbr_group_obj (group : Slacko.group_obj) = + { + name = group.Slacko.name; + is_group = group.Slacko.is_group; + created = group.Slacko.created; + is_archived = group.Slacko.is_archived; + topic = abbr_topic_obj group.Slacko.topic; + purpose = abbr_topic_obj group.Slacko.purpose; + is_open = group.Slacko.is_open; + last_read = group.Slacko.last_read; + unread_count = group.Slacko.unread_count; + unread_count_display = group.Slacko.unread_count_display; + latest = group.Slacko.latest; + } + +type abbr_group_obj_list = abbr_group_obj list [@@deriving show, yojson] type abbr_im_obj = { - id: string; - is_im: bool; + id : string; + is_im : bool; (* user: user; *) - created: Timestamp.t; - is_user_deleted: bool; - unread_count: int option [@default None]; - unread_count_display: int option [@default None]; -} [@@deriving show, yojson { strict = false }] - -let abbr_im_obj (im : Slacko.im_obj) = { - id = im.Slacko.id; - is_im = im.Slacko.is_im; - created = im.Slacko.created; - is_user_deleted = im.Slacko.is_user_deleted; - unread_count = im.Slacko.unread_count; - unread_count_display = im.Slacko.unread_count_display; + created : Timestamp.t; + is_user_deleted : bool; + unread_count : int option; [@default None] + unread_count_display : int option; [@default None] } - -type abbr_im_obj_list = abbr_im_obj list -[@@deriving show, yojson] +[@@deriving show, yojson { strict = false }] + +let abbr_im_obj (im : Slacko.im_obj) = + { + id = im.Slacko.id; + is_im = im.Slacko.is_im; + created = im.Slacko.created; + is_user_deleted = im.Slacko.is_user_deleted; + unread_count = im.Slacko.unread_count; + unread_count_display = im.Slacko.unread_count_display; + } + +type abbr_im_obj_list = abbr_im_obj list [@@deriving show, yojson] diff --git a/test/dune b/test/dune index d346aec..6ff6157 100644 --- a/test/dune +++ b/test/dune @@ -1,15 +1,20 @@ (rule - (targets timestamp.ml) - (deps ../src/lib/timestamp.ml) - (action (copy %{deps} %{targets}))) + (targets timestamp.ml) + (deps ../src/lib/timestamp.ml) + (action + (copy %{deps} %{targets}))) (executable - (name test_slacko) - (libraries slacko ounit2) - (preprocess (pps ppx_deriving_yojson ppx_deriving.std))) + (name test_slacko) + (libraries slacko ounit2) + (preprocess + (pps ppx_deriving_yojson ppx_deriving.std))) (rule - (alias runtest) - (deps (:test (file test_slacko.exe)) - (glob_files *.json)) - (action (run %{test} -runner sequential))) + (alias runtest) + (deps + (:test + (file test_slacko.exe)) + (glob_files *.json)) + (action + (run %{test} -runner sequential))) diff --git a/test/fake_slack.ml b/test/fake_slack.ml index d48d952..12fa192 100644 --- a/test/fake_slack.ml +++ b/test/fake_slack.ml @@ -14,10 +14,9 @@ let ch_random = "C3TTWNCTA" let ch_archivable = "C3XTJPLFL" let ch_archived = "C3XTHDCTC" let gr_seekrit = "G536YKXPE" + (* slacko doesn't have a lookup function for us, so we just use it directly. *) let im_slackbot = "D3UMJU8VA" - - let channels_json = Yojson.Safe.from_file "channels.json" let conversations_json = Yojson.Safe.from_file "conversations.json" let new_channel_json = Yojson.Safe.from_file "new_channel.json" @@ -34,16 +33,11 @@ let json_fields = function | `Assoc fields -> fields | _ -> failwith "Can't parse test json." - let reply_json ok fields = - let body = - `Assoc (("ok", `Bool ok) :: fields) - |> Yojson.Safe.to_string - in + let body = `Assoc (("ok", `Bool ok) :: fields) |> Yojson.Safe.to_string in Server.respond_string ~status:`OK ~body () let reply_ok fields = reply_json true fields - let reply_err err fields = reply_json false (("error", `String err) :: fields) let get_token_opt req = @@ -52,16 +46,13 @@ let get_token_opt req = match header with | Some x -> let hlen = String.length "Bearer " in - Some (String.sub x hlen @@ String.length x - hlen) + Some (String.sub x hlen @@ (String.length x - hlen)) | _ -> None -let get_arg_opt arg req = - Uri.get_query_param (Request.uri req) arg +let get_arg_opt arg req = Uri.get_query_param (Request.uri req) arg let get_arg_default arg default req = - match get_arg_opt arg req with - | Some x -> x - | None -> default + match get_arg_opt arg req with Some x -> x | None -> default let get_arg arg req = match get_arg_opt arg req with @@ -77,21 +68,21 @@ let check_auth f req body = let bad_path req _body = let path = req |> Request.uri |> Uri.path in - reply_err "unknown_method" ["req_method", `String path] + reply_err "unknown_method" [ ("req_method", `String path) ] let api_test req _body = let args = req |> Request.uri |> Uri.query in - let field_of_arg (k, v) = k, `String (List.hd v) in - let fields = match args with + let field_of_arg (k, v) = (k, `String (List.hd v)) in + let fields = + match args with | [] -> [] - | args -> ["args", `Assoc (List.map field_of_arg args)] + | args -> [ ("args", `Assoc (List.map field_of_arg args)) ] in match Uri.get_query_param (Request.uri req) "error" with | None -> reply_ok fields | Some err -> reply_err err fields -let auth_test _req _body = - reply_ok (json_fields authed_json) +let auth_test _req _body = reply_ok (json_fields authed_json) let channels_archive req _body = match get_arg "channel" req with @@ -103,7 +94,7 @@ let channels_archive req _body = let channels_create req _body = match get_arg "name" req with | "general" | "random" -> reply_err "name_taken" [] - | "new_channel" | _ -> reply_ok ["channel", new_channel_json] + | "new_channel" | _ -> reply_ok [ ("channel", new_channel_json) ] let channels_history req _body = (* TODO: Check various filtering params. *) @@ -113,7 +104,7 @@ let channels_history req _body = let channels_list _req _body = (* TODO: Check exclude_archived param. *) - reply_ok ["channels", channels_json] + reply_ok [ ("channels", channels_json) ] let files_list _req _body = (* TODO: Check various filtering params. *) @@ -121,7 +112,7 @@ let files_list _req _body = let groups_list _req _body = (* TODO: Check exclude_archived param. *) - reply_ok ["groups", groups_json] + reply_ok [ ("groups", groups_json) ] let groups_history req _body = (* TODO: Check various filtering params. *) @@ -129,8 +120,7 @@ let groups_history req _body = | gr when gr = gr_seekrit -> reply_ok (json_fields seekrit_history_json) | _ -> reply_err "channel_not_found" [] -let im_list _req _body = - reply_ok ["ims", ims_json] +let im_list _req _body = reply_ok [ ("ims", ims_json) ] let im_history req _body = (* TODO: Check various filtering params. *) @@ -142,14 +132,14 @@ let users_list _req _body = (* TODO: Check presence param. *) reply_ok (json_fields users_json) -let conversations_list _req _body = - reply_ok (json_fields conversations_json) +let conversations_list _req _body = reply_ok (json_fields conversations_json) (* Dispatcher, etc. *) -let server ?(port=7357) ~stop () = +let server ?(port = 7357) ~stop () = let callback _conn req body = - let handler = match req |> Request.uri |> Uri.path with + let handler = + match req |> Request.uri |> Uri.path with | "/api/api.test" -> api_test | "/api/auth.test" -> check_auth auth_test | "/api/channels.archive" -> check_auth channels_archive diff --git a/test/files.json b/test/files.json index ff6d1d4..c534a76 100644 --- a/test/files.json +++ b/test/files.json @@ -1,134 +1,134 @@ { - "files": [ - { - "channels": [], - "comments_count": 0, - "created": 1484993283, - "display_as_bot": false, - "editable": true, - "external_type": "", - "filetype": "space", - "groups": [], - "id": "F3UMJU94L", - "ims": [], - "is_external": false, - "is_public": true, - "mimetype": "text/plain", - "mode": "space", - "name": "Welcome_to_Slack", - "permalink": "https://slackobot.slack.com/files/slackbot/F3UMJU94L/welcome_to_slack", - "permalink_public": "https://slack-files.com/T3UMV2E2H-F3UMJU94L-37beaf2b00", - "pretty_type": "Post", - "preview": "

Slack is here to help make your working life simpler, more pleasant, and more productive.

", - "public_url_shared": false, - "size": 1887, - "state": "locked", - "timestamp": 1484993283, - "title": "Welcome to Slack!", - "updated": 1484993283, - "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU94L/welcome_to_slack", - "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU94L/download/welcome_to_slack", - "user": "USLACKBOT", - "username": "" - }, - { - "channels": [], - "comments_count": 0, - "created": 1484993283, - "display_as_bot": false, - "editable": true, - "external_type": "", - "filetype": "space", - "groups": [], - "id": "F3TTWND40", - "ims": [], - "is_external": false, - "is_public": true, - "mimetype": "text/plain", - "mode": "space", - "name": "Channels_Keep_Your_Conversations_Organized", - "permalink": "https://slackobot.slack.com/files/slackbot/F3TTWND40/channels_keep_your_conversations_organized", - "permalink_public": "https://slack-files.com/T3UMV2E2H-F3TTWND40-e6670a0e44", - "pretty_type": "Post", - "preview": "

Slack lets you join and create channels to keep your conversations organized and focused. Create channels for your functional teams, large projects, and topics of interest.

Find existing channels to join by clicking the more channels link below, or \"your channels\" above, your open channels. Or create a new one from those same menus (hint: you can also do the same thing by typing \"/open #name\" into the chat input).

When you join a channel, the entire message history is available to you. You can see who else is in that channel by clicking the members tab in the upper right corner of the message pane.

Remove a channel from your open channel list by typing /close in the chat input or from the channel's drop-down list. If you re-open the channel later, all the content will still be there, including messages exchanged when you didn't have the channel open.

You can view content from all your team's channels without joining the channel in the Message Archives. And when you search in Slack, the results show content from all channels, even if you are not in that one.

", - "public_url_shared": false, - "size": 1445, - "state": "locked", - "timestamp": 1484993283, - "title": "Channels Keep Your Conversations Organized", - "updated": 1484993283, - "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3TTWND40/channels_keep_your_conversations_organized", - "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3TTWND40/download/channels_keep_your_conversations_organized", - "user": "USLACKBOT", - "username": "" - }, - { - "channels": [], - "comments_count": 0, - "created": 1484993283, - "display_as_bot": false, - "editable": true, - "external_type": "", - "filetype": "space", - "groups": [], - "id": "F3V9V05SS", - "ims": [], - "is_external": false, - "is_public": true, - "mimetype": "text/plain", - "mode": "space", - "name": "Getting_Started_with_Posts", - "permalink": "https://slackobot.slack.com/files/slackbot/F3V9V05SS/getting_started_with_posts", - "permalink_public": "https://slack-files.com/T3UMV2E2H-F3V9V05SS-342113c685", - "pretty_type": "Post", - "preview": "

Hi! Welcome to Posts, Slack's built-in document editor. Posts are a great way to share long-form content — like project plans, or documentation — directly in Slack. So how does one use Posts? Well, let's get right to it:

Creating a new Post

You can create a new Post from the + button in the Slack message input.

Formatting text

Text formatting in Posts was designed for simplicity, with just the right formatting options to help you get your thoughts organized.

", - "public_url_shared": false, - "size": 3294, - "state": "locked", - "timestamp": 1484993283, - "title": "Getting Started with Posts", - "updated": 1484993283, - "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3V9V05SS/getting_started_with_posts", - "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3V9V05SS/download/getting_started_with_posts", - "user": "USLACKBOT", - "username": "" - }, - { - "channels": [], - "comments_count": 0, - "created": 1484993283, - "display_as_bot": false, - "editable": true, - "external_type": "", - "filetype": "space", - "groups": [], - "id": "F3UMJU96G", - "ims": [], - "is_external": false, - "is_public": true, - "mimetype": "text/plain", - "mode": "space", - "name": "Uploading_Your_Files_Into_Slack", - "permalink": "https://slackobot.slack.com/files/slackbot/F3UMJU96G/uploading_your_files_into_slack", - "permalink_public": "https://slack-files.com/T3UMV2E2H-F3UMJU96G-3d27c638d7", - "pretty_type": "Post", - "preview": "

Slack lets you add your files into a channel to get feedback from your team. Anyone who can see the document can add comments, and all those comments stay with the file. You can add your files into additional channels with the share command, and all comments and changes will flow back to those channels.

Just drag and drop the file into the Slack web app or click + File to the left of the chat input. You will be prompted to add a title, select a channel or individual, and will also have an option to make the file private for your eyes only. You can also add files from the mobile app using the + button in the Files tab.

Sharing a file with an individual in a direct message won't make that file public if it wasn't already; you can share private files on an individual basis and otherwise keep them private.

All the text in your file is indexed and searchable. Even PDFs.

", - "public_url_shared": false, - "size": 1141, - "state": "locked", - "timestamp": 1484993283, - "title": "Uploading Your Files Into Slack", - "updated": 1484993283, - "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU96G/uploading_your_files_into_slack", - "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU96G/download/uploading_your_files_into_slack", - "user": "USLACKBOT", - "username": "" - } - ], - "paging": { - "count": 100, - "page": 1, - "pages": 1, - "total": 4 + "files": [ + { + "channels": [], + "comments_count": 0, + "created": 1484993283, + "display_as_bot": false, + "editable": true, + "external_type": "", + "filetype": "space", + "groups": [], + "id": "F3UMJU94L", + "ims": [], + "is_external": false, + "is_public": true, + "mimetype": "text/plain", + "mode": "space", + "name": "Welcome_to_Slack", + "permalink": "https://slackobot.slack.com/files/slackbot/F3UMJU94L/welcome_to_slack", + "permalink_public": "https://slack-files.com/T3UMV2E2H-F3UMJU94L-37beaf2b00", + "pretty_type": "Post", + "preview": "

Slack is here to help make your working life simpler, more pleasant, and more productive.

", + "public_url_shared": false, + "size": 1887, + "state": "locked", + "timestamp": 1484993283, + "title": "Welcome to Slack!", + "updated": 1484993283, + "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU94L/welcome_to_slack", + "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU94L/download/welcome_to_slack", + "user": "USLACKBOT", + "username": "" + }, + { + "channels": [], + "comments_count": 0, + "created": 1484993283, + "display_as_bot": false, + "editable": true, + "external_type": "", + "filetype": "space", + "groups": [], + "id": "F3TTWND40", + "ims": [], + "is_external": false, + "is_public": true, + "mimetype": "text/plain", + "mode": "space", + "name": "Channels_Keep_Your_Conversations_Organized", + "permalink": "https://slackobot.slack.com/files/slackbot/F3TTWND40/channels_keep_your_conversations_organized", + "permalink_public": "https://slack-files.com/T3UMV2E2H-F3TTWND40-e6670a0e44", + "pretty_type": "Post", + "preview": "

Slack lets you join and create channels to keep your conversations organized and focused. Create channels for your functional teams, large projects, and topics of interest.

Find existing channels to join by clicking the more channels link below, or \"your channels\" above, your open channels. Or create a new one from those same menus (hint: you can also do the same thing by typing \"/open #name\" into the chat input).

When you join a channel, the entire message history is available to you. You can see who else is in that channel by clicking the members tab in the upper right corner of the message pane.

Remove a channel from your open channel list by typing /close in the chat input or from the channel's drop-down list. If you re-open the channel later, all the content will still be there, including messages exchanged when you didn't have the channel open.

You can view content from all your team's channels without joining the channel in the Message Archives. And when you search in Slack, the results show content from all channels, even if you are not in that one.

", + "public_url_shared": false, + "size": 1445, + "state": "locked", + "timestamp": 1484993283, + "title": "Channels Keep Your Conversations Organized", + "updated": 1484993283, + "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3TTWND40/channels_keep_your_conversations_organized", + "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3TTWND40/download/channels_keep_your_conversations_organized", + "user": "USLACKBOT", + "username": "" + }, + { + "channels": [], + "comments_count": 0, + "created": 1484993283, + "display_as_bot": false, + "editable": true, + "external_type": "", + "filetype": "space", + "groups": [], + "id": "F3V9V05SS", + "ims": [], + "is_external": false, + "is_public": true, + "mimetype": "text/plain", + "mode": "space", + "name": "Getting_Started_with_Posts", + "permalink": "https://slackobot.slack.com/files/slackbot/F3V9V05SS/getting_started_with_posts", + "permalink_public": "https://slack-files.com/T3UMV2E2H-F3V9V05SS-342113c685", + "pretty_type": "Post", + "preview": "

Hi! Welcome to Posts, Slack's built-in document editor. Posts are a great way to share long-form content — like project plans, or documentation — directly in Slack. So how does one use Posts? Well, let's get right to it:

Creating a new Post

You can create a new Post from the + button in the Slack message input.

Formatting text

Text formatting in Posts was designed for simplicity, with just the right formatting options to help you get your thoughts organized.

", + "public_url_shared": false, + "size": 3294, + "state": "locked", + "timestamp": 1484993283, + "title": "Getting Started with Posts", + "updated": 1484993283, + "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3V9V05SS/getting_started_with_posts", + "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3V9V05SS/download/getting_started_with_posts", + "user": "USLACKBOT", + "username": "" + }, + { + "channels": [], + "comments_count": 0, + "created": 1484993283, + "display_as_bot": false, + "editable": true, + "external_type": "", + "filetype": "space", + "groups": [], + "id": "F3UMJU96G", + "ims": [], + "is_external": false, + "is_public": true, + "mimetype": "text/plain", + "mode": "space", + "name": "Uploading_Your_Files_Into_Slack", + "permalink": "https://slackobot.slack.com/files/slackbot/F3UMJU96G/uploading_your_files_into_slack", + "permalink_public": "https://slack-files.com/T3UMV2E2H-F3UMJU96G-3d27c638d7", + "pretty_type": "Post", + "preview": "

Slack lets you add your files into a channel to get feedback from your team. Anyone who can see the document can add comments, and all those comments stay with the file. You can add your files into additional channels with the share command, and all comments and changes will flow back to those channels.

Just drag and drop the file into the Slack web app or click + File to the left of the chat input. You will be prompted to add a title, select a channel or individual, and will also have an option to make the file private for your eyes only. You can also add files from the mobile app using the + button in the Files tab.

Sharing a file with an individual in a direct message won't make that file public if it wasn't already; you can share private files on an individual basis and otherwise keep them private.

All the text in your file is indexed and searchable. Even PDFs.

", + "public_url_shared": false, + "size": 1141, + "state": "locked", + "timestamp": 1484993283, + "title": "Uploading Your Files Into Slack", + "updated": 1484993283, + "url_private": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU96G/uploading_your_files_into_slack", + "url_private_download": "https://files.slack.com/files-pri/T3UMV2E2H-F3UMJU96G/download/uploading_your_files_into_slack", + "user": "USLACKBOT", + "username": "" } + ], + "paging": { + "count": 100, + "page": 1, + "pages": 1, + "total": 4 + } } diff --git a/test/groups.json b/test/groups.json index ef854af..ba30f17 100644 --- a/test/groups.json +++ b/test/groups.json @@ -1,25 +1,25 @@ [ - { - "created": 1492937682, - "creator": "U3UMJU868", - "id": "G536YKXPE", - "is_archived": false, - "is_group": true, - "is_mpim": false, - "members": [ - "U3UMJU868" - ], - "name": "seekrit", - "name_normalized": "seekrit", - "purpose": { - "creator": "U3UMJU868", - "last_set": 1492937683, - "value": "Secret place for secretly secreting secrets" - }, - "topic": { - "creator": "", - "last_set": 0, - "value": "" - } + { + "created": 1492937682, + "creator": "U3UMJU868", + "id": "G536YKXPE", + "is_archived": false, + "is_group": true, + "is_mpim": false, + "members": [ + "U3UMJU868" + ], + "name": "seekrit", + "name_normalized": "seekrit", + "purpose": { + "creator": "U3UMJU868", + "last_set": 1492937683, + "value": "Secret place for secretly secreting secrets" + }, + "topic": { + "creator": "", + "last_set": 0, + "value": "" } + } ] diff --git a/test/ims.json b/test/ims.json index bb1122a..e05ca8a 100644 --- a/test/ims.json +++ b/test/ims.json @@ -1,18 +1,18 @@ [ - { - "created": 1484993283, - "id": "D3UMJU8VA", - "is_im": true, - "is_org_shared": false, - "is_user_deleted": false, - "user": "USLACKBOT" - }, - { - "created": 1484993283, - "id": "D3TUQB1PB", - "is_im": true, - "is_org_shared": false, - "is_user_deleted": false, - "user": "U3UMJU868" - } + { + "created": 1484993283, + "id": "D3UMJU8VA", + "is_im": true, + "is_org_shared": false, + "is_user_deleted": false, + "user": "USLACKBOT" + }, + { + "created": 1484993283, + "id": "D3TUQB1PB", + "is_im": true, + "is_org_shared": false, + "is_user_deleted": false, + "user": "U3UMJU868" + } ] diff --git a/test/random_history.json b/test/random_history.json index ffa5447..2071c01 100644 --- a/test/random_history.json +++ b/test/random_history.json @@ -1,31 +1,31 @@ { - "has_more": false, - "messages": [ - { - "subtype": "me_message", - "text": "thinks you are.", - "ts": "1492867855.021331", - "type": "message", - "user": "U3UMJU868" - }, - { - "text": "I am not a slackobot. Are you?", - "ts": "1492867843.020464", - "type": "message", - "user": "U3UMJU868" - }, - { - "text": "hello", - "ts": "1492867142.979902", - "type": "message", - "user": "U3UMJU868" - }, - { - "subtype": "channel_join", - "text": "<@U3UMJU868|jerith> has joined the channel", - "ts": "1484993283.000002", - "type": "message", - "user": "U3UMJU868" - } - ] + "has_more": false, + "messages": [ + { + "subtype": "me_message", + "text": "thinks you are.", + "ts": "1492867855.021331", + "type": "message", + "user": "U3UMJU868" + }, + { + "text": "I am not a slackobot. Are you?", + "ts": "1492867843.020464", + "type": "message", + "user": "U3UMJU868" + }, + { + "text": "hello", + "ts": "1492867142.979902", + "type": "message", + "user": "U3UMJU868" + }, + { + "subtype": "channel_join", + "text": "<@U3UMJU868|jerith> has joined the channel", + "ts": "1484993283.000002", + "type": "message", + "user": "U3UMJU868" + } + ] } diff --git a/test/seekrit_history.json b/test/seekrit_history.json index db3854d..c4a8e80 100644 --- a/test/seekrit_history.json +++ b/test/seekrit_history.json @@ -1,45 +1,45 @@ { - "has_more": false, - "messages": [ - { - "text": "Want an X.509 private key?", - "ts": "1492937859.690158", - "type": "message", - "user": "U3UMJU868" - }, - { - "subtype": "me_message", - "text": "lurks in a shadow.", - "ts": "1492937718.682784", - "type": "message", - "user": "U3UMJU868" - }, - { - "text": "Hey you, pssst.", - "ts": "1492937707.682226", - "type": "message", - "user": "U3UMJU868" - }, - { - "text": "Pssssst.", - "ts": "1492937698.681778", - "type": "message", - "user": "U3UMJU868" - }, - { - "purpose": "Secret place for secretly secreting secrets", - "subtype": "group_purpose", - "text": "<@U3UMJU868|jerith> set the channel's purpose: Secret place for secretly secreting secrets", - "ts": "1492937683.680879", - "type": "message", - "user": "U3UMJU868" - }, - { - "subtype": "group_join", - "text": "<@U3UMJU868|jerith> has joined the group", - "ts": "1492937682.680791", - "type": "message", - "user": "U3UMJU868" - } - ] + "has_more": false, + "messages": [ + { + "text": "Want an X.509 private key?", + "ts": "1492937859.690158", + "type": "message", + "user": "U3UMJU868" + }, + { + "subtype": "me_message", + "text": "lurks in a shadow.", + "ts": "1492937718.682784", + "type": "message", + "user": "U3UMJU868" + }, + { + "text": "Hey you, pssst.", + "ts": "1492937707.682226", + "type": "message", + "user": "U3UMJU868" + }, + { + "text": "Pssssst.", + "ts": "1492937698.681778", + "type": "message", + "user": "U3UMJU868" + }, + { + "purpose": "Secret place for secretly secreting secrets", + "subtype": "group_purpose", + "text": "<@U3UMJU868|jerith> set the channel's purpose: Secret place for secretly secreting secrets", + "ts": "1492937683.680879", + "type": "message", + "user": "U3UMJU868" + }, + { + "subtype": "group_join", + "text": "<@U3UMJU868|jerith> has joined the group", + "ts": "1492937682.680791", + "type": "message", + "user": "U3UMJU868" + } + ] } diff --git a/test/slackbot_history.json b/test/slackbot_history.json index 281fedc..2201ed0 100644 --- a/test/slackbot_history.json +++ b/test/slackbot_history.json @@ -1,11 +1,11 @@ { - "has_more": false, - "messages": [ - { - "text": "If you have any questions about *how to use Slack*, please ask me! I’ll do my best to help.", - "ts": "1484993303.000002", - "type": "message", - "user": "USLACKBOT" - } - ] + "has_more": false, + "messages": [ + { + "text": "If you have any questions about *how to use Slack*, please ask me! I’ll do my best to help.", + "ts": "1484993303.000002", + "type": "message", + "user": "USLACKBOT" + } + ] } diff --git a/test/test_slacko.ml b/test/test_slacko.ml index 58d0854..2a7995d 100644 --- a/test/test_slacko.ml +++ b/test/test_slacko.ml @@ -1,10 +1,8 @@ open Lwt open OUnit2 - open Slounit open Abbrtypes - let token = try Sys.getenv "SLACKO_TEST_TOKEN" with Not_found -> Fake_slack.valid_token @@ -12,18 +10,19 @@ let badtoken = "badtoken" (* If we have a non-default token, assume we want to talk to real slack. If not, use our local fake instead. *) -let base_url = match token with +let base_url = + match token with | t when t = Fake_slack.valid_token -> Some "http://127.0.0.1:7357/api/" - | _ -> - print_endline ("NOTE: Because an API token has been provided, " ^ - "tests will run against the real slack API."); - try - (* We may want to talk to a proxy or a different fake slack. *) - let base_url = Sys.getenv "SLACKO_TEST_BASE_URL" in - print_endline @@ "NOTE: Overriding slack base URL to " ^ base_url; - Some base_url; - with Not_found -> None - + | _ -> ( + print_endline + ("NOTE: Because an API token has been provided, " + ^ "tests will run against the real slack API."); + try + (* We may want to talk to a proxy or a different fake slack. *) + let base_url = Sys.getenv "SLACKO_TEST_BASE_URL" in + print_endline @@ "NOTE: Overriding slack base URL to " ^ base_url; + Some base_url + with Not_found -> None) let abbr_json abbr_of_yojson json = match abbr_of_yojson json with @@ -34,19 +33,16 @@ let get_success = function | `Success obj -> obj | _ -> assert_failure "Unexpected failure." - (* api_test *) let test_api_test_nodata _tctx = Slacko.api_test ?base_url () >|= get_success >|= fun json -> - assert_equal ~printer:Yojson.Safe.to_string - (`Assoc []) - json + assert_equal ~printer:Yojson.Safe.to_string (`Assoc []) json let test_api_test_foo _tctx = Slacko.api_test ?base_url ~foo:"hello" () >|= get_success >|= fun json -> assert_equal ~printer:Yojson.Safe.to_string - (`Assoc ["args", `Assoc ["foo", `String "hello"]]) + (`Assoc [ ("args", `Assoc [ ("foo", `String "hello") ]) ]) json let test_api_test_err _tctx = @@ -57,32 +53,34 @@ let test_api_test_err_foo _tctx = Slacko.api_test ?base_url ~foo:"goodbye" ~error:"badthing" () >|= fun resp -> assert_equal (`Unhandled_error "badthing") resp -let api_test_tests = fake_slack_tests "api_test" [ - "test_nodata", test_api_test_nodata; - "test_foo", test_api_test_foo; - "test_err", test_api_test_err; - "test_err_foo", test_api_test_err_foo; -] +let api_test_tests = + fake_slack_tests "api_test" + [ + ("test_nodata", test_api_test_nodata); + ("test_foo", test_api_test_foo); + ("test_err", test_api_test_err); + ("test_err_foo", test_api_test_err_foo); + ] (* auth_test *) let test_auth_test_valid _tctx = let session = Slacko.start_session ?base_url token in - Slacko.auth_test session >|= get_success >|= - abbr_authed_obj >|= fun authed -> + Slacko.auth_test session >|= get_success >|= abbr_authed_obj >|= fun authed -> assert_equal ~printer:show_abbr_authed_obj (abbr_json abbr_authed_obj_of_yojson Fake_slack.authed_json) authed let test_auth_test_invalid _tctx = let session = Slacko.start_session ?base_url badtoken in - Slacko.auth_test session >|= fun resp -> - assert_equal `Invalid_auth resp + Slacko.auth_test session >|= fun resp -> assert_equal `Invalid_auth resp -let auth_test_tests = fake_slack_tests "test_auth" [ - "test_valid", test_auth_test_valid; - "test_invalid", test_auth_test_invalid; -] +let auth_test_tests = + fake_slack_tests "test_auth" + [ + ("test_valid", test_auth_test_valid); + ("test_invalid", test_auth_test_invalid); + ] (* channels_archive *) @@ -116,13 +114,15 @@ let test_channels_archive_general _tctx = Slacko.channels_archive session general >|= fun resp -> assert_equal `Cant_archive_general resp -let channels_archive_tests = fake_slack_tests "channels_archive" [ - "test_bad_auth", test_channels_archive_bad_auth; - "test_existing", test_channels_archive_existing; - "test_missing", test_channels_archive_missing; - "test_archived", test_channels_archive_archived; - "test_general", test_channels_archive_general; -] +let channels_archive_tests = + fake_slack_tests "channels_archive" + [ + ("test_bad_auth", test_channels_archive_bad_auth); + ("test_existing", test_channels_archive_existing); + ("test_missing", test_channels_archive_missing); + ("test_archived", test_channels_archive_archived); + ("test_general", test_channels_archive_general); + ] (* channels_create *) @@ -133,8 +133,9 @@ let test_channels_create_bad_auth _tctx = let test_channels_create_new _tctx = let session = Slacko.start_session ?base_url token in - Slacko.channels_create session "new_channel" >|= get_success >|= - abbr_channel_obj >|= fun channel -> + Slacko.channels_create session "new_channel" + >|= get_success >|= abbr_channel_obj + >|= fun channel -> assert_equal ~printer:show_abbr_channel_obj (abbr_json abbr_channel_obj_of_yojson Fake_slack.new_channel_json) channel @@ -144,11 +145,13 @@ let test_channels_create_existing _tctx = Slacko.channels_create session "general" >|= fun resp -> assert_equal `Name_taken resp -let channels_create_tests = fake_slack_tests "channels_create" [ - "test_bad_auth", test_channels_create_bad_auth; - "test_new", test_channels_create_new; - "test_existing", test_channels_create_existing; -] +let channels_create_tests = + fake_slack_tests "channels_create" + [ + ("test_bad_auth", test_channels_create_bad_auth); + ("test_new", test_channels_create_new); + ("test_existing", test_channels_create_existing); + ] (* channels_history *) @@ -166,10 +169,12 @@ let test_channels_history_no_params _tctx = (abbr_json abbr_history_obj_of_yojson Fake_slack.random_history_json) (abbr_history_obj history) -let channels_history_tests = fake_slack_tests "channels_history" [ - "test_bad_auth", test_channels_history_bad_auth; - "test_no_params", test_channels_history_no_params; -] +let channels_history_tests = + fake_slack_tests "channels_history" + [ + ("test_bad_auth", test_channels_history_bad_auth); + ("test_no_params", test_channels_history_no_params); + ] (* channels_info *) (* channels_invite *) @@ -191,13 +196,16 @@ let test_conversations_list _tctx = >|= List.map abbr_conversation_obj >|= fun conversations -> assert_equal ~printer:show_abbr_conversation_obj_list - (abbr_json abbr_conversation_obj_list_of_yojson Fake_slack.conversations_json) + (abbr_json abbr_conversation_obj_list_of_yojson + Fake_slack.conversations_json) conversations -let conversations_list_tests = fake_slack_tests "conversations_list" [ - "test_bad_auth", test_conversations_list_bad_auth; - "test", test_conversations_list; -] +let conversations_list_tests = + fake_slack_tests "conversations_list" + [ + ("test_bad_auth", test_conversations_list_bad_auth); + ("test", test_conversations_list); + ] (* channels_mark *) (* channels_rename *) @@ -215,21 +223,19 @@ let conversations_list_tests = fake_slack_tests "conversations_list" [ let test_files_list_bad_auth _tctx = let session = Slacko.start_session ?base_url badtoken in - Slacko.files_list session >|= fun resp -> - assert_equal `Invalid_auth resp + Slacko.files_list session >|= fun resp -> assert_equal `Invalid_auth resp let test_files_list _tctx = let session = Slacko.start_session ?base_url token in - Slacko.files_list session >|= get_success >|= - abbr_files_list_obj >|= fun files -> + Slacko.files_list session >|= get_success >|= abbr_files_list_obj + >|= fun files -> assert_equal ~printer:show_abbr_files_list_obj (abbr_json abbr_files_list_obj_of_yojson Fake_slack.files_json) files -let files_list_tests = fake_slack_tests "files_list" [ - "test_bad_auth", test_files_list_bad_auth; - "test", test_files_list; -] +let files_list_tests = + fake_slack_tests "files_list" + [ ("test_bad_auth", test_files_list_bad_auth); ("test", test_files_list) ] (* files_upload *) (* groups_archive *) @@ -253,10 +259,12 @@ let test_groups_history_no_params _tctx = (abbr_json abbr_history_obj_of_yojson Fake_slack.seekrit_history_json) (abbr_history_obj history) -let groups_history_tests = fake_slack_tests "groups_history" [ - "test_bad_auth", test_groups_history_bad_auth; - "test_no_params", test_groups_history_no_params; -] +let groups_history_tests = + fake_slack_tests "groups_history" + [ + ("test_bad_auth", test_groups_history_bad_auth); + ("test_no_params", test_groups_history_no_params); + ] (* groups_invite *) (* groups_kick *) @@ -266,21 +274,19 @@ let groups_history_tests = fake_slack_tests "groups_history" [ let test_groups_list_bad_auth _tctx = let session = Slacko.start_session ?base_url badtoken in - Slacko.groups_list session >|= fun resp -> - assert_equal `Invalid_auth resp + Slacko.groups_list session >|= fun resp -> assert_equal `Invalid_auth resp let test_groups_list _tctx = let session = Slacko.start_session ?base_url token in - Slacko.groups_list session >|= get_success >|= - List.map abbr_group_obj >|= fun groups -> + Slacko.groups_list session >|= get_success >|= List.map abbr_group_obj + >|= fun groups -> assert_equal ~printer:show_abbr_group_obj_list (abbr_json abbr_group_obj_list_of_yojson Fake_slack.groups_json) groups -let groups_list_tests = fake_slack_tests "groups_list" [ - "test_bad_auth", test_groups_list_bad_auth; - "test", test_groups_list; -] +let groups_list_tests = + fake_slack_tests "groups_list" + [ ("test_bad_auth", test_groups_list_bad_auth); ("test", test_groups_list) ] (* groups_mark *) (* groups_open *) @@ -306,30 +312,29 @@ let test_im_history_no_params _tctx = (abbr_json abbr_history_obj_of_yojson Fake_slack.slackbot_history_json) (abbr_history_obj history) -let im_history_tests = fake_slack_tests "im_history" [ - "test_bad_auth", test_im_history_bad_auth; - "test_no_params", test_im_history_no_params; -] +let im_history_tests = + fake_slack_tests "im_history" + [ + ("test_bad_auth", test_im_history_bad_auth); + ("test_no_params", test_im_history_no_params); + ] (* im_list *) let test_im_list_bad_auth _tctx = let session = Slacko.start_session ?base_url badtoken in - Slacko.im_list session >|= fun resp -> - assert_equal `Invalid_auth resp + Slacko.im_list session >|= fun resp -> assert_equal `Invalid_auth resp let test_im_list _tctx = let session = Slacko.start_session ?base_url token in - Slacko.im_list session >|= get_success >|= - List.map abbr_im_obj >|= fun ims -> + Slacko.im_list session >|= get_success >|= List.map abbr_im_obj >|= fun ims -> assert_equal ~printer:show_abbr_im_obj_list (abbr_json abbr_im_obj_list_of_yojson Fake_slack.ims_json) ims -let im_list_tests = fake_slack_tests "im_list" [ - "test_bad_auth", test_im_list_bad_auth; - "test", test_im_list; -] +let im_list_tests = + fake_slack_tests "im_list" + [ ("test_bad_auth", test_im_list_bad_auth); ("test", test_im_list) ] (* im_mark *) (* im_open *) @@ -347,85 +352,84 @@ let im_list_tests = fake_slack_tests "im_list" [ let test_users_list_bad_auth _tctx = let session = Slacko.start_session ?base_url badtoken in - Slacko.users_list session >|= fun resp -> - assert_equal `Invalid_auth resp + Slacko.users_list session >|= fun resp -> assert_equal `Invalid_auth resp let test_users_list _tctx = let session = Slacko.start_session ?base_url token in - Slacko.users_list session >|= get_success >|= - List.map abbr_user_obj >|= fun users -> + Slacko.users_list session >|= get_success >|= List.map abbr_user_obj + >|= fun users -> assert_equal ~printer:show_abbr_user_obj_list (abbr_json abbr_user_obj_list_of_yojson Fake_slack.users_json) users -let users_list_tests = fake_slack_tests "users_list" [ - "test_bad_auth", test_users_list_bad_auth; - "test", test_users_list; -] +let users_list_tests = + fake_slack_tests "users_list" + [ ("test_bad_auth", test_users_list_bad_auth); ("test", test_users_list) ] (* users_set_active *) (* users_set_presence *) (* Gotta run them all! *) -let suite = "tests" >::: [ - api_test_tests; - auth_test_tests; - channels_archive_tests; - channels_create_tests; - channels_history_tests; - (* channels_info_tests; *) - (* channels_invite_tests; *) - (* channels_join_tests; *) - (* channels_kick_tests; *) - (* channels_leave_tests; *) - conversations_list_tests; - (* channels_mark_tests; *) - (* channels_rename_tests; *) - (* channels_set_purpose_tests; *) - (* channels_set_topic_tests; *) - (* channels_unarchive_tests; *) - (* chat_delete_tests; *) - (* chat_post_message_tests; *) - (* chat_update_tests; *) - (* emoji_list_tests; *) - (* files_delete_tests; *) - (* files_info_tests; *) - files_list_tests; - (* files_upload_tests; *) - (* groups_archive_tests; *) - (* groups_close_tests; *) - (* groups_create_tests; *) - (* groups_create_child_tests; *) - groups_history_tests; - (* groups_invite_tests; *) - (* groups_kick_tests; *) - (* groups_leave_tests; *) - groups_list_tests; - (* groups_mark_tests; *) - (* groups_open_tests; *) - (* groups_rename_tests; *) - (* groups_set_purpose_tests; *) - (* groups_set_topic_tests; *) - (* groups_unarchive_tests; *) - (* im_close_tests; *) - im_history_tests; - im_list_tests; - (* im_mark_tests; *) - (* im_open_tests; *) - (* oauth_access_tests; *) - (* search_all_tests; *) - (* search_files_tests; *) - (* search_messages_tests; *) - (* stars_list_tests; *) - (* team_access_logs_tests; *) - (* team_info_tests; *) - (* users_get_presence_tests; *) - (* users_info_tests; *) - users_list_tests; - (* users_set_active_tests; *) - (* users_set_presence_tests; *) - ] - +let suite = + "tests" + >::: [ + api_test_tests; + auth_test_tests; + channels_archive_tests; + channels_create_tests; + channels_history_tests; + (* channels_info_tests; *) + (* channels_invite_tests; *) + (* channels_join_tests; *) + (* channels_kick_tests; *) + (* channels_leave_tests; *) + conversations_list_tests; + (* channels_mark_tests; *) + (* channels_rename_tests; *) + (* channels_set_purpose_tests; *) + (* channels_set_topic_tests; *) + (* channels_unarchive_tests; *) + (* chat_delete_tests; *) + (* chat_post_message_tests; *) + (* chat_update_tests; *) + (* emoji_list_tests; *) + (* files_delete_tests; *) + (* files_info_tests; *) + files_list_tests; + (* files_upload_tests; *) + (* groups_archive_tests; *) + (* groups_close_tests; *) + (* groups_create_tests; *) + (* groups_create_child_tests; *) + groups_history_tests; + (* groups_invite_tests; *) + (* groups_kick_tests; *) + (* groups_leave_tests; *) + groups_list_tests; + (* groups_mark_tests; *) + (* groups_open_tests; *) + (* groups_rename_tests; *) + (* groups_set_purpose_tests; *) + (* groups_set_topic_tests; *) + (* groups_unarchive_tests; *) + (* im_close_tests; *) + im_history_tests; + im_list_tests; + (* im_mark_tests; *) + (* im_open_tests; *) + (* oauth_access_tests; *) + (* search_all_tests; *) + (* search_files_tests; *) + (* search_messages_tests; *) + (* stars_list_tests; *) + (* team_access_logs_tests; *) + (* team_info_tests; *) + (* users_get_presence_tests; *) + (* users_info_tests; *) + users_list_tests; + (* users_set_active_tests; *) + (* users_set_presence_tests; *) + ] let () = run_test_tt_main suite diff --git a/test/users.json b/test/users.json index a53272d..da2dd8b 100644 --- a/test/users.json +++ b/test/users.json @@ -1,73 +1,73 @@ { - "cache_ts": 1492880206, - "members": [ - { - "color": "9f69e7", - "deleted": false, - "has_2fa": false, - "id": "U3UMJU868", - "is_admin": true, - "is_bot": false, - "is_owner": true, - "is_primary_owner": true, - "is_restricted": false, - "is_ultra_restricted": false, - "name": "jerith", - "profile": { - "avatar_hash": "gb7842ee0df3", - "email": "firxen@gmail.com", - "first_name": "Jeremy", - "image_192": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0011-192.png", - "image_24": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-24.png", - "image_32": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-32.png", - "image_48": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-48.png", - "image_512": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0011-512.png", - "image_72": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F3654%2Fimg%2Favatars%2Fava_0011-72.png", - "last_name": "Thurgood", - "real_name": "Jeremy Thurgood", - "real_name_normalized": "Jeremy Thurgood" - }, - "real_name": "Jeremy Thurgood", - "status": null, - "team_id": "T3UMV2E2H", - "tz": "Africa/Harare", - "tz_label": "Central Africa Time", - "tz_offset": 7200, - "updated": 1484993283 - }, - { - "color": "757575", - "deleted": false, - "id": "USLACKBOT", - "is_admin": false, - "is_bot": false, - "is_owner": false, - "is_primary_owner": false, - "is_restricted": false, - "is_ultra_restricted": false, - "name": "slackbot", - "profile": { - "always_active": true, - "avatar_hash": "sv1444671949", - "fields": null, - "first_name": "slackbot", - "image_192": "https://a.slack-edge.com/66f9/img/slackbot_192.png", - "image_24": "https://a.slack-edge.com/0180/img/slackbot_24.png", - "image_32": "https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_32.png", - "image_48": "https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_48.png", - "image_512": "https://a.slack-edge.com/1801/img/slackbot_512.png", - "image_72": "https://a.slack-edge.com/0180/img/slackbot_72.png", - "last_name": "", - "real_name": "slackbot", - "real_name_normalized": "slackbot" - }, - "real_name": "slackbot", - "status": null, - "team_id": "T3UMV2E2H", - "tz": null, - "tz_label": "Pacific Daylight Time", - "tz_offset": -25200, - "updated": 0 - } - ] + "cache_ts": 1492880206, + "members": [ + { + "color": "9f69e7", + "deleted": false, + "has_2fa": false, + "id": "U3UMJU868", + "is_admin": true, + "is_bot": false, + "is_owner": true, + "is_primary_owner": true, + "is_restricted": false, + "is_ultra_restricted": false, + "name": "jerith", + "profile": { + "avatar_hash": "gb7842ee0df3", + "email": "firxen@gmail.com", + "first_name": "Jeremy", + "image_192": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0011-192.png", + "image_24": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-24.png", + "image_32": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-32.png", + "image_48": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2F66f9%2Fimg%2Favatars%2Fava_0011-48.png", + "image_512": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F7fa9%2Fimg%2Favatars%2Fava_0011-512.png", + "image_72": "https://secure.gravatar.com/avatar/b7842ee0df3b024a6cad55bbe09c0d08.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2F3654%2Fimg%2Favatars%2Fava_0011-72.png", + "last_name": "Thurgood", + "real_name": "Jeremy Thurgood", + "real_name_normalized": "Jeremy Thurgood" + }, + "real_name": "Jeremy Thurgood", + "status": null, + "team_id": "T3UMV2E2H", + "tz": "Africa/Harare", + "tz_label": "Central Africa Time", + "tz_offset": 7200, + "updated": 1484993283 + }, + { + "color": "757575", + "deleted": false, + "id": "USLACKBOT", + "is_admin": false, + "is_bot": false, + "is_owner": false, + "is_primary_owner": false, + "is_restricted": false, + "is_ultra_restricted": false, + "name": "slackbot", + "profile": { + "always_active": true, + "avatar_hash": "sv1444671949", + "fields": null, + "first_name": "slackbot", + "image_192": "https://a.slack-edge.com/66f9/img/slackbot_192.png", + "image_24": "https://a.slack-edge.com/0180/img/slackbot_24.png", + "image_32": "https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_32.png", + "image_48": "https://a.slack-edge.com/2fac/plugins/slackbot/assets/service_48.png", + "image_512": "https://a.slack-edge.com/1801/img/slackbot_512.png", + "image_72": "https://a.slack-edge.com/0180/img/slackbot_72.png", + "last_name": "", + "real_name": "slackbot", + "real_name_normalized": "slackbot" + }, + "real_name": "slackbot", + "status": null, + "team_id": "T3UMV2E2H", + "tz": null, + "tz_label": "Pacific Daylight Time", + "tz_offset": -25200, + "updated": 0 + } + ] }