require 'cgi' require 'uri' require 'httparty' require 'hashie' require 'json' module CreateSend USER_AGENT_STRING = "createsend-ruby-#{VERSION}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}-#{RUBY_PLATFORM}" # Represents a CreateSend API error. Contains specific data about the error. class CreateSendError < StandardError attr_reader :data def initialize(data) @data = data # @data should contain Code, Message and optionally ResultData extra = @data.ResultData ? "\nExtra result data: #{@data.ResultData}" : "" super "The CreateSend API responded with the following error"\ " - #{@data.Code}: #{@data.Message}#{extra}" end end # Raised for HTTP response codes of 400...500 class ClientError < StandardError; end # Raised for HTTP response codes of 500...600 class ServerError < StandardError; end # Raised for HTTP response code of 400 class BadRequest < CreateSendError; end # Raised for HTTP response code of 401 class Unauthorized < CreateSendError; end # Raised for HTTP response code of 404 class NotFound < ClientError; end # Raised for HTTP response code of 401, specifically when an OAuth token # in invalid (Code: 120, Message: 'Invalid OAuth Token') class InvalidOAuthToken < Unauthorized; end # Raised for HTTP response code of 401, specifically when an OAuth token # has expired (Code: 121, Message: 'Expired OAuth Token') class ExpiredOAuthToken < Unauthorized; end # Raised for HTTP response code of 401, specifically when an OAuth token # has been revoked (Code: 122, Message: 'Revoked OAuth Token') class RevokedOAuthToken < Unauthorized; end # Provides high level CreateSend functionality/data you'll probably need. class CreateSend include HTTParty attr_reader :auth_details # Specify cert authority file for cert validation ssl_ca_file File.expand_path(File.join(File.dirname(__FILE__), 'cacert.pem')) # Set a custom user agent string to be used when instances of # CreateSend::CreateSend make API calls. # # user_agent - The user agent string to use in the User-Agent header when # instances of this class make API calls. If set to nil, the # default value of CreateSend::USER_AGENT_STRING will be used. def self.user_agent(user_agent) headers({'User-Agent' => user_agent || USER_AGENT_STRING}) end # Get the authorization URL for your application, given the application's # client_id, redirect_uri, scope, and optional state data. def self.authorize_url(client_id, redirect_uri, scope, state=nil) qs = "client_id=#{CGI.escape(client_id.to_s)}" qs << "&redirect_uri=#{CGI.escape(redirect_uri.to_s)}" qs << "&scope=#{CGI.escape(scope.to_s)}" qs << "&state=#{CGI.escape(state.to_s)}" if state "#{@@oauth_base_uri}?#{qs}" end # Exchange a provided OAuth code for an OAuth access token, 'expires in' # value, and refresh token. def self.exchange_token(client_id, client_secret, redirect_uri, code) body = "grant_type=authorization_code" body << "&client_id=#{CGI.escape(client_id.to_s)}" body << "&client_secret=#{CGI.escape(client_secret.to_s)}" body << "&redirect_uri=#{CGI.escape(redirect_uri.to_s)}" body << "&code=#{CGI.escape(code.to_s)}" options = {:body => body} response = HTTParty.post(@@oauth_token_uri, options) if response.has_key? 'error' and response.has_key? 'error_description' err = "Error exchanging code for access token: " err << "#{response['error']} - #{response['error_description']}" raise err end r = Hashie::Mash.new(response) [r.access_token, r.expires_in, r.refresh_token] end # Refresh an OAuth access token, given an OAuth refresh token. # Returns a new access token, 'expires in' value, and refresh token. def self.refresh_access_token(refresh_token) options = { :body => "grant_type=refresh_token&refresh_token=#{CGI.escape(refresh_token)}" } response = HTTParty.post(@@oauth_token_uri, options) if response.has_key? 'error' and response.has_key? 'error_description' err = "Error refreshing access token: " err << "#{response['error']} - #{response['error_description']}" raise err end r = Hashie::Mash.new(response) [r.access_token, r.expires_in, r.refresh_token] end def initialize(*args) if args.size > 0 auth args.first # Expect auth details as first argument end end @@base_uri = "https://api.createsend.com/api/v3" @@oauth_base_uri = "https://api.createsend.com/oauth" @@oauth_token_uri = "#{@@oauth_base_uri}/token" headers({ 'User-Agent' => USER_AGENT_STRING, 'Content-Type' => 'application/json; charset=utf-8', 'Accept-Encoding' => 'gzip, deflate' }) base_uri @@base_uri # Authenticate using either OAuth or an API key. def auth(auth_details) @auth_details = auth_details end # Refresh the current OAuth token using the current refresh token. def refresh_token if not @auth_details or not @auth_details.has_key? :refresh_token or not @auth_details[:refresh_token] raise '@auth_details[:refresh_token] does not contain a refresh token.' end access_token, expires_in, refresh_token = self.class.refresh_access_token @auth_details[:refresh_token] auth({ :access_token => access_token, :refresh_token => refresh_token}) [access_token, expires_in, refresh_token] end # Gets your CreateSend API key, given your site url, username and password. def apikey(site_url, username, password) site_url = CGI.escape(site_url) options = {:basic_auth => {:username => username, :password => password}} response = get("/apikey.json?SiteUrl=#{site_url}", options) result = Hashie::Mash.new(response) auth({:api_key => result.ApiKey}) if not @auth_details result end # Gets your clients. def clients response = get('/clients.json') response.map{|item| Hashie::Mash.new(item)} end # Get your billing details. def billing_details response = get('/billingdetails.json') Hashie::Mash.new(response) end # Gets valid countries. def countries response = get('/countries.json') response.parsed_response end # Gets the current date in your account's timezone. def systemdate response = get('/systemdate.json') Hashie::Mash.new(response) end # Gets valid timezones. def timezones response = get('/timezones.json') response.parsed_response end # Gets the administrators for the account. def administrators response = get('/admins.json') response.map{|item| Hashie::Mash.new(item)} end # Gets the primary contact for the account. def get_primary_contact response = get('/primarycontact.json') Hashie::Mash.new(response) end # Set the primary contect for the account. def set_primary_contact(email) options = { :query => { :email => email } } response = put("/primarycontact.json", options) Hashie::Mash.new(response) end # Get a URL which initiates a new external session for the user with the # given email. # Full details: http://www.campaignmonitor.com/api/account/#single_sign_on # # email - The email address of the Campaign Monitor user for whom # the login session should be created. # chrome - Which 'chrome' to display - Must be either "all", # "tabs", or "none". # url - The URL to display once logged in. e.g. "/subscribers/" # integrator_id - The integrator ID. You need to contact Campaign Monitor # support to get an integrator ID. # client_id - The Client ID of the client which should be active once # logged in to the Campaign Monitor account. # # Returns An object containing a single field SessionUrl which represents # the URL to initiate the external Campaign Monitor session. def external_session_url(email, chrome, url, integrator_id, client_id) options = { :body => { :Email => email, :Chrome => chrome, :Url => url, :IntegratorID => integrator_id, :ClientID => client_id }.to_json } response = put("/externalsession.json", options) Hashie::Mash.new(response) end def get(*args) args = add_auth_details_to_options(args) handle_response CreateSend.get(*args) end alias_method :cs_get, :get def post(*args) args = add_auth_details_to_options(args) handle_response CreateSend.post(*args) end alias_method :cs_post, :post def put(*args) args = add_auth_details_to_options(args) handle_response CreateSend.put(*args) end alias_method :cs_put, :put def delete(*args) args = add_auth_details_to_options(args) handle_response CreateSend.delete(*args) end alias_method :cs_delete, :delete def add_auth_details_to_options(args) if @auth_details options = {} if args.size > 1 options = args[1] end if @auth_details.has_key? :access_token options[:headers] = { "Authorization" => "Bearer #{@auth_details[:access_token]}" } elsif @auth_details.has_key? :api_key if not options.has_key? :basic_auth options[:basic_auth] = { :username => @auth_details[:api_key], :password => 'x' } end end args[1] = options end args end def handle_response(response) # :nodoc: case response.code when 400 raise BadRequest.new(Hashie::Mash.new response) when 401 data = Hashie::Mash.new(response) case data.Code when 120 raise InvalidOAuthToken.new data when 121 raise ExpiredOAuthToken.new data when 122 raise RevokedOAuthToken.new data else raise Unauthorized.new data end when 404 raise NotFound.new when 400...500 raise ClientError.new when 500...600 raise ServerError.new else response end end end end