diff --git a/drivers/cisco/webex/api/messages.cr b/drivers/cisco/webex/api/messages.cr deleted file mode 100644 index 24bfa2edc2c..00000000000 --- a/drivers/cisco/webex/api/messages.cr +++ /dev/null @@ -1,41 +0,0 @@ -module Cisco - module Webex - module Api - class Messages - def initialize(@session : Session) - end - - def list(room_id : String, parent_id : String = "", mentioned_people : String = "", before : String = "", before_message : String = "", max : Int32 = 50) : Array(Models::Message) - params = Utils.hash_from_items_with_values(roomId: room_id, parentId: parent_id, mentionedPeople: mentioned_people, before: before, beforeMessage: before_message, max: max) - response = @session.get([Constants::MESSAGES_ENDPOINT, "/"].join(""), params: params) - data = JSON.parse(response.body) - - data.["items"].as_a.map do |item| - Models::Message.from_json(item.to_json) - end - end - - def list_direct(person_id : String = "", person_email : String = "", parent_id : String = "") : Array(Models::Message) - params = Utils.hash_from_items_with_values(personId: person_id, personEmail: person_email, parentId: parent_id) - response = @session.get([Constants::MESSAGES_ENDPOINT, "/"].join(""), params: params) - data = JSON.parse(response.body) - - data.["items"].as_a.map do |item| - Models::Message.from_json(item.to_json) - end - end - - def create(room_id : String = "", parent_id : String = "", to_person_id : String = "", to_person_email : String = "", text : String = "", markdown : String = "") : Models::Message - json = Utils.hash_from_items_with_values(roomId: room_id, parentId: parent_id, toPersonId: to_person_id, toPersonEmail: to_person_email, text: text, markdown: markdown) - response = @session.post([Constants::MESSAGES_ENDPOINT, "/"].join(""), json: json) - Models::Message.from_json(response.body) - end - - def get(message_id : String) : Models::Message - response = @session.get([Constants::MESSAGES_ENDPOINT, "/", message_id].join("")) - Models::Message.from_json(response.body) - end - end - end - end -end diff --git a/drivers/cisco/webex/api/people.cr b/drivers/cisco/webex/api/people.cr deleted file mode 100644 index 500cd455c05..00000000000 --- a/drivers/cisco/webex/api/people.cr +++ /dev/null @@ -1,15 +0,0 @@ -module Cisco - module Webex - module Api - class People - def initialize(@session : Session) - end - - def me : Models::Person - response = @session.get([Constants::PEOPLE_ENDPOINT, "/", "me"].join("")) - Models::Person.from_json(response.body) - end - end - end - end -end diff --git a/drivers/cisco/webex/api/rooms.cr b/drivers/cisco/webex/api/rooms.cr deleted file mode 100644 index bf80133d252..00000000000 --- a/drivers/cisco/webex/api/rooms.cr +++ /dev/null @@ -1,41 +0,0 @@ -module Cisco - module Webex - module Api - class Rooms - def initialize(@session : Session) - end - - def list(room_id : String, parent_id : String = "", mentioned_people : String = "", before : String = "", before_message : String = "", max : Int32 = 50) : Array(Models::Message) - params = Utils.hash_from_items_with_values(roomId: room_id, parentId: parent_id, mentionedPeople: mentioned_people, before: before, beforeMessage: before_message, max: max) - response = @session.get([Constants::MESSAGES_ENDPOINT, "/"].join(""), params: params) - data = JSON.parse(response.body) - - data.["items"].as_a.map do |item| - Models::Message.from_json(item.to_json) - end - end - - def list_direct(person_id : String = "", person_email : String = "", parent_id : String = "") : Array(Models::Message) - params = Utils.hash_from_items_with_values(personId: person_id, personEmail: person_email, parentId: parent_id) - response = @session.get([Constants::MESSAGES_ENDPOINT, "/"].join(""), params: params) - data = JSON.parse(response.body) - - data.["items"].as_a.map do |item| - Models::Message.from_json(item.to_json) - end - end - - def create(room_id : String = "", parent_id : String = "", to_person_id : String = "", to_person_email : String = "", text : String = "", markdown : String = "") : Models::Message - json = Utils.hash_from_items_with_values(roomId: room_id, parentId: parent_id, toPersonId: to_person_id, toPersonEmail: to_person_email, text: text, markdown: markdown) - response = @session.post([Constants::MESSAGES_ENDPOINT, "/"].join(""), json: json) - Models::Message.from_json(response.body) - end - - def get(message_id : String) : Models::Message - response = @session.get([Constants::MESSAGES_ENDPOINT, "/", message_id].join("")) - Models::Message.from_json(response.body) - end - end - end - end -end diff --git a/drivers/cisco/webex/booking.cr b/drivers/cisco/webex/booking.cr new file mode 100644 index 00000000000..0754eea1c20 --- /dev/null +++ b/drivers/cisco/webex/booking.cr @@ -0,0 +1,69 @@ +require "placeos-driver" +require "placeos-driver/interface/chat_bot" +require "placeos-driver/interface/locatable" +require "place_calendar" + +module Cisco + module Webex + class Booking < PlaceOS::Driver + default_settings({keyword: "book", organization_id: ""}) + + def on_load + on_update + end + + def on_update + organization_id = setting(String, :organization_id) + monitor("chat/webex/#{organization_id}/message") { |_subscription, payload| on_message(payload) } + end + + def on_message(message : String) + message = Interface::ChatBot::Message.from_json(message) + + keyword = message.text.split.first.downcase + + # An example message text would look something like this: + # {% keyword %} a room for 30 minutes + text = message + .text + .sub(keyword, "") + .sub("a room", "") + .strip + + # Ignore the message if the keyword doesn't match the booking keyword specified in the settings + if keyword != setting(String, :keyword) + send_message(message.id, "Specified keyword is not recognized as a valid acommand for the PlaceOS Bot, #{keyword}.") + send_message(message.id, "An example booking command would look something like this: #{setting(String, :keyword)} a room for 30 minutes") + + return + end + + # Notify the user to await for a free room + send_message(message.id, "Looking for an available room to book, please wait!") + + # Split the remaining text into chunks to process them + conjunction, period, measurement = text.split + + case measurement + when "hours" + period_in_seconds = (period.to_i * 3600).to_i64 + event = PlaceCalendar::Event.from_json(system.implementing(Interface::Locatable).book_now(period_in_seconds).get.first.to_json) + send_message(message.id, "Successfully booked an event #{event.title}, from #{event.event_start}, to #{event.event_end}, in #{event.timezone}, on #{event.host}.") + when "minutes" + period_in_seconds = (period.to_i * 60).to_i64 + event = PlaceCalendar::Event.from_json(system.implementing(Interface::Locatable).book_now(period_in_seconds).get.first.to_json) + send_message(message.id, "Successfully booked an event #{event.title}, from #{event.event_start}, to #{event.event_end}, in #{event.timezone}, on #{event.host}.") + when "seconds" + event = PlaceCalendar::Event.from_json(system.implementing(Interface::Locatable).book_now(period.to_i64).get.first.to_json) + send_message(message.id, "Successfully booked an event #{event.title}, from #{event.event_start}, to #{event.event_end}, in #{event.timezone}, on #{event.host}.") + else + send_message(message.id, "Specified measurement is not recognized as a valid measurement, please use: minutes, seconds or hours.") + end + end + + private def send_message(id : Interface::ChatBot::Id, response : String) + system.implementing(Interface::ChatBot).reply(Interface::ChatBot::Message.new(id, response).to_json) + end + end + end +end diff --git a/drivers/cisco/webex/client.cr b/drivers/cisco/webex/client.cr deleted file mode 100644 index 8d5dc1aed32..00000000000 --- a/drivers/cisco/webex/client.cr +++ /dev/null @@ -1,153 +0,0 @@ -module Cisco - module Webex - class Client - Log = ::Log.for(self) - - property id : String - property keywords : Hash(String, Command) - property socket : HTTP::WebSocket? - - def initialize(@name : String, @access_token : String, @emails : String, @session : Session, @commands : Array(Command)) - @rooms = Api::Rooms.new(@session) - @people = Api::People.new(@session) - @messages = Api::Messages.new(@session) - - @keywords = - @commands - .flat_map { |command| command.keywords.map { |keyword| {"#{keyword}" => command} } } - .reduce { |acc, i| acc.try(&.merge(i.not_nil!)) } - - @id = @people.me.id - end - - def rooms - @rooms - end - - def people - @people - end - - def messages - @messages - end - - private def device(check_existing : Bool = true) : Models::Device - if check_existing - response = @session.get([Constants::DEFAULT_DEVICE_URL, "/", "devices"].join("")) - data = JSON.parse(response.body) - - devices = data.["devices"].as_a.map do |item| - Models::Device.from_json(item.to_json) - end - - devices.each do |device| - if device.name == nil - next - end - - if device.name == Constants::DEVICE["name"] - return device - end - end - end - - response = @session.post([Constants::DEFAULT_DEVICE_URL, "/", "devices"].join(""), json: Constants::DEVICE) - Models::Device.from_json(response.body) - end - - private def message_id(activity) : String - # In order to geo-locate the correct DC to fetch the message from, you need to use the base64 Id of the message. - id = activity.id - target_url = activity.target.url - target_id = activity.target.id - - verb = activity.verb == "post" ? "messages" : "attachment/actions" - - message_url = target_url.gsub(["conversations", "/", target_id].join(""), [verb, "/", id].join("")) - response = Halite.get(message_url, headers: {"Authorization" => ["Bearer", @access_token].join(" ")}) - - message = JSON.parse(response.body) - message["id"].to_s - end - - private def process_incoming_websocket_message(socket, message) - peek = Models::Peek.from_json(message) - return if peek.data.event_type == "status.start_typing" - - begin - event = Models::Event.from_json(message) - - if event.data.event_type == "conversation.activity" - activity = event.data.activity - Log.debug { "Activity verb is: #{activity.verb}" } - - if activity.verb == "post" - id = message_id(activity) - message = self.messages.get(id) - - if message.person_id != @id - # Ack that this message has been processed. This will prevent the message coming again. - socket.send({"type" => "ack", "messageId" => id}.to_json) - - if message.text.starts_with?(@name) - message.text = message.text.sub(@name, "").strip - end - - return if @emails.none?(activity.actor.email) - - keyword = message.text.split.first.downcase - - if @keywords[keyword]? - message.text = message.text.sub(keyword, "").strip - message = @keywords[keyword].execute(event, keyword, message) - - room_id = message["id"]? || "" - parent_id = message["parent_id"]? || "" - to_person_id = message["to_person_id"]? || "" - to_person_email = message["to_person_email"]? || "" - text = message["text"]? || "" - markdown = message["markdown"]? || "" - - self.messages.create(room_id, parent_id, to_person_id, to_person_email, text, markdown) - else - end - end - else - end - end - rescue e : Exception - Log.debug(exception: e) { } - end - end - - def run : Void - device = device() - @socket = socket = HTTP::WebSocket.new(URI.parse(device.websocket_url)) - - socket.on_message do |message| - process_incoming_websocket_message(socket, message) - end - - socket.on_binary do |binary| - process_incoming_websocket_message(socket, String.new(binary)) - end - - message = { - "id" => UUID.random.to_s, - "type" => "authorization", - "trackingId" => ["webex", "-", UUID.random.to_s].join(""), - "data" => { - "token" => ["Bearer", @access_token].join(" "), - }, - } - socket.send(message.to_json) - socket.run - end - - def stop : Void - @socket.close - end - end - end -end diff --git a/drivers/cisco/webex/command.cr b/drivers/cisco/webex/command.cr deleted file mode 100644 index 4fdac03b1d3..00000000000 --- a/drivers/cisco/webex/command.cr +++ /dev/null @@ -1,9 +0,0 @@ -module Cisco - module Webex - abstract class Command - abstract def keywords : Array(String) - abstract def description : String - abstract def execute(event, keyword, message) - end - end -end diff --git a/drivers/cisco/webex/commands/echo.cr b/drivers/cisco/webex/commands/echo.cr deleted file mode 100644 index 5014b58595a..00000000000 --- a/drivers/cisco/webex/commands/echo.cr +++ /dev/null @@ -1,19 +0,0 @@ -module Cisco - module Webex - module Commands - class Echo < Command - def keywords : Array(String) - ["echo"] - end - - def description : String - "This command simply replies your message!" - end - - def execute(_event, _keyword, message) - {"id" => message.room_id, "text" => message.text} - end - end - end - end -end diff --git a/drivers/cisco/webex/commands/greeting.cr b/drivers/cisco/webex/commands/greeting.cr deleted file mode 100644 index 13843de0810..00000000000 --- a/drivers/cisco/webex/commands/greeting.cr +++ /dev/null @@ -1,19 +0,0 @@ -module Cisco - module Webex - module Commands - class Greeting < Command - def keywords : Array(String) - ["hello", "hi"] - end - - def description : String - "This command simply responds to hello, hi, how are you, etc." - end - - def execute(_event, _keyword, message) - {"id" => message.room_id, "text" => "👋"} - end - end - end - end -end diff --git a/drivers/cisco/webex/communication.cr b/drivers/cisco/webex/communication.cr new file mode 100644 index 00000000000..8f47af61bdd --- /dev/null +++ b/drivers/cisco/webex/communication.cr @@ -0,0 +1,61 @@ +require "placeos-driver" +require "placeos-driver/interface/chat_bot" +require "http" + +require "./models/**" + +module Cisco + module Webex + class Communication < PlaceOS::Driver + include Interface::ChatBot + + descriptive_name "Cisco Webex Bot Communication" + generic_name :Communication + uri_base "wss://webex.placeos.com/ws/messages" + + default_settings({ + organization_id: "", + api_key: "", + }) + + protected getter! socket : HTTP::WebSocket + + def on_load + on_update + end + + def on_update + headers = HTTP::Headers.new + + organization_id = setting(String, :organization_id) + + headers.merge!({"Organization-ID" => organization_id}) + headers.merge!({"X-API-Key" => setting(String, :api_key)}) + + @socket = HTTP::WebSocket.new(URI.parse(config.uri.not_nil!.to_s), headers) + + spawn do + socket.try(&.on_message do |message| + event = Models::Event.from_json(JSON.parse(message).as_h.["event"].to_json) + event_message = Models::Message.from_json(JSON.parse(message).as_h.["message"].to_json) + + id = Interface::ChatBot::Id.new(event_message.id.to_s, event_message.room_id.to_s, event.data.activity.actor.id, event.data.activity.actor.organization_id) + bot_message = Interface::ChatBot::Message.new(id, event_message.text.to_s) + + publish("chat/webex/#{organization_id}/message", bot_message.to_json) + end) + + socket.try(&.run) + end + end + + def notify_typing(id : Interface::ChatBot::Id) + end + + def reply(id : Interface::ChatBot::Id, response : String, url : String? = nil, attachment : Interface::ChatBot::Attachment? = nil) + files = [url.to_s] if url + socket.try(&.send({"roomId" => id.room_id.to_s, "text" => response, "files" => files || [] of String}.to_json)) + end + end + end +end diff --git a/drivers/cisco/webex/constants.cr b/drivers/cisco/webex/constants.cr deleted file mode 100644 index ef5605e0096..00000000000 --- a/drivers/cisco/webex/constants.cr +++ /dev/null @@ -1,45 +0,0 @@ -module Cisco - module Webex - module Constants - VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify.downcase }} - - STATUS_CODES = { - 200 => "Successful request with body content.", - 204 => "Successful request without body content.", - 400 => "The request was invalid or cannot be otherwise served.", - 401 => "Authentication credentials were missing or incorrect.", - 403 => "The request is understood, but it has been refused or access is not allowed.", - 404 => "The URI requested is invalid or the resource requested, such as a user, does not exist. Also returned when the requested format is not supported by the requested method.", - 405 => "The request was made to a resource using an HTTP request method that is not supported.", - 409 => "The request could not be processed because it conflicts with some established rule of the system. For example, a person may not be added to a room more than once.", - 410 => "The requested resource is no longer available.", - 415 => "The request was made to a resource without specifying a media type or used a media type that is not supported.", - 423 => "The requested resource is temporarily unavailable. A `Retry-After` header may be present that specifies how many seconds you need to wait before attempting the request again.", - 429 => "Too many requests have been sent in a given amount of time and the request has been rate limited. A `Retry-After` header should be present that specifies how many seconds you need to wait before a successful request can be made.", - 500 => "Something went wrong on the server. If the issue persists, feel free to contact the Webex Developer Support team (https://developer.webex.com/support).", - 502 => "The server received an invalid response from an upstream server while processing the request. Try again later.", - 503 => "Server is overloaded with requests. Try again later.", - } - - DEFAULT_BASE_URL = "https://webexapis.com/v1/" - DEFAULT_DEVICE_URL = "https://wdm-a.wbx2.com/wdm/api/v1/" - DEFAULT_SINGLE_REQUEST_TIMEOUT = 60 - DEFAULT_WAIT_ON_RATE_LIMIT = true - - DEVICE = { - "deviceType" => "DESKTOP", - "localizedModel" => "crystal", - "model" => "crystal", - "name" => UUID.random.to_s, - "systemName" => "webex-bot-client", - "systemVersion" => VERSION, - } - - ROOMS_ENDPOINT = "rooms" - PEOPLE_ENDPOINT = "people" - MESSAGES_ENDPOINT = "messages" - - WEBEX_TEAMS_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" - end - end -end diff --git a/drivers/cisco/webex/exceptions/argument.cr b/drivers/cisco/webex/exceptions/argument.cr deleted file mode 100644 index 4885bbce93f..00000000000 --- a/drivers/cisco/webex/exceptions/argument.cr +++ /dev/null @@ -1,8 +0,0 @@ -module Cisco - module Webex - module Exceptions - class Argument < Exception - end - end - end -end diff --git a/drivers/cisco/webex/exceptions/method.cr b/drivers/cisco/webex/exceptions/method.cr deleted file mode 100644 index 3daec0b5636..00000000000 --- a/drivers/cisco/webex/exceptions/method.cr +++ /dev/null @@ -1,8 +0,0 @@ -module Cisco - module Webex - module Exceptions - class Method < Exception - end - end - end -end diff --git a/drivers/cisco/webex/exceptions/rate_limit.cr b/drivers/cisco/webex/exceptions/rate_limit.cr deleted file mode 100644 index af22d61182f..00000000000 --- a/drivers/cisco/webex/exceptions/rate_limit.cr +++ /dev/null @@ -1,8 +0,0 @@ -module Cisco - module Webex - module Exceptions - class RateLimit < Exception - end - end - end -end diff --git a/drivers/cisco/webex/exceptions/status_code.cr b/drivers/cisco/webex/exceptions/status_code.cr deleted file mode 100644 index de113fad804..00000000000 --- a/drivers/cisco/webex/exceptions/status_code.cr +++ /dev/null @@ -1,8 +0,0 @@ -module Cisco - module Webex - module Exceptions - class StatusCode < Exception - end - end - end -end diff --git a/drivers/cisco/webex/extensions/chainable.cr b/drivers/cisco/webex/extensions/chainable.cr deleted file mode 100644 index 2707afeb345..00000000000 --- a/drivers/cisco/webex/extensions/chainable.cr +++ /dev/null @@ -1,536 +0,0 @@ -require "base64" - -module Halite - module Chainable - {% for verb in %w(get head) %} - # {{ verb.id.capitalize }} a resource - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything", params: { - # first_name: "foo", - # last_name: "bar" - # }) - # ``` - def {{ verb.id }}(uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil) : Halite::Response - request({{ verb }}, uri, headers: headers, params: params, raw: raw, tls: tls) - end - - # {{ verb.id.capitalize }} a streaming resource - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything") do |response| - # puts response.status_code - # while line = response.body_io.gets - # puts line - # end - # end - # ``` - def {{ verb.id }}(uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil, - &block : Halite::Response ->) - request({{ verb }}, uri, headers: headers, params: params, raw: raw, tls: tls, &block) - end - {% end %} - - {% for verb in %w(put post patch delete options) %} - # {{ verb.id.capitalize }} a resource - # - # ### Request with form data - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything", form: { - # first_name: "foo", - # last_name: "bar" - # }) - # ``` - # - # ### Request with json data - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything", json: { - # first_name: "foo", - # last_name: "bar" - # }) - # ``` - # - # ### Request with raw string - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything", raw: "name=Peter+Lee&address=%23123+Happy+Ave&Language=C%2B%2B") - # ``` - def {{ verb.id }}(uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil) : Halite::Response - request({{ verb }}, uri, headers: headers, params: params, form: form, json: json, raw: raw, tls: tls) - end - - # {{ verb.id.capitalize }} a streaming resource - # - # ``` - # Halite.{{ verb.id }}("http://httpbin.org/anything") do |response| - # puts response.status_code - # while line = response.body_io.gets - # puts line - # end - # end - # ``` - def {{ verb.id }}(uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil, - &block : Halite::Response ->) - request({{ verb }}, uri, headers: headers, params: params, form: form, json: json, raw: raw, tls: tls, &block) - end - {% end %} - - # Adds a endpoint to the request. - # - # - # ``` - # Halite.endpoint("https://httpbin.org") - # .get("/get") - # ``` - def endpoint(endpoint : String | URI) : Halite::Client - branch(default_options.with_endpoint(endpoint)) - end - - # Make a request with the given Basic authorization header - # - # ``` - # Halite.basic_auth("icyleaf", "p@ssw0rd") - # .get("http://httpbin.org/get") - # ``` - # - # See Also: [http://tools.ietf.org/html/rfc2617](http://tools.ietf.org/html/rfc2617) - def basic_auth(user : String, pass : String) : Halite::Client - auth("Basic " + Base64.strict_encode(user + ":" + pass)) - end - - # Make a request with the given Authorization header - # - # ``` - # Halite.auth("private-token", "6abaef100b77808ceb7fe26a3bcff1d0") - # .get("http://httpbin.org/get") - # ``` - def auth(value : String) : Halite::Client - headers({"Authorization" => value}) - end - - # Accept the given MIME type - # - # ``` - # Halite.accept("application/json") - # .get("http://httpbin.org/get") - # ``` - def accept(value : String) : Halite::Client - headers({"Accept" => value}) - end - - # Set requests user agent - # - # ``` - # Halite.user_agent("Custom User Agent") - # .get("http://httpbin.org/get") - # ``` - def user_agent(value : String) : Halite::Client - headers({"User-Agent" => value}) - end - - # Make a request with the given headers - # - # ``` - # Halite.headers({"Content-Type", "application/json", "Connection": "keep-alive"}) - # .get("http://httpbin.org/get") - # # Or - # Halite.headers({content_type: "application/json", connection: "keep-alive"}) - # .get("http://httpbin.org/get") - # ``` - def headers(headers : Hash(String, _) | NamedTuple) : Halite::Client - branch(default_options.with_headers(headers)) - end - - # Make a request with the given headers - # - # ``` - # Halite.headers(content_type: "application/json", connection: "keep-alive") - # .get("http://httpbin.org/get") - # ``` - def headers(**kargs) : Halite::Client - branch(default_options.with_headers(kargs)) - end - - # Make a request with the given cookies - # - # ``` - # Halite.cookies({"private-token", "6abaef100b77808ceb7fe26a3bcff1d0"}) - # .get("http://httpbin.org/get") - # # Or - # Halite.cookies({private-token: "6abaef100b77808ceb7fe26a3bcff1d0"}) - # .get("http://httpbin.org/get") - # ``` - def cookies(cookies : Hash(String, _) | NamedTuple) : Halite::Client - branch(default_options.with_cookies(cookies)) - end - - # Make a request with the given cookies - # - # ``` - # Halite.cookies(name: "icyleaf", "gender": "male") - # .get("http://httpbin.org/get") - # ``` - def cookies(**kargs) : Halite::Client - branch(default_options.with_cookies(kargs)) - end - - # Make a request with the given cookies - # - # ``` - # cookies = HTTP::Cookies.from_client_headers(headers) - # Halite.cookies(cookies) - # .get("http://httpbin.org/get") - # ``` - def cookies(cookies : HTTP::Cookies) : Halite::Client - branch(default_options.with_cookies(cookies)) - end - - # Adds a timeout to the request. - # - # How long to wait for the server to send data before giving up, as a int, float or time span. - # The timeout value will be applied to both the connect and the read timeouts. - # - # Set `nil` to timeout to ignore timeout. - # - # ``` - # Halite.timeout(5.5).get("http://httpbin.org/get") - # # Or - # Halite.timeout(2.minutes) - # .post("http://httpbin.org/post", form: {file: "file.txt"}) - # ``` - def timeout(timeout : (Int32 | Float64 | Time::Span)?) - timeout ? timeout(timeout, timeout, timeout) : branch - end - - # Adds a timeout to the request. - # - # How long to wait for the server to send data before giving up, as a int, float or time span. - # The timeout value will be applied to both the connect and the read timeouts. - # - # ``` - # Halite.timeout(3, 3.minutes, 5) - # .post("http://httpbin.org/post", form: {file: "file.txt"}) - # # Or - # Halite.timeout(3.04, 64, 10.0) - # .get("http://httpbin.org/get") - # ``` - def timeout(connect : (Int32 | Float64 | Time::Span)? = nil, - read : (Int32 | Float64 | Time::Span)? = nil, - write : (Int32 | Float64 | Time::Span)? = nil) - branch(default_options.with_timeout(connect, read, write)) - end - - # Returns `Options` self with automatically following redirects. - # - # ``` - # # Automatically following redirects. - # Halite.follow - # .get("http://httpbin.org/relative-redirect/5") - # - # # Always redirect with any request methods - # Halite.follow(strict: false) - # .get("http://httpbin.org/get") - # ``` - def follow(strict = Halite::Options::Follow::STRICT) : Halite::Client - branch(default_options.with_follow(strict: strict)) - end - - # Returns `Options` self with given max hops of redirect times. - # - # ``` - # # Max hops 3 times - # Halite.follow(3) - # .get("http://httpbin.org/relative-redirect/3") - # - # # Always redirect with any request methods - # Halite.follow(4, strict: false) - # .get("http://httpbin.org/relative-redirect/4") - # ``` - def follow(hops : Int32, strict = Halite::Options::Follow::STRICT) : Halite::Client - branch(default_options.with_follow(hops, strict)) - end - - # Returns `Options` self with enable or disable logging. - # - # #### Enable logging - # - # Same as call `logging` method without any argument. - # - # ``` - # Halite.logging.get("http://httpbin.org/get") - # ``` - # - # #### Disable logging - # - # ``` - # Halite.logging(false).get("http://httpbin.org/get") - # ``` - def logging(enable : Bool = true) - options = default_options - options.logging = enable - branch(options) - end - - # Returns `Options` self with given the logging which it integration from `Halite::Logging`. - # - # #### Simple logging - # - # ``` - # Halite.logging - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # - # => 2018-08-28 14:33:19 +08:00 | request | POST | http://httpbin.org/post - # => 2018-08-28 14:33:21 +08:00 | response | 200 | http://httpbin.org/post | 1.61s | application/json - # { ... } - # ``` - # - # #### Logger configuration - # - # By default, Halite will logging all outgoing HTTP requests and their responses(without binary stream) to `STDOUT` on DEBUG level. - # You can configuring the following options: - # - # - `skip_request_body`: By default is `false`. - # - `skip_response_body`: By default is `false`. - # - `skip_benchmark`: Display elapsed time, by default is `false`. - # - `colorize`: Enable colorize in terminal, only apply in `common` format, by default is `true`. - # - # ``` - # Halite.logging(skip_request_body: true, skip_response_body: true) - # .post("http://httpbin.org/get", form: {image: File.open("halite-logo.png")}) - # - # # => 2018-08-28 14:33:19 +08:00 | request | POST | http://httpbin.org/post - # # => 2018-08-28 14:33:21 +08:00 | response | 200 | http://httpbin.org/post | 1.61s | application/json - # ``` - # - # #### Use custom logging - # - # Creating the custom logging by integration `Halite::Logging::Abstract` abstract class. - # Here has two methods must be implement: `#request` and `#response`. - # - # ``` - # class CustomLogger < Halite::Logging::Abstract - # def request(request) - # @logger.info "| >> | %s | %s %s" % [request.verb, request.uri, request.body] - # end - # - # def response(response) - # @logger.info "| << | %s | %s %s" % [response.status_code, response.uri, response.content_type] - # end - # end - # - # # Add to adapter list (optional) - # Halite::Logging.register_adapter "custom", CustomLogger.new - # - # Halite.logging(logging: CustomLogger.new) - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # - # # We can also call it use format name if you added it. - # Halite.logging(format: "custom") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # - # # => 2017-12-13 16:40:13 +08:00 | >> | GET | http://httpbin.org/get?name=foobar - # # => 2017-12-13 16:40:15 +08:00 | << | 200 | http://httpbin.org/get?name=foobar application/json - # ``` - def logging(logging : Halite::Logging::Abstract = Halite::Logging::Common.new) - branch(default_options.with_logging(logging)) - end - - # Returns `Options` self with given the file with the path. - # - # #### JSON-formatted logging - # - # ``` - # Halite.logging(format: "json") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # ``` - # - # #### create a http request and log to file - # - # ``` - # Log.setup("halite.file", backend: Log::IOBackend.new(File.open("/tmp/halite.log", "a"))) - # Halite.logging(for: "halite.file") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # ``` - # - # #### Always create new log file and store data to JSON formatted - # - # ``` - # Log.setup("halite.file", backend: Log::IOBackend.new(File.open("/tmp/halite.log", "w")) - # Halite.logging(for: "halite.file", format: "json") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # ``` - # - # Check the log file content: **/tmp/halite.log** - def logging(format : String = "common", *, for : String = "halite", - skip_request_body = false, skip_response_body = false, - skip_benchmark = false, colorize = true) - opts = { - for: for, - skip_request_body: skip_request_body, - skip_response_body: skip_response_body, - skip_benchmark: skip_benchmark, - colorize: colorize, - } - branch(default_options.with_logging(format, **opts)) - end - - # Turn on given features and its options. - # - # Available features to review all subclasses of `Halite::Feature`. - # - # #### Use JSON logging - # - # ``` - # Halite.use("logging", format: "json") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # - # # => { ... } - # ``` - # - # #### Use common format logging and skip response body - # ``` - # Halite.use("logging", format: "common", skip_response_body: true) - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # - # # => 2018-08-28 14:58:26 +08:00 | request | GET | http://httpbin.org/get - # # => 2018-08-28 14:58:27 +08:00 | response | 200 | http://httpbin.org/get | 615.8ms | application/json - # ``` - def use(feature : String, **opts) - branch(default_options.with_features(feature, **opts)) - end - - # Turn on given the name of features. - # - # Available features to review all subclasses of `Halite::Feature`. - # - # ``` - # Halite.use("logging", "your-custom-feature-name") - # .get("http://httpbin.org/get", params: {name: "foobar"}) - # ``` - def use(*features) - branch(default_options.with_features(*features)) - end - - # Make an HTTP request with the given verb - # - # ``` - # Halite.request("get", "http://httpbin.org/get", { - # "headers" = { "user_agent" => "halite" }, - # "params" => { "nickname" => "foo" }, - # "form" => { "username" => "bar" }, - # }) - # ``` - def request(verb : String, uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil) : Halite::Response - request(verb, uri, options_with(headers, params, form, json, raw, tls)) - end - - # Make an HTTP request with the given verb and options - # - # > This method will be executed with oneshot request. - # - # ``` - # Halite.request("get", "http://httpbin.org/stream/3", headers: {"user-agent" => "halite"}) do |response| - # puts response.status_code - # while line = response.body_io.gets - # puts line - # end - # end - # ``` - def request(verb : String, uri : String, *, - headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil, - &block : Halite::Response ->) - request(verb, uri, options_with(headers, params, form, json, raw, tls), &block) - end - - # Make an HTTP request with the given verb and options - # - # > This method will be executed with oneshot request. - # - # ``` - # Halite.request("get", "http://httpbin.org/get", Halite::Options.new( - # "headers" = { "user_agent" => "halite" }, - # "params" => { "nickname" => "foo" }, - # "form" => { "username" => "bar" }, - # ) - # ``` - def request(verb : String, uri : String, options : Halite::Options? = nil) : Halite::Response - branch(options).request(verb, uri) - end - - # Make an HTTP request with the given verb and options - # - # > This method will be executed with oneshot request. - # - # ``` - # Halite.request("get", "http://httpbin.org/stream/3") do |response| - # puts response.status_code - # while line = response.body_io.gets - # puts line - # end - # end - # ``` - def request(verb : String, uri : String, options : Halite::Options? = nil, &block : Halite::Response ->) - branch(options).request(verb, uri, &block) - end - - private def branch(options : Halite::Options? = nil) : Halite::Client - options ||= default_options - Halite::Client.new(options) - end - - private def default_options - {% if @type.superclass %} - @default_options - {% else %} - DEFAULT_OPTIONS.clear! - {% end %} - end - - private def options_with(headers : (Hash(String, _) | NamedTuple)? = nil, - params : (Hash(String, _) | NamedTuple)? = nil, - form : (Hash(String, _) | NamedTuple)? = nil, - json : (Hash(String, _) | NamedTuple)? = nil, - raw : String? = nil, - tls : OpenSSL::SSL::Context::Client? = nil) - options = Halite::Options.new(headers: headers, params: params, form: form, json: json, raw: raw, tls: tls) - default_options.merge!(options) - end - end -end diff --git a/drivers/cisco/webex/models/device.cr b/drivers/cisco/webex/models/device.cr deleted file mode 100644 index 3ccad2ccce3..00000000000 --- a/drivers/cisco/webex/models/device.cr +++ /dev/null @@ -1,15 +0,0 @@ -module Cisco - module Webex - module Models - class Device - include JSON::Serializable - - @[JSON::Field(key: "webSocketUrl")] - property websocket_url : String - - @[JSON::Field(key: "name")] - property name : String? - end - end - end -end diff --git a/drivers/cisco/webex/models/event.cr b/drivers/cisco/webex/models/event.cr index d0ff44cd00b..0d2493989f4 100644 --- a/drivers/cisco/webex/models/event.cr +++ b/drivers/cisco/webex/models/event.cr @@ -1,3 +1,5 @@ +require "./events/**" + module Cisco module Webex module Models diff --git a/drivers/cisco/webex/models/events/actor.cr b/drivers/cisco/webex/models/events/actor.cr index 01c3b9f8007..8ea659c923d 100644 --- a/drivers/cisco/webex/models/events/actor.cr +++ b/drivers/cisco/webex/models/events/actor.cr @@ -15,7 +15,7 @@ module Cisco property display_name : String @[JSON::Field(key: "orgId")] - property organisation_id : String + property organization_id : String @[JSON::Field(key: "emailAddress")] property email : String diff --git a/drivers/cisco/webex/models/message.cr b/drivers/cisco/webex/models/message.cr index a2136fcc694..bb2f02f291c 100644 --- a/drivers/cisco/webex/models/message.cr +++ b/drivers/cisco/webex/models/message.cr @@ -6,7 +6,7 @@ module Cisco # The unique identifier for the message. @[JSON::Field(key: "id")] - property id : String + property id : String? # The unique identifier for the parent message. @[JSON::Field(key: "parentId")] @@ -14,11 +14,11 @@ module Cisco # The room ID of the message. @[JSON::Field(key: "roomId")] - property room_id : String + property room_id : String? # The type of room. @[JSON::Field(key: "roomType")] - property room_type : String + property room_type : String? # The person ID of the recipient when sending a 1:1 message. @[JSON::Field(key: "toPersonId")] @@ -30,7 +30,7 @@ module Cisco # The message, in plain text. @[JSON::Field(key: "text")] - property text : String + property text : String? # The message, in Markdown format. @[JSON::Field(key: "markdown")] @@ -46,11 +46,11 @@ module Cisco # The person ID of the message author. @[JSON::Field(key: "personId")] - property person_id : String + property person_id : String? # The email address of the message author. @[JSON::Field(key: "personEmail")] - property person_email : String + property person_email : String? # People IDs for anyone mentioned in the message. @[JSON::Field(key: "mentionedPeople")] @@ -61,12 +61,12 @@ module Cisco property mentioned_groups : Array(String)? # Message content attachments attached to the message. - @[JSON::Field(key: "attachments")] - property attachments : Array(String)? + # @[JSON::Field(key: "attachments")] + # property attachments : Array(Attachment)? # The date and time the message was created. @[JSON::Field(key: "created")] - property created : String + property created : String? # The date and time the message was created. @[JSON::Field(key: "updated")] diff --git a/drivers/cisco/webex/models/peek.cr b/drivers/cisco/webex/models/peek.cr deleted file mode 100644 index 2198be1757f..00000000000 --- a/drivers/cisco/webex/models/peek.cr +++ /dev/null @@ -1,15 +0,0 @@ -module Cisco - module Webex - module Models - class Peek - include JSON::Serializable - - @[JSON::Field(key: "id")] - property id : String - - @[JSON::Field(key: "data")] - property data : Events::Type - end - end - end -end diff --git a/drivers/cisco/webex/models/person.cr b/drivers/cisco/webex/models/person.cr deleted file mode 100644 index 0d74284904b..00000000000 --- a/drivers/cisco/webex/models/person.cr +++ /dev/null @@ -1,12 +0,0 @@ -module Cisco - module Webex - module Models - class Person - include JSON::Serializable - - @[JSON::Field(key: "id")] - property id : String - end - end - end -end diff --git a/drivers/cisco/webex/models/room.cr b/drivers/cisco/webex/models/room.cr deleted file mode 100644 index d674cda6f9c..00000000000 --- a/drivers/cisco/webex/models/room.cr +++ /dev/null @@ -1,45 +0,0 @@ -module Cisco - module Webex - module Models - class Room - include JSON::Serializable - - # A unique identifier for the room. - @[JSON::Field(key: "id")] - property id : String - - # The name of the room. - @[JSON::Field(key: "title")] - property title : String - - # The room type. - @[JSON::Field(key: "type")] - property type : String - - # Whether the room is moderated (locked) or not. - @[JSON::Field(key: "isLocked")] - property is_locked : Bool - - # The ID for the team with which this room is associated.. - @[JSON::Field(key: "teamId")] - property team_id : String? - - # The date and time of the room"s last activity.. - @[JSON::Field(key: "lastActivity")] - property last_activity : String - - # The ID of the person who created this room. - @[JSON::Field(key: "creatorId")] - property creator_id : String - - # The date and time the room was created. - @[JSON::Field(key: "created")] - property created : String - - # The ID of the organization which owns this room. - @[JSON::Field(key: "ownerId")] - property owner_id : String - end - end - end -end diff --git a/drivers/cisco/webex/session.cr b/drivers/cisco/webex/session.cr deleted file mode 100644 index f67597a687c..00000000000 --- a/drivers/cisco/webex/session.cr +++ /dev/null @@ -1,82 +0,0 @@ -module Cisco - module Webex - class Session - Log = ::Log.for(self) - - property base_url : String = Constants::DEFAULT_BASE_URL - property single_request_timeout : Int32 = Constants::DEFAULT_SINGLE_REQUEST_TIMEOUT - property user_agent : String = ["Tepha", Constants::VERSION].join(" ") - property wait_on_rate_limit : Bool = Constants::DEFAULT_WAIT_ON_RATE_LIMIT - - private property client : Halite::Client = Halite::Client.new - - def initialize(@access_token : String) - end - - def request(method : String, url : String, **kwargs) : Halite::Response - # Abstract base method for making requests to the Webex Teams APIs. - # This base method: - # * Expands the API endpoint URL to an absolute URL - # * Makes the actual HTTP request to the API endpoint - # * Provides support for Webex Teams rate-limiting - # * Inspects response codes and raises exceptions as appropriate - - absolute_url = URI.parse(base_url).resolve(url).to_s - - @client.headers({"Authorization" => ["Bearer", @access_token].join(" ")}) - @client.headers({"Content-Type" => "application/json;charset=utf-8"}) - @client.timeout single_request_timeout - - loop do - case method - when "GET" - response = @client.get absolute_url, **kwargs - when "POST" - response = @client.post absolute_url, **kwargs - when "PUT" - response = @client.put absolute_url, **kwargs - when "DELETE" - response = @client.delete absolute_url, **kwargs - else - raise Exceptions::Method.new("The request-method type is invalid.") - end - - begin - status_code = StatusCode.new(response.status_code) - raise Exceptions::RateLimit.new(status_code.message) if response.status_code == 429 - raise Exceptions::StatusCode.new(status_code.message) if !status_code.valid? - - return response - rescue e : Exceptions::StatusCode - Log.error(exception: e) { } - rescue e : Exceptions::RateLimit - Log.error(exception: e) { } - - retry_after = (response.headers["Retry-After"]? || "15").to_i * 1000 - sleep(retry_after) - end - end - end - - def get(url : String, **kwargs) : Halite::Response - # Sends a GET request. - request("GET", url, **kwargs) - end - - def post(url : String, **kwargs) : Halite::Response - # Sends a POST request. - request("POST", url, **kwargs) - end - - def put(url : String, **kwargs) : Halite::Response - # Sends a PUT request. - request("PUT", url, **kwargs) - end - - def delete(url : String, **kwargs) : Halite::Response - # Sends a DELETE request. - request("DELETE", url, **kwargs) - end - end - end -end diff --git a/drivers/cisco/webex/status_code.cr b/drivers/cisco/webex/status_code.cr deleted file mode 100644 index 4e48f8d80ba..00000000000 --- a/drivers/cisco/webex/status_code.cr +++ /dev/null @@ -1,23 +0,0 @@ -module Cisco - module Webex - class StatusCode - private property code : Int32 - - def initialize(@code : Int32) - end - - def valid? : Bool - case @code - when 200, 204 - true - else - false - end - end - - def message : String - Constants::STATUS_CODES[@code] - end - end - end -end diff --git a/drivers/cisco/webex/utils.cr b/drivers/cisco/webex/utils.cr deleted file mode 100644 index 1dc7c015cc1..00000000000 --- a/drivers/cisco/webex/utils.cr +++ /dev/null @@ -1,23 +0,0 @@ -module Cisco - module Webex - module Utils - def self.hash_from_items_with_values(**kwargs) - kwargs = kwargs.map { |k, v| - if v != nil && v != "" - {"#{k}" => v} - end - } - - kwargs.reject!(nil) - kwargs = kwargs.reduce { |acc, i| acc.try(&.merge(i.not_nil!)) } - - kwargs - end - - def self.named_tuple_from_hash(hash) - named_tuple = NamedTuple.new(roomId: String, text: String) - named_tuple.from(hash) - end - end - end -end diff --git a/drivers/xy_sense/location_service_spec.cr b/drivers/xy_sense/location_service_spec.cr index c2f86fc32ae..3a1142ed47f 100644 --- a/drivers/xy_sense/location_service_spec.cr +++ b/drivers/xy_sense/location_service_spec.cr @@ -33,18 +33,18 @@ class XYSenseMock < DriverSpecs::MockDriver capacity: 1, category: "Workpoint", }, - { - id: "xysense-desk-456-id", - name: "desk-456", - capacity: 1, - category: "Workpoint", - }, - { - id: "xysense-area-567-id", - name: "area-567", - capacity: 20, - category: "Lobby", - }], + { + id: "xysense-desk-456-id", + name: "desk-456", + capacity: 1, + category: "Workpoint", + }, + { + id: "xysense-area-567-id", + name: "area-567", + capacity: 20, + category: "Lobby", + }], }, }