kampainer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 03a41e771943f773f67b6e0ac09039ee33852554beb8de46657357dba397b48f
4
+ data.tar.gz: b04481efe285529989e41f5ed1add04cacd7670811b4c9641ccd80122a62d282
5
+ SHA512:
6
+ metadata.gz: c74bade2fc8fc314545e4d0d133bd4ec26fe704452d6873fdaca7c22376a87748e05d6ffc046e5621a5fb3791de61132e8e17b31ac3116c219b5675fd244c532
7
+ data.tar.gz: 0a88c11d58fce7d2bf25a5944701dcff1d45dc8a420fc6f5ba6188226734bef911f6b1f0e6683045c883a3a7bd76b94a15d09e2b4e7721be055d113bb5eacd79
@@ -0,0 +1,53 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ # specify the version you desire here
10
+ - image: circleci/ruby:2.5
11
+
12
+ working_directory: ~/repo
13
+
14
+ steps:
15
+ - checkout
16
+
17
+ # Download and cache dependencies
18
+ - restore_cache:
19
+ keys:
20
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
21
+ # fallback to using the latest cache if no exact match is found
22
+ - v1-dependencies-
23
+
24
+ - run:
25
+ name: install dependencies
26
+ command: |
27
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
28
+
29
+ - save_cache:
30
+ paths:
31
+ - ./vendor/bundle
32
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
33
+
34
+ # run tests!
35
+ - run:
36
+ name: run tests
37
+ command: |
38
+ mkdir /tmp/test-results
39
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
40
+ circleci tests split --split-by=timings)"
41
+
42
+ bundle exec rspec \
43
+ --format progress \
44
+ --out /tmp/test-results/rspec.xml \
45
+ --format progress \
46
+ $TEST_FILES
47
+
48
+ # collect reports
49
+ - store_test_results:
50
+ path: /tmp/test-results
51
+ - store_artifacts:
52
+ path: /tmp/test-results
53
+ destination: test-results
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in kampainer.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Piers Chambers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Kampainer
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/kampainer.svg)](https://badge.fury.io/rb/kampainer)
4
+ [![CircleCI](https://circleci.com/gh/varyonic/kampainer.svg?style=svg)](https://circleci.com/gh/varyonic/kampainer)
5
+
6
+ Unofficial Ruby wrapper for the Campaigner Elements API
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'kampainer'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ ## Usage
21
+
22
+ See specs for sample usage.
23
+
24
+ ## Development
25
+
26
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
27
+
28
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
29
+
30
+ ## Contributing
31
+
32
+ Bug reports and pull requests are welcome on GitHub at https://github.com/varyonic/kampainer.
33
+
34
+ ## License
35
+
36
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kampainer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/kampainer.gemspec ADDED
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "kampainer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kampainer"
8
+ spec.version = Kampainer::VERSION
9
+ spec.authors = ["Piers Chambers"]
10
+ spec.email = ["piers@varyonic.com"]
11
+
12
+ spec.summary = %q{Unofficial Ruby wrapper for the Campaigner Elements API.}
13
+ spec.homepage = "https://github.com/varyonic/kampainer"
14
+ spec.license = "MIT"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "nokogiri"
26
+ spec.add_dependency "roxml"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.17"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ end
@@ -0,0 +1,40 @@
1
+ module Kampainer
2
+ # See https://ws.campaigner.com/2013/01/campaignmanagement.asmx?WSDL
3
+
4
+ # GetCampaignSummary
5
+ class CampaignData < SchemaObject
6
+ xml_name 'campaignData'
7
+ xml_accessor :id, as: Integer
8
+ xml_accessor :campaign_name
9
+ xml_accessor :campaign_subject
10
+ xml_accessor :campaign_format
11
+ xml_accessor :campaign_status
12
+ xml_accessor :campaign_type
13
+ xml_accessor :html_content
14
+ xml_accessor :txt_content
15
+ xml_accessor :from_name
16
+ xml_accessor :from_email_id, as: Integer
17
+ xml_accessor :reply_email_id, as: Integer
18
+ xml_accessor :track_replies?
19
+ xml_accessor :auto_reply_message?
20
+ xml_accessor :project_id, as: Integer
21
+ xml_accessor :is_welcome_campaign?
22
+ xml_accessor :date_modified
23
+ end
24
+
25
+ # GetCampaignSummary
26
+ class CampaignRecipientsData < SchemaObject
27
+ xml_reader :send_to_all_contacts?
28
+ # TODO
29
+ end
30
+
31
+ class CampaignScheduleData < SchemaObject
32
+ # TODO
33
+ end
34
+
35
+ class FromEmailDescription < SchemaObject
36
+ xml_reader :id, as: Integer
37
+ xml_reader :email_address
38
+ xml_reader :from_email_status
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ module Kampainer
2
+ # See https://ws.campaigner.com/2013/01/campaignmanagement.asmx?WSDL
3
+ class CampaignManager
4
+ attr_reader :session
5
+
6
+ def initialize(session)
7
+ @session = session
8
+ end
9
+
10
+ def create_update_campaign(campaign)
11
+ call('CreateUpdateCampaign', campaign.to_xml)[0].to_i
12
+ end
13
+
14
+ def delete_campaign(id)
15
+ call('DeleteCampaign', campaignId: id)
16
+ end
17
+
18
+ def get_campaign_summary(id)
19
+ call('GetCampaignSummary', campaignId: id)
20
+ end
21
+
22
+ def list_from_emails
23
+ call('ListFromEmails')
24
+ end
25
+
26
+ protected
27
+
28
+ def call(action_name, *params)
29
+ session.call("#{session.base_url}2013/01/campaignmanagement.asmx", action_name, *params)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,149 @@
1
+ module Kampainer
2
+
3
+ class ContactKey < SchemaObject
4
+ xml_name 'ContactKey'
5
+ xml_accessor :id, as: Integer, from: 'ContactId'
6
+ xml_accessor :unique_identifier, from: 'ContactUniqueIdentifier'
7
+ end
8
+
9
+ class ArrayOfInt < SchemaCollection
10
+ xml_accessor :collection, as: [Integer], from: 'int'
11
+ end
12
+
13
+ class Contact < SchemaObject
14
+ class Key < ContactKey; end
15
+
16
+ class CustomAttribute < SchemaObject
17
+ xml_name 'CustomAttribute'
18
+ xml_accessor :id, as: Integer, from: :attr
19
+ xml_accessor :value, from: :content
20
+ end
21
+
22
+ class CustomAttributes < SchemaCollection
23
+ xml_accessor :collection, as: [CustomAttribute]
24
+ end
25
+
26
+ xml_name 'ContactData'
27
+ xml_accessor :key, as: Key, from: 'ContactKey'
28
+ xml_accessor :first_name
29
+ xml_accessor :last_name
30
+ xml_accessor :email_address
31
+ xml_accessor :phone
32
+ xml_accessor :email_format
33
+ xml_accessor :status
34
+ xml_accessor :is_test_contact
35
+ xml_accessor :custom_attributes, as: CustomAttributes
36
+ xml_accessor :add_to_groups, as: ArrayOfInt
37
+
38
+ def custom_attributes=(custom_attributes)
39
+ custom_attributes = CustomAttributes.new(custom_attributes) if custom_attributes.is_a?(Array)
40
+ @custom_attributes = custom_attributes
41
+ end
42
+
43
+ def add_to_groups=(group_ids)
44
+ group_ids = ArrayOfInt.new(group_ids) unless group_ids.is_a?(SchemaObject)
45
+ @add_to_groups = group_ids
46
+ end
47
+ end
48
+
49
+ class ContactKeys < SchemaCollection
50
+ xml_reader :collection, as: [Contact::Key]
51
+
52
+ def initialize(keys = [])
53
+ @collection = keys.map do |key|
54
+ case key
55
+ when Contact::Key then key
56
+ when Integer then Contact::Key.new(id: key)
57
+ when String then Contact::Key.new(unique_identifier: key)
58
+ else Contact::Key.new(key)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ class ContactsDataFilter < SchemaObject
65
+ xml_name 'contactFilter'
66
+ xml_accessor :keys, as: ContactKeys, from: 'ContactKeys'
67
+ end
68
+
69
+ class ContactInformationFilter < SchemaObject
70
+ xml_name 'contactInformationFilter' # GetContacts
71
+ xml_accessor :include_static_attributes
72
+ xml_accessor :include_custom_attributes
73
+ xml_accessor :include_system_attributes
74
+ xml_accessor :include_group_membership_data
75
+
76
+ def initialize(options)
77
+ options.each do |k, v|
78
+ @include_static_attributes = !!v if k.to_s =~ /static/ || v.to_s =~ /static/
79
+ @include_custom_attributes = !!v if k.to_s =~ /custom/ || v.to_s =~ /custom/
80
+ @include_system_attributes = !!v if k.to_s =~ /system/ || v.to_s =~ /system/
81
+ @include_group_membership_data = !!v if k.to_s =~ /group|member/ || v.to_s =~ /group|member/
82
+ end
83
+ end
84
+ end
85
+
86
+ # GetContacts
87
+ class ContactDetailData < SchemaObject
88
+ class StaticAttributes < SchemaObject
89
+ xml_reader :first_name
90
+ xml_reader :last_name
91
+ xml_reader :email
92
+ xml_reader :phone
93
+ xml_reader :email_format
94
+ xml_reader :status
95
+ xml_reader :is_test_contact
96
+ end
97
+
98
+ class AttributeDetails < SchemaObject
99
+ xml_name 'AttributeDetails'
100
+ xml_reader :id, as: Integer
101
+ xml_reader :name
102
+ xml_reader :value
103
+ xml_reader :default_value
104
+ end
105
+
106
+ class ArrayOfAttributeDetails < SchemaCollection
107
+ xml_name 'CustomAttributes'
108
+ xml_reader :collection, as: [AttributeDetails]
109
+ end
110
+
111
+ class ArrayOfContactGroupDescription < SchemaCollection
112
+ xml_reader :collection, as: [ContactGroupDescription]
113
+ end
114
+
115
+ xml_name 'ContactDetailData'
116
+ xml_reader :key, as: Contact::Key, from: 'ContactKey'
117
+ xml_reader :static_attributes, as: StaticAttributes
118
+ xml_reader :custom_attributes, as: ArrayOfAttributeDetails, from: 'CustomAttributes'
119
+ xml_reader :contact_groups, as: ArrayOfContactGroupDescription, from: 'group_membership_data'
120
+ end
121
+
122
+ # GetContacts
123
+ class ContactData < SchemaCollection
124
+ xml_accessor :collection, as: [ContactDetailData]
125
+ end
126
+
127
+ # ImmediateUpload
128
+ class Contacts < SchemaCollection
129
+ xml_name 'contacts'
130
+ xml_accessor :collection, as: [Contact]
131
+ end
132
+
133
+ # ImmediateUpload
134
+ class UploadResultData < SchemaObject
135
+ xml_accessor :index, as: Integer
136
+ xml_accessor :key, as: ContactKey, from: 'ContactKey'
137
+ xml_accessor :result_description
138
+ end
139
+
140
+ # ListTestContacts
141
+ class TestContact < Contact
142
+ xml_accessor :email
143
+ end
144
+
145
+ # DeleteContacts
146
+ class ArrayOfContactKey < ContactKeys
147
+ xml_name 'contactKeys'
148
+ end
149
+ end
@@ -0,0 +1,31 @@
1
+ module Kampainer
2
+ class Attribute < SchemaObject
3
+ class Filter < SchemaObject
4
+ xml_name 'filter'
5
+ xml_accessor :include_all_default_attributes?
6
+ xml_accessor :include_all_custom_attributes?
7
+ xml_accessor :include_all_system_attributes?
8
+
9
+ def initialize(options)
10
+ @include_all_default_attributes = false
11
+ @include_all_custom_attributes = false
12
+ @include_all_system_attributes = false
13
+ options.each do |k, v|
14
+ @include_all_default_attributes = !!v if k.to_s =~ /default/ || v.to_s =~ /default/
15
+ @include_all_custom_attributes = !!v if k.to_s =~ /custom/ || v.to_s =~ /custom/
16
+ @include_all_system_attributes = !!v if k.to_s =~ /system/ || v.to_s =~ /system/
17
+ end
18
+ end
19
+ end
20
+
21
+ xml_accessor :id, as: Integer
22
+ xml_accessor :name
23
+ xml_accessor :static_attribute_id, as: Integer
24
+ xml_accessor :is_key?
25
+ xml_accessor :attribute_type
26
+ xml_accessor :data_type
27
+ xml_accessor :last_modified_date # was LastUpdatedDate
28
+ end
29
+
30
+ class AttributeDescription < Attribute; end
31
+ end
@@ -0,0 +1,26 @@
1
+ module Kampainer
2
+
3
+ # ListContactGroups
4
+ class ContactGroupDescription < SchemaObject
5
+ xml_reader :type
6
+ xml_reader :id
7
+ xml_reader :name
8
+ xml_reader :description
9
+ xml_reader :status
10
+ xml_reader :last_modified_date
11
+ xml_reader :project_id
12
+ end
13
+
14
+ # CreateContactGroups
15
+ class ContactGroup < ContactGroupDescription
16
+ attr_accessor :type, :id, :name, :description
17
+ end
18
+
19
+ class ContactGroupId < SchemaObject
20
+ xml_reader :id, from: :content
21
+ end
22
+
23
+ class ContactGroupIds < SchemaCollection
24
+ xml_accessor :collection, as: [Integer], from: 'int'
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ module Kampainer
2
+ # See https://ws.campaigner.com/2013/01/contactmanagement.asmx?WSDL
3
+ class ContactManager
4
+ attr_reader :session
5
+
6
+ def initialize(session)
7
+ @session = session
8
+ end
9
+
10
+ # @option params [String] attribute_name
11
+ # @option params [String] attribute_type
12
+ # @option params [String|??] default_value
13
+ # @return [Integer] attribute id
14
+ def create_update_attribute(params)
15
+ params.transform_keys! { |key| key.to_s.camelcase(:lower) }
16
+ call('CreateUpdateAttribute', params)[0].to_i
17
+ end
18
+
19
+ def delete_attribute(id)
20
+ call('DeleteAttribute', id: id)
21
+ end
22
+
23
+ # @param *keys One or more keys of contacts to delete.
24
+ def delete_contacts(*keys)
25
+ contact_keys = ArrayOfContactKey[keys]
26
+ call('DeleteContacts', contact_keys.to_xml)[0].to_a
27
+ end
28
+
29
+ # @param *keys One or more contact keys.
30
+ def get_contacts(*keys)
31
+ filter = ContactsDataFilter.new(keys: ContactKeys[keys])
32
+ attribute_filter = ContactInformationFilter.new(include: 'static,custom,groups')
33
+ call('GetContacts', filter.to_xml, attribute_filter.to_xml)[0].to_a
34
+ end
35
+
36
+ # @return [ContactKey]
37
+ def immediate_upload(contact)
38
+ contact.key ||= ContactKey.new(unique_identifier: contact.email_address, id: 0)
39
+ contacts = Contacts.new(Array(contact))
40
+ call('ImmediateUpload', contacts.to_xml)[0].key
41
+ end
42
+
43
+ # @option filters [Boolean] include_all_default_attributes
44
+ # @option filters [Boolean] include_all_custom_attributes
45
+ # @option filters [Boolean] include_all_system_attributes
46
+ def list_attributes(filters)
47
+ filter_xml = Attribute::Filter.new(filters).to_xml
48
+ call('ListAttributes', filter_xml)
49
+ end
50
+
51
+ def list_test_contacts
52
+ call('ListTestContacts')
53
+ end
54
+
55
+ protected
56
+
57
+ def call(action_name, *params)
58
+ session.call("#{session.base_url}2013/01/contactmanagement.asmx", action_name, *params)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ require 'benchmark'
2
+ require 'net/http'
3
+
4
+ module Kampainer
5
+
6
+ # Generic Net::HTTP wrapper that logs the request and response.
7
+ class HttpRequest
8
+ attr_reader :uri, :request
9
+ attr_reader :headers
10
+ attr_reader :logger
11
+
12
+ def initialize(method, path, headers, logger)
13
+ @headers = headers
14
+ @logger = logger
15
+ @uri = URI(path)
16
+ @request = Net::HTTP.const_get(method.capitalize).new(uri)
17
+ end
18
+
19
+ # Create HTTP request with provided headers.
20
+ # Invoke request over HTTPS.
21
+ # Return response on success or log failure and throw error.
22
+ def send(action_name, data)
23
+ headers.each { |k, v| request[k] = v }
24
+ request.body = data
25
+ response = log_request_response(data) do
26
+ https(uri).request(request)
27
+ end
28
+ fail_unless_expected_response response, Net::HTTPSuccess
29
+ response.body
30
+ end
31
+
32
+ def https(uri)
33
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
34
+ http.use_ssl = true
35
+ end
36
+ end
37
+
38
+ # Log URI, method, data
39
+ # Start timer.
40
+ # Yield URI, method, data.
41
+ # Log response and time taken.
42
+ def log_request_response(data = nil)
43
+ logger.info "[#{self.class.name}] request = #{request.method} #{uri}"
44
+ logger.info "[#{self.class.name}] headers = #{headers.inspect}"
45
+ logger.info "[#{self.class.name}] data = #{data}"
46
+ response = nil
47
+ tms = Benchmark.measure do
48
+ response = yield
49
+ end
50
+ logger.info("[#{self.class.name}] response (#{ms(tms)}ms): #{response.inspect} #{response.body}")
51
+ response
52
+ end
53
+
54
+ def ms(tms)
55
+ (tms.real*1000).round(3)
56
+ end
57
+
58
+ class UnexpectedHttpResponse < StandardError
59
+ attr_reader :response
60
+
61
+ def initialize(response)
62
+ @response = response
63
+ super "#{response.message} (#{response.code}): #{response.body}"
64
+ end
65
+ end
66
+
67
+ def fail_unless_expected_response(response, *allowed_responses)
68
+ unless allowed_responses.any? { |allowed| response.is_a?(allowed) }
69
+ logger.error "#{response.inspect}: #{response.body}"
70
+ raise UnexpectedHttpResponse, response
71
+ end
72
+ response
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,36 @@
1
+ module Kampainer
2
+ # See https://ws.campaigner.com/2013/01/listmanagement.asmx?WSDL
3
+ class ListManager
4
+ attr_reader :session
5
+
6
+ def initialize(session)
7
+ @session = session
8
+ end
9
+
10
+ # @param [Hash] contact_group
11
+ def create_update_contact_group(contact_group)
12
+ params = [
13
+ { contactGroupType: contact_group.type },
14
+ { contactGroupId: contact_group.id || 0 },
15
+ { name: contact_group.name },
16
+ { description: contact_group.description },
17
+ { isTempGroup: 'false' },
18
+ ]
19
+ call('CreateUpdateContactGroups', *params)[0].id
20
+ end
21
+
22
+ def delete_contact_groups(*ids)
23
+ call('DeleteContactGroups', ContactGroupIds.new(ids).to_xml)
24
+ end
25
+
26
+ def list_contact_groups
27
+ call('ListContactGroups')
28
+ end
29
+
30
+ protected
31
+
32
+ def call(action_name, *params)
33
+ session.call("#{session.base_url}2013/01/listmanagement.asmx", action_name, *params)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ class RequestBody < Nokogiri::XML::Builder
2
+ def initialize(action_name, username, password, request)
3
+ super()
4
+ __send__('soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
5
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
6
+ 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'}) do |root|
7
+ root.__send__('soap12:Body') do |body|
8
+ body.__send__(action_name, xmlns: 'https://ws.campaigner.com/2013/01') do |doc|
9
+ doc.authentication do
10
+ doc.Username(username)
11
+ doc.Password(password)
12
+ end
13
+ request.each do |node|
14
+ if node.is_a?(Hash)
15
+ node.each { |key, val| body.send(key, val) }
16
+ else
17
+ body.parent << node
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Kampainer
2
+ class ResponseHeader < SchemaObject
3
+ xml_accessor :error_flag?
4
+ xml_accessor :return_code
5
+ xml_accessor :return_message
6
+
7
+ def message
8
+ "#{return_code}: #{return_message}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ require 'forwardable'
2
+ require 'roxml'
3
+
4
+ module Kampainer
5
+ class SchemaObject
6
+ include ROXML
7
+ xml_convention :camelcase
8
+
9
+ def initialize(options = {})
10
+ options.each_pair { |k, v| send("#{k}=", v) }
11
+ end
12
+
13
+ def inspect
14
+ variables = instance_variables - [:@roxml_references]
15
+ s = variables.map { |iv| "#{iv}: #{instance_variable_get(iv).inspect}" }.join(', ')
16
+ "<#{self.class.name}: #{s} >"
17
+ end
18
+ end
19
+
20
+ # https://stackoverflow.com/questions/2844106/ruby-roxml-how-to-get-an-array-to-render-its-xml
21
+ class SchemaCollection < SchemaObject
22
+ extend Forwardable
23
+
24
+ class << self
25
+ alias_method :[], :new
26
+ end
27
+
28
+ def initialize(collection = [])
29
+ @collection = collection
30
+ end
31
+ def_delegators :collection, :length, :<<, :to_a
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ require 'logger'
2
+ require 'nokogiri'
3
+
4
+ module Kampainer
5
+ class Session
6
+ attr_reader :base_url
7
+ attr_reader :username, :password
8
+ attr_accessor :logger
9
+
10
+ XMLNS = 'https://ws.campaigner.com/2013/01'.freeze
11
+
12
+ def initialize(username:, password:, base_url: nil, logger: nil)
13
+ @base_url = base_url || 'https://ws.campaigner.com/'
14
+ @username = username
15
+ @password = password
16
+ @logger = logger || Logger.new('/dev/null')
17
+ end
18
+
19
+ def call(url, action_name, *params)
20
+ xml_request = build_xml_request(action_name, *params)
21
+ commit(url, action_name, xml_request)
22
+ end
23
+
24
+ protected
25
+
26
+ def build_xml_request(action_name, *nodes)
27
+ RequestBody.new(action_name, username, password, nodes).to_xml
28
+ end
29
+
30
+ def commit(url, action_name, xml_request)
31
+ headers = {
32
+ 'SOAPAction' => "#{XMLNS}/#{action_name}",
33
+ 'Content-Type' => 'text/xml; charset=utf-8',
34
+ 'Content-Length' => xml_request.size.to_s
35
+ }
36
+ request = HttpRequest.new('POST', url, headers, logger)
37
+ parse request.send("#{XMLNS}/#{action_name}", xml_request)
38
+ end
39
+
40
+ def parse(xml)
41
+ xmldoc = Nokogiri::XML(xml)
42
+
43
+ header = xmldoc.xpath('//soap:Header/*')
44
+ response_header = Kampainer.const_get(header[0].name).from_xml(header[0].to_s)
45
+ raise Error, response_header.message if response_header.error_flag?
46
+
47
+ body = xmldoc.xpath('//soap:Body/*[1]')[0]
48
+
49
+ response = []
50
+
51
+ body.xpath('*')&.each do |node|
52
+ node.elements.each do |childnode|
53
+ response << Kampainer.const_get(childnode.name).from_xml(childnode.to_s)
54
+ rescue
55
+ response << childnode.inner_text
56
+ end
57
+ end
58
+
59
+ response
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ module Kampainer
2
+ VERSION = "0.1.0"
3
+ end
data/lib/kampainer.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "kampainer/version"
2
+
3
+ require 'kampainer/schema_object'
4
+ require 'kampainer/contact_attribute'
5
+ require 'kampainer/contact_group'
6
+ require 'kampainer/contact'
7
+ require 'kampainer/contact_manager'
8
+ require 'kampainer/campaign'
9
+ require 'kampainer/campaign_manager'
10
+ require 'kampainer/list_manager'
11
+
12
+ require 'kampainer/http_request'
13
+ require "kampainer/session"
14
+ require 'kampainer/request_body'
15
+ require 'kampainer/response'
16
+
17
+ module Kampainer
18
+ class Error < StandardError; end
19
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kampainer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piers Chambers
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: roxml
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.17'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.17'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description:
84
+ email:
85
+ - piers@varyonic.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".circleci/config.yml"
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - kampainer.gemspec
100
+ - lib/kampainer.rb
101
+ - lib/kampainer/campaign.rb
102
+ - lib/kampainer/campaign_manager.rb
103
+ - lib/kampainer/contact.rb
104
+ - lib/kampainer/contact_attribute.rb
105
+ - lib/kampainer/contact_group.rb
106
+ - lib/kampainer/contact_manager.rb
107
+ - lib/kampainer/http_request.rb
108
+ - lib/kampainer/list_manager.rb
109
+ - lib/kampainer/request_body.rb
110
+ - lib/kampainer/response.rb
111
+ - lib/kampainer/schema_object.rb
112
+ - lib/kampainer/session.rb
113
+ - lib/kampainer/version.rb
114
+ homepage: https://github.com/varyonic/kampainer
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.7.6
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Unofficial Ruby wrapper for the Campaigner Elements API.
138
+ test_files: []