1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
require 'rubygems'
require 'httparty'
require 'multi_json'
module Flowdock
FLOWDOCK_API_URL = "https://api.flowdock.com/v1"
class InvalidParameterError < StandardError; end
class NotFoundError < StandardError; end
class ApiError < StandardError; end
module Helpers
def blank?(var)
var.nil? || var.respond_to?(:length) && var.length == 0
end
def handle_response(resp)
body = (resp.body.nil? || resp.body.strip.empty?) ? '{}' : resp.body
json = MultiJson.decode(body)
if resp.code == 404
raise NotFoundError, "Flowdock API returned error:\nStatus: #{resp.code}\n Message: #{json["message"]}"
end
unless resp.code >= 200 && resp.code < 300
errors = json["errors"].map {|k,v| "#{k}: #{v.join(',')}"}.join("\n") unless json["errors"].nil?
raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\n Message: #{json["message"]}\n Errors:\n#{errors}"
end
json
rescue MultiJson::DecodeError
raise ApiError, "Flowdock API returned error:\nStatus: #{resp.code}\nBody: #{resp.body}"
end
end
class Flow
include HTTParty
include Helpers
attr_reader :api_token, :source, :project, :from, :external_user_name
# Required options keys: :api_token
# Optional keys: :external_user_name, :source, :project, :from => { :name, :address }, :reply_to
def initialize(options = {})
@api_token = if options[:api_token].kind_of?(Array)
options[:api_token].join(",")
else
options[:api_token].to_s
end
raise InvalidParameterError, "Flow must have :api_token attribute" if blank?(@api_token)
@source = options[:source] || nil
@project = options[:project] || nil
@from = options[:from] || {}
@reply_to = options[:reply_to] || nil
@external_user_name = options[:external_user_name] || nil
end
def push_to_team_inbox(params)
@source = params[:source] unless blank?(params[:source])
raise InvalidParameterError, "Message must have valid :source attribute, only alphanumeric characters and underscores can be used" if blank?(@source) || !@source.match(/^[a-z0-9\-_ ]+$/i)
@project = params[:project] unless blank?(params[:project])
raise InvalidParameterError, "Optional attribute :project can only contain alphanumeric characters and underscores" if !blank?(@project) && !@project.match(/^[a-z0-9\-_ ]+$/i)
raise InvalidParameterError, "Message must have both :subject and :content" if blank?(params[:subject]) || blank?(params[:content])
from = (params[:from].kind_of?(Hash)) ? params[:from] : @from
raise InvalidParameterError, "Message's :from attribute must have :address attribute" if blank?(from[:address])
reply_to = (!blank?(params[:reply_to])) ? params[:reply_to] : @reply_to
tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
link = (!blank?(params[:link])) ? params[:link] : nil
params = {
:source => @source,
:format => 'html', # currently only supported format
:from_address => from[:address],
:subject => params[:subject],
:content => params[:content],
}
params[:from_name] = from[:name] unless blank?(from[:name])
params[:reply_to] = reply_to unless blank?(reply_to)
params[:tags] = tags.join(",") if tags.size > 0
params[:project] = @project unless blank?(@project)
params[:link] = link unless blank?(link)
# Send the request
resp = self.class.post(get_flowdock_api_url("messages/team_inbox"), :body => params)
handle_response(resp)
true
end
def push_to_chat(params)
raise InvalidParameterError, "Message must have :content" if blank?(params[:content])
@external_user_name = params[:external_user_name] unless blank?(params[:external_user_name])
if blank?(@external_user_name) || @external_user_name.match(/^[\S]+$/).nil? || @external_user_name.length > 16
raise InvalidParameterError, "Message must have :external_user_name that has no whitespace and maximum of 16 characters"
end
tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
thread_id = params[:thread_id]
message_id = params[:message_id] || params[:message]
params = {
:content => params[:content],
:external_user_name => @external_user_name
}
params[:tags] = tags.join(",") if tags.size > 0
params[:thread_id] = thread_id if thread_id
params[:message_id] = message_id if message_id
# Send the request
resp = self.class.post(get_flowdock_api_url("messages/chat"), :body => params)
handle_response(resp)
true
end
# <b>DEPRECATED:</b> Please use <tt>useful</tt> instead.
def send_message(params)
warn "[DEPRECATION] `send_message` is deprecated. Please use `push_to_team_inbox` instead."
push_to_team_inbox(params)
end
private
def get_flowdock_api_url(path)
"#{FLOWDOCK_API_URL}/#{path}/#{@api_token}"
end
end
class Client
include HTTParty
include Helpers
attr_reader :api_token
def initialize(options = {})
@api_token = options[:api_token]
@flow_token = options[:flow_token]
raise InvalidParameterError, "Client must have :api_token or an :flow_token" if blank?(@api_token) && blank?(@flow_token)
end
def chat_message(params)
raise InvalidParameterError, "missing api_token" if blank?(@api_token)
raise InvalidParameterError, "Message must have :content" if blank?(params[:content])
raise InvalidParameterError, "Message must have :flow" if blank?(params[:flow])
params = params.clone
tags = (params[:tags].kind_of?(Array)) ? params[:tags] : []
params[:message] = params.delete(:message_id) if params[:message_id]
tags.reject! { |tag| !tag.kind_of?(String) || blank?(tag) }
event = if params[:message] then 'comment' else 'message' end
post(event + 's', params.merge(tags: tags, event: event))
end
def private_message(params)
raise InvalidParameterError, "missing api_token" if blank?(@api_token)
raise InvalidParameterError, "Message must have :content" if blank?(params[:content])
raise InvalidParameterError, "Message must have :user_id" if blank?(params[:user_id])
user_id = params.delete(:user_id)
params = params.clone
event = "message"
post("private/#{user_id}/messages", params.merge(event: event))
end
def post_to_thread(thread)
raise InvalidParameterError, "missing flow_token" if blank?(@flow_token)
resp = self.class.post(api_url("/messages"),
body: MultiJson.dump(thread.merge(flow_token: @flow_token)),
headers: headers)
handle_response resp
end
def post(path, data = {})
resp = self.class.post(api_url(path), :body => MultiJson.dump(data), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
handle_response(resp)
end
def get(path, data = {})
resp = self.class.get(api_url(path), :query => data, :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
handle_response(resp)
end
def put(path, data = {})
resp = self.class.put(api_url(path), :body => MultiJson.dump(data), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
handle_response(resp)
end
def delete(path)
resp = self.class.delete(api_url(path), :basic_auth => {:username => @api_token, :password => ''}, :headers => headers)
handle_response(resp)
end
private
def api_url(path)
File.join(FLOWDOCK_API_URL, path)
end
def headers
{"Content-Type" => "application/json", "Accept" => "application/json"}
end
end
end
|