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 +7 -0
- data/.circleci/config.yml +53 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kampainer.gemspec +31 -0
- data/lib/kampainer/campaign.rb +40 -0
- data/lib/kampainer/campaign_manager.rb +32 -0
- data/lib/kampainer/contact.rb +149 -0
- data/lib/kampainer/contact_attribute.rb +31 -0
- data/lib/kampainer/contact_group.rb +26 -0
- data/lib/kampainer/contact_manager.rb +61 -0
- data/lib/kampainer/http_request.rb +75 -0
- data/lib/kampainer/list_manager.rb +36 -0
- data/lib/kampainer/request_body.rb +24 -0
- data/lib/kampainer/response.rb +11 -0
- data/lib/kampainer/schema_object.rb +33 -0
- data/lib/kampainer/session.rb +62 -0
- data/lib/kampainer/version.rb +3 -0
- data/lib/kampainer.rb +19 -0
- metadata +138 -0
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
data/.rspec
ADDED
data/Gemfile
ADDED
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
|
+
[](https://badge.fury.io/rb/kampainer)
|
4
|
+
[](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
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
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,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
|
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: []
|