congress_forms 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.example +14 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +684 -0
- data/README.md +152 -0
- data/Rakefile +6 -0
- data/bin/congress_forms +100 -0
- data/bin/console +9 -0
- data/bin/setup +8 -0
- data/congress_forms.gemspec +30 -0
- data/lib/congress_forms/actions.rb +160 -0
- data/lib/congress_forms/cwc_form.rb +62 -0
- data/lib/congress_forms/form.rb +36 -0
- data/lib/congress_forms/repo.rb +95 -0
- data/lib/congress_forms/version.rb +3 -0
- data/lib/congress_forms/web_form.rb +102 -0
- data/lib/congress_forms.rb +85 -0
- data/lib/cwc/bad_request.rb +13 -0
- data/lib/cwc/client.rb +171 -0
- data/lib/cwc/extensions/hash.rb +20 -0
- data/lib/cwc/fixtures.rb +102 -0
- data/lib/cwc/message.rb +184 -0
- data/lib/cwc/office.rb +29 -0
- data/lib/cwc/topic_codes.rb +37 -0
- data/lib/cwc.rb +2 -0
- metadata +201 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
module CongressForms
|
2
|
+
class WebForm < Form
|
3
|
+
attr_accessor :bioguide, :actions
|
4
|
+
attr_accessor :success_status, :success_content
|
5
|
+
attr_accessor :updated_at
|
6
|
+
|
7
|
+
def self.parse(spec, attrs={})
|
8
|
+
yaml = YAML.load(spec)
|
9
|
+
|
10
|
+
actions = yaml.dig("contact_form", "steps").map do |step|
|
11
|
+
Actions.build(step)
|
12
|
+
end.flatten
|
13
|
+
|
14
|
+
new(
|
15
|
+
actions,
|
16
|
+
attrs.merge(
|
17
|
+
bioguide: yaml["bioguide"],
|
18
|
+
success_status:
|
19
|
+
yaml.dig("contact_form", "success", "headers", "status"),
|
20
|
+
success_content:
|
21
|
+
yaml.dig("contact_form", "success", "body", "contains"),
|
22
|
+
)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.create_browser
|
27
|
+
if ENV["HEADLESS"] == "0"
|
28
|
+
Capybara::Session.new(:chrome)
|
29
|
+
else
|
30
|
+
Capybara::Session.new(:headless_chrome)
|
31
|
+
end.tap do |browser|
|
32
|
+
browser.current_window.resize_to(1920, 1080)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(actions = [], bioguide: nil,
|
37
|
+
success_status: nil,
|
38
|
+
success_content: nil,
|
39
|
+
updated_at: nil)
|
40
|
+
self.bioguide = bioguide
|
41
|
+
self.actions = actions
|
42
|
+
self.success_status = success_status
|
43
|
+
self.success_content = success_content
|
44
|
+
self.updated_at = updated_at
|
45
|
+
end
|
46
|
+
|
47
|
+
def required_params
|
48
|
+
required_actions = actions.dup
|
49
|
+
|
50
|
+
required_actions.select!(&:required?)
|
51
|
+
required_actions.select!(&:placeholder_value?)
|
52
|
+
|
53
|
+
required_actions.map do |action|
|
54
|
+
{
|
55
|
+
value: action.value,
|
56
|
+
max_length: action.max_length,
|
57
|
+
options: action.select_options
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fill(values, browser: self.class.create_browser, validate_only: false)
|
63
|
+
log("#{bioguide} fill")
|
64
|
+
|
65
|
+
actions.each do |action|
|
66
|
+
break if action.submit? && validate_only
|
67
|
+
|
68
|
+
action.perform(browser, values)
|
69
|
+
end
|
70
|
+
|
71
|
+
log("done: success")
|
72
|
+
rescue Capybara::CapybaraError, Selenium::WebDriver::Error::WebDriverError => e
|
73
|
+
log("done: error")
|
74
|
+
|
75
|
+
error = Error.new(e.message)
|
76
|
+
error.set_backtrace(e.backtrace)
|
77
|
+
|
78
|
+
if screenshot = ENV["CONGRESS_FORMS_SCREENSHOT_LOCATION"]
|
79
|
+
error.screenshot = "#{screenshot}/#{SecureRandom.hex(16)}.png"
|
80
|
+
browser.save_screenshot(error.screenshot, full: true)
|
81
|
+
end
|
82
|
+
|
83
|
+
raise error
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def log(message)
|
89
|
+
if defined?(Rails)
|
90
|
+
Rails.logger.debug(message)
|
91
|
+
end
|
92
|
+
|
93
|
+
if defined?(Raven)
|
94
|
+
unless Raven.context.extra.key?(:fill_log)
|
95
|
+
Raven.extra_context(fill_log: "")
|
96
|
+
end
|
97
|
+
|
98
|
+
Raven.context.extra[:fill_log] << message << "\n"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
require "capybara"
|
4
|
+
require "selenium/webdriver"
|
5
|
+
|
6
|
+
require "congress_forms/version"
|
7
|
+
|
8
|
+
require "cwc"
|
9
|
+
|
10
|
+
unless ENV["CWC_API_KEY"].nil?
|
11
|
+
Cwc::Client.configure(
|
12
|
+
api_key: ENV["CWC_API_KEY"],
|
13
|
+
host: ENV["CWC_HOST"],
|
14
|
+
delivery_agent: ENV["CWC_DELIVERY_AGENT"],
|
15
|
+
delivery_agent_ack_email: ENV["CWC_DELIVERY_AGENT_ACK_EMAIL"],
|
16
|
+
delivery_agent_contact_name: ENV["CWC_DELIVERY_AGENT_CONTACT_NAME"],
|
17
|
+
delivery_agent_contact_email: ENV["CWC_DELIVERY_AGENT_CONTACT_EMAIL"],
|
18
|
+
delivery_agent_contact_phone: ENV["CWC_DELIVERY_AGENT_CONTACT_PHONE"]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
Capybara.register_driver :chrome do
|
24
|
+
Capybara::Selenium::Driver.new(nil, browser: :chrome)
|
25
|
+
end
|
26
|
+
|
27
|
+
Capybara.register_driver :headless_chrome do
|
28
|
+
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
|
29
|
+
chromeOptions: {
|
30
|
+
args: %w(no-sandbox disable-gpu window-size=1200,1400).tap do |o|
|
31
|
+
o.push("--headless") unless ENV["HEADLESS"] == "0"
|
32
|
+
end
|
33
|
+
}
|
34
|
+
)
|
35
|
+
|
36
|
+
Capybara::Selenium::Driver.new(
|
37
|
+
nil,
|
38
|
+
browser: :chrome,
|
39
|
+
desired_capabilities: capabilities
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
module CongressForms
|
44
|
+
Error = Class.new(Exception) do
|
45
|
+
attr_accessor :screenshot
|
46
|
+
end
|
47
|
+
|
48
|
+
autoload :Form, "congress_forms/form"
|
49
|
+
autoload :WebForm, "congress_forms/web_form"
|
50
|
+
autoload :CwcForm, "congress_forms/cwc_form"
|
51
|
+
autoload :Actions, "congress_forms/actions"
|
52
|
+
|
53
|
+
autoload :Repo, "congress_forms/repo"
|
54
|
+
|
55
|
+
@@contact_congress_remote = "https://github.com/unitedstates/contact-congress.git"
|
56
|
+
|
57
|
+
|
58
|
+
def self.contact_congress_remote=(location)
|
59
|
+
@@contact_congress_remote = location
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.contact_congress_remote
|
63
|
+
@@contact_congress_remote
|
64
|
+
end
|
65
|
+
|
66
|
+
@@contact_congress_repository = nil
|
67
|
+
|
68
|
+
def self.contact_congress_repository=(location)
|
69
|
+
@@contact_congress_repository = location
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.contact_congress_repository
|
73
|
+
@@contact_congress_repository
|
74
|
+
end
|
75
|
+
|
76
|
+
@@auto_update_contact_congress = true
|
77
|
+
|
78
|
+
def self.auto_update_contact_congress=(auto_update)
|
79
|
+
@@auto_update_contact_congress = auto_update
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.auto_update_contact_congress?
|
83
|
+
@@auto_update_contact_congress
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cwc
|
2
|
+
class BadRequest < Exception
|
3
|
+
attr_reader :original_exception, :errors
|
4
|
+
|
5
|
+
def initialize(e)
|
6
|
+
@original_exception = e
|
7
|
+
@errors = Nokogiri::XML(e.response.body).xpath("//Error").map(&:content)
|
8
|
+
if @errors.empty?
|
9
|
+
@errors << e.response.body
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/cwc/client.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
require "rest-client"
|
5
|
+
|
6
|
+
require "cwc/office"
|
7
|
+
require "cwc/message"
|
8
|
+
require "cwc/topic_codes"
|
9
|
+
require "cwc/bad_request"
|
10
|
+
require "cwc/fixtures"
|
11
|
+
|
12
|
+
module Cwc
|
13
|
+
class Client
|
14
|
+
attr_accessor :options
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def default_client_configuration=(x)
|
18
|
+
@default_client_configuration = x
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_client_configuration
|
22
|
+
@default_client_configuration ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure(options)
|
26
|
+
self.default_client_configuration = options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Required options keys
|
31
|
+
# api_key String
|
32
|
+
# delivery_agent String, must match the api key owner
|
33
|
+
# delivery_agent_ack_email String
|
34
|
+
# delivery_agent_contact_name String
|
35
|
+
# delivery_agent_contact_email String
|
36
|
+
# delivery_agent_contact_phone String, format xxx-xxx-xxxx
|
37
|
+
def initialize(options={})
|
38
|
+
options = self.class.default_client_configuration.merge(options)
|
39
|
+
self.options = {
|
40
|
+
api_key: options.fetch(:api_key),
|
41
|
+
host: options.fetch(:host),
|
42
|
+
|
43
|
+
delivery_agent: {
|
44
|
+
name: options.fetch(:delivery_agent),
|
45
|
+
ack_email: options.fetch(:delivery_agent_ack_email),
|
46
|
+
contact_name: options.fetch(:delivery_agent_contact_name),
|
47
|
+
contact_email: options.fetch(:delivery_agent_contact_email),
|
48
|
+
contact_phone: options.fetch(:delivery_agent_contact_phone)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Params format
|
54
|
+
# {
|
55
|
+
# campaign_id: String
|
56
|
+
# recipient: {
|
57
|
+
# member_office: String
|
58
|
+
# is_response_requested: Boolean ?
|
59
|
+
# newsletter_opt_in: Boolean ?
|
60
|
+
# },
|
61
|
+
# organization: {
|
62
|
+
# name: String ?
|
63
|
+
# contact: {
|
64
|
+
# name: String ?
|
65
|
+
# email: String ?
|
66
|
+
# phone: String ?
|
67
|
+
# about: String ?
|
68
|
+
# }
|
69
|
+
# },
|
70
|
+
# constituent: {
|
71
|
+
# prefix: String
|
72
|
+
# first_name: String
|
73
|
+
# middle_name: String ?
|
74
|
+
# last_name: String
|
75
|
+
# suffix: String ?
|
76
|
+
# title: String ?
|
77
|
+
# organization: String ?
|
78
|
+
# address: Array[String]
|
79
|
+
# city: String
|
80
|
+
# state_abbreviation: String
|
81
|
+
# zip: String
|
82
|
+
# phone: String ?
|
83
|
+
# address_validation: Boolean ?
|
84
|
+
# email: String
|
85
|
+
# email_validation: Boolean ?
|
86
|
+
# },
|
87
|
+
# message: {
|
88
|
+
# subject: String
|
89
|
+
# library_of_congress_topics: Array[String], drawn from Cwc::TopicCodes. Must give at least 1.
|
90
|
+
# bills: { Array[Hash]
|
91
|
+
# congress: Integer ?
|
92
|
+
# type_abbreviation: String
|
93
|
+
# number: Integer
|
94
|
+
# },
|
95
|
+
# pro_or_con: "pro" or "con" ?
|
96
|
+
# organization_statement: String ?
|
97
|
+
# constituent_message: String ?
|
98
|
+
# more_info: String (URL) ?
|
99
|
+
# }
|
100
|
+
#
|
101
|
+
# Use message[:constituent_message] for personal message,
|
102
|
+
# or message[:organization_statement] for campaign message
|
103
|
+
# At least one of these must be given
|
104
|
+
def create_message(params)
|
105
|
+
Cwc::Message.new.tap do |message|
|
106
|
+
message.delivery[:agent] = options.fetch(:delivery_agent)
|
107
|
+
message.delivery[:organization] = params.fetch(:organization, {})
|
108
|
+
message.delivery[:campaign_id] = params.fetch(:campaign_id)
|
109
|
+
|
110
|
+
message.recipient.merge!(params.fetch(:recipient))
|
111
|
+
message.constituent.merge!(params.fetch(:constituent))
|
112
|
+
message.message.merge!(params.fetch(:message))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def deliver(message)
|
117
|
+
post action("/v2/message"), message.to_xml
|
118
|
+
true
|
119
|
+
rescue RestClient::BadRequest => e
|
120
|
+
raise BadRequest.new(e)
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate(message)
|
124
|
+
post action("/v2/validate"), message.to_xml
|
125
|
+
true
|
126
|
+
rescue RestClient::BadRequest => e
|
127
|
+
raise BadRequest.new(e)
|
128
|
+
end
|
129
|
+
|
130
|
+
def office_supported?(office_code)
|
131
|
+
!offices.find{ |office| office.code == office_code }.nil?
|
132
|
+
end
|
133
|
+
|
134
|
+
def required_json(o={})
|
135
|
+
Cwc::RequiredJson.merge(o)
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def action(action)
|
141
|
+
host = options[:host].sub(/\/+$/, '')
|
142
|
+
action = action.sub(/^\/+/, '')
|
143
|
+
"#{host}/#{action}?apikey=#{options[:api_key]}"
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def get(url)
|
149
|
+
verify = !["false", "0"].include?(ENV["CWC_VERIFY_SSL"])
|
150
|
+
headers = { host: ENV["CWC_HOST_HEADER"] }.reject{ |_, v| v.nil? }
|
151
|
+
RestClient::Resource.new(url, verify_ssl: verify).get(headers)
|
152
|
+
end
|
153
|
+
|
154
|
+
def post(url, message)
|
155
|
+
verify = !["false", "0"].include?(ENV["CWC_VERIFY_SSL"])
|
156
|
+
headers = { content_type: :xml, host: ENV["CWC_HOST_HEADER"] }.
|
157
|
+
reject{ |_, v| v.nil? }
|
158
|
+
RestClient::Resource.new(url, verify_ssl: verify).
|
159
|
+
post(message, headers)
|
160
|
+
end
|
161
|
+
|
162
|
+
def offices
|
163
|
+
if options[:host] =~ %r{^https://cwc.house.gov}
|
164
|
+
Cwc::OfficeCodes.map{ |code| Office.new(code) }
|
165
|
+
else
|
166
|
+
response = get action("/offices")
|
167
|
+
JSON.parse(response.body).map{ |code| Office.new(code) }
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Hash
|
2
|
+
unless instance_methods.include?(:dig)
|
3
|
+
def dig(key, *args)
|
4
|
+
obj = self[key]
|
5
|
+
if args.empty?
|
6
|
+
obj
|
7
|
+
elsif !obj.nil?
|
8
|
+
obj.dig(*args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def dig!(key, *keys)
|
14
|
+
if keys.empty?
|
15
|
+
fetch(key)
|
16
|
+
else
|
17
|
+
fetch(key).dig!(*keys)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/cwc/fixtures.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Cwc
|
2
|
+
Prefixes = [
|
3
|
+
"Mr.", "Mrs.", "Ms.", "Mr. and Mrs.", "Miss", "Dr.", "Dr. and Mrs.",
|
4
|
+
"Dr. and Mr.", "Admiral", "Captain", "Chief Master Sergeant", "Colonel",
|
5
|
+
"Commander", "Corporal", "Father", "Lieutenant", "Lieutenant Colonel",
|
6
|
+
"Master Sergeant", "Reverend", "Sergeant", "Second Lieutenant", "Sergeant Major",
|
7
|
+
"Sister", "Technical Sergeant"
|
8
|
+
]
|
9
|
+
|
10
|
+
RequiredJson = {
|
11
|
+
"required_actions" => [
|
12
|
+
{ value: "$NAME_PREFIX", max_length: nil, options: Cwc::Prefixes },
|
13
|
+
{ value: "$NAME_FIRST", max_length: nil, options: nil },
|
14
|
+
{ value: "$NAME_LAST", max_length: nil, options: nil },
|
15
|
+
{ value: "$ADDRESS_STREET", max_length: nil, options: nil },
|
16
|
+
{ value: "$ADDRESS_CITY", max_length: nil, options: nil },
|
17
|
+
{ value: "$ADDRESS_ZIP5", max_length: nil, options: nil },
|
18
|
+
{ value: "$EMAIL", max_length: nil, options: nil },
|
19
|
+
{ value: "$SUBJECT", max_length: nil, options: nil },
|
20
|
+
{ value: "$MESSAGE", max_length: nil, options: nil },
|
21
|
+
{
|
22
|
+
value: "$ADDRESS_STATE_POSTAL_ABBREV", max_length: nil, options: [
|
23
|
+
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL", "GA",
|
24
|
+
"HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA",
|
25
|
+
"MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY",
|
26
|
+
"NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX",
|
27
|
+
"UT", "VT", "VA", "WA", "WV", "WI", "WY"
|
28
|
+
]
|
29
|
+
},
|
30
|
+
{ value: "$TOPIC", max_length: nil, options: Cwc::TopicCodes}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
|
34
|
+
# this method was doing a request to a static JSON file on the CWC server.
|
35
|
+
# When their server temporarily went down, it brought down our form requests with it.
|
36
|
+
# We should probably update this at some point.
|
37
|
+
OfficeCodes = [
|
38
|
+
"HAK00","HAL01","HAL02","HAL03","HAL04","HAL05","HAL06",
|
39
|
+
"HAL07","HAR01","HAR02","HAR03","HAR04","HAZ01","HAZ02",
|
40
|
+
"HAZ03","HAZ04","HAZ05","HAZ06","HAZ07","HAZ08","HAZ09",
|
41
|
+
"HCA01","HCA02","HCA03","HCA04","HCA05","HCA06","HCA07",
|
42
|
+
"HCA08","HCA09","HCA10","HCA11","HCA12","HCA13","HCA14",
|
43
|
+
"HCA15","HCA16","HCA17","HCA18","HCA19","HCA20","HCA21",
|
44
|
+
"HCA22","HCA23","HCA24","HCA25","HCA26","HCA27","HCA28",
|
45
|
+
"HCA29","HCA30","HCA31","HCA32","HCA33","HCA34","HCA35",
|
46
|
+
"HCA36","HCA37","HCA38","HCA39","HCA40","HCA41","HCA42",
|
47
|
+
"HCA43","HCA44","HCA45","HCA46","HCA47","HCA48","HCA49",
|
48
|
+
"HCA50","HCA51","HCA52","HCA53","HCO01","HCO02","HCO03",
|
49
|
+
"HCO04","HCO05","HCO06","HCO07","HCT01","HCT02","HCT03",
|
50
|
+
"HCT04","HCT05","HDC00","HDE00","HFL01","HFL02","HFL03",
|
51
|
+
"HFL04","HFL05","HFL06","HFL07","HFL08","HFL09","HFL10",
|
52
|
+
"HFL11","HFL12","HFL13","HFL14","HFL15","HFL16","HFL17",
|
53
|
+
"HFL18","HFL19","HFL20","HFL21","HFL22","HFL23","HFL24",
|
54
|
+
"HFL25","HFL26","HFL27","HGA01","HGA02","HGA03","HGA04",
|
55
|
+
"HGA05","HGA06","HGA07","HGA08","HGA09","HGA10","HGA11",
|
56
|
+
"HGA12","HGA13","HGA14","HGU00","HHI01","HHI02","HIA01",
|
57
|
+
"HIA02","HIA03","HIA04","HID01","HID02","HIL01","HIL02",
|
58
|
+
"HIL03","HIL04","HIL05","HIL06","HIL07","HIL08","HIL09",
|
59
|
+
"HIL10","HIL11","HIL12","HIL13","HIL14","HIL15","HIL16",
|
60
|
+
"HIL17","HIL18","HIN01","HIN02","HIN03","HIN04","HIN05",
|
61
|
+
"HIN06","HIN07","HIN08","HIN09","HKS01","HKS02","HKS03",
|
62
|
+
"HKS04","HKY01","HKY02","HKY03","HKY04","HKY05","HKY06",
|
63
|
+
"HLA01","HLA02","HLA03","HLA04","HLA05","HLA06","HMA01",
|
64
|
+
"HMA02","HMA03","HMA04","HMA05","HMA06","HMA07","HMA08",
|
65
|
+
"HMA09","HMD01","HMD02","HMD03","HMD04","HMD05","HMD06",
|
66
|
+
"HMD07","HMD08","HME01","HME02","HMI01","HMI02","HMI03",
|
67
|
+
"HMI04","HMI05","HMI06","HMI07","HMI08","HMI09","HMI10",
|
68
|
+
"HMI11","HMI12","HMI13","HMI14","HMN01","HMN02","HMN03",
|
69
|
+
"HMN04","HMN05","HMN06","HMN07","HMN08","HMO01","HMO02",
|
70
|
+
"HMO03","HMO04","HMO05","HMO06","HMO07","HMO08","HMS01",
|
71
|
+
"HMS02","HMS03","HMS04","HMT00","HNC01","HNC02","HNC03",
|
72
|
+
"HNC04","HNC05","HNC06","HNC07","HNC08","HNC09","HNC10",
|
73
|
+
"HNC11","HNC12","HNC13","HND00","HNE01","HNE02","HNE03",
|
74
|
+
"HNH01","HNH02","HNJ01","HNJ02","HNJ03","HNJ04","HNJ05",
|
75
|
+
"HNJ06","HNJ07","HNJ08","HNJ09","HNJ10","HNJ11","HNJ12",
|
76
|
+
"HNM01","HNM02","HNM03","HNV01","HNV02","HNV03","HNV04",
|
77
|
+
"HNY01","HNY02","HNY03","HNY04","HNY05","HNY06","HNY07",
|
78
|
+
"HNY08","HNY09","HNY10","HNY11","HNY12","HNY13","HNY14",
|
79
|
+
"HNY15","HNY16","HNY17","HNY18","HNY19","HNY20","HNY21",
|
80
|
+
"HNY22","HNY23","HNY24","HNY25","HNY26","HNY27","HOH01",
|
81
|
+
"HOH02","HOH03","HOH04","HOH05","HOH06","HOH07","HOH08",
|
82
|
+
"HOH09","HOH10","HOH11","HOH12","HOH13","HOH14","HOH15",
|
83
|
+
"HOH16","HOK01","HOK02","HOK03","HOK04","HOK05","HOR01",
|
84
|
+
"HOR02","HOR03","HOR04","HOR05","HPA01","HPA02","HPA03",
|
85
|
+
"HPA04","HPA05","HPA06","HPA07","HPA08","HPA09","HPA10",
|
86
|
+
"HPA11","HPA12","HPA13","HPA14","HPA15","HPA16","HPA17",
|
87
|
+
"HPA18","HPR00","HRI01","HRI02","HSC01","HSC02","HSC03",
|
88
|
+
"HSC04","HSC05","HSC06","HSC07","HSD00","HTN01","HTN02",
|
89
|
+
"HTN03","HTN04","HTN05","HTN06","HTN07","HTN08","HTN09",
|
90
|
+
"HTX01","HTX02","HTX03","HTX04","HTX05","HTX06","HTX07",
|
91
|
+
"HTX08","HTX09","HTX10","HTX11","HTX12","HTX13","HTX14",
|
92
|
+
"HTX15","HTX16","HTX17","HTX18","HTX19","HTX20","HTX21",
|
93
|
+
"HTX22","HTX23","HTX24","HTX25","HTX26","HTX27","HTX28",
|
94
|
+
"HTX29","HTX30","HTX31","HTX32","HTX33","HTX34","HTX35",
|
95
|
+
"HTX36","HUT01","HUT02","HUT03","HUT04","HVA01","HVA02",
|
96
|
+
"HVA03","HVA04","HVA05","HVA06","HVA07","HVA08","HVA09",
|
97
|
+
"HVA10","HVA11","HVI00","HVT00","HWA01","HWA02","HWA03",
|
98
|
+
"HWA04","HWA05","HWA06","HWA07","HWA08","HWA09","HWA10",
|
99
|
+
"HWI01","HWI02","HWI03","HWI04","HWI05","HWI06","HWI07",
|
100
|
+
"HWI08","HWV01","HWV02","HWV03","HWY00","MP00"
|
101
|
+
]
|
102
|
+
end
|
data/lib/cwc/message.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
|
2
|
+
require "securerandom"
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "cwc/extensions/hash"
|
6
|
+
|
7
|
+
module Cwc
|
8
|
+
class Message
|
9
|
+
attr_accessor :delivery
|
10
|
+
attr_accessor :recipient
|
11
|
+
attr_accessor :constituent
|
12
|
+
attr_accessor :message
|
13
|
+
|
14
|
+
attr_accessor :delivery_id
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
self.delivery = {}
|
18
|
+
self.recipient = {}
|
19
|
+
self.constituent = {}
|
20
|
+
self.message = {}
|
21
|
+
|
22
|
+
self.delivery_id = SecureRandom.random_number(36**33).to_s(36).rjust(12, "0")[0, 32]
|
23
|
+
end
|
24
|
+
|
25
|
+
def delivery_date
|
26
|
+
Time.now.strftime("%Y%m%d")
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_xml
|
30
|
+
Nokogiri::XML::Builder.new do |xml|
|
31
|
+
xml.CWC {
|
32
|
+
xml.CWCVersion "2.0"
|
33
|
+
|
34
|
+
delivery_section(xml)
|
35
|
+
recipient_section(xml)
|
36
|
+
constituent_section(xml)
|
37
|
+
message_section(xml)
|
38
|
+
}
|
39
|
+
end.to_xml
|
40
|
+
end
|
41
|
+
|
42
|
+
def delivery_section(xml)
|
43
|
+
xml.Delivery {
|
44
|
+
xml.DeliveryId delivery_id
|
45
|
+
xml.DeliveryDate delivery_date
|
46
|
+
|
47
|
+
xml.DeliveryAgent delivery.dig!(:agent, :name)
|
48
|
+
xml.DeliveryAgentAckEmailAddress delivery.dig!(:agent, :ack_email)
|
49
|
+
xml.DeliveryAgentContact {
|
50
|
+
xml.DeliveryAgentContactName delivery.dig!(:agent, :contact_name)
|
51
|
+
xml.DeliveryAgentContactEmail delivery.dig!(:agent, :contact_email)
|
52
|
+
xml.DeliveryAgentContactPhone delivery.dig!(:agent, :contact_phone)
|
53
|
+
}
|
54
|
+
|
55
|
+
if delivery.dig(:organization, :name)
|
56
|
+
xml.Organization delivery.dig(:organization, :name)
|
57
|
+
end
|
58
|
+
|
59
|
+
if (delivery.dig(:organization, :contact) || {}).keys.grep(/name|email|phone/).any?
|
60
|
+
xml.OrganizationContact {
|
61
|
+
if delivery.dig(:organization, :contact, :name)
|
62
|
+
xml.OrganizationContactName delivery.dig(:organization, :contact, :name)
|
63
|
+
end
|
64
|
+
|
65
|
+
if delivery.dig(:organization, :contact, :email)
|
66
|
+
xml.OrganizationContactEmail delivery.dig(:organization, :contact, :email)
|
67
|
+
end
|
68
|
+
|
69
|
+
if delivery.dig(:organization, :contact, :phone)
|
70
|
+
xml.OrganizationContactPhone delivery.dig(:organization, :contact, :phone)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
if delivery.dig(:organization, :about)
|
76
|
+
xml.OrganizationAbout delivery.dig(:organization, :about)
|
77
|
+
end
|
78
|
+
|
79
|
+
xml.CampaignId delivery.fetch(:campaign_id)
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def recipient_section(xml)
|
84
|
+
xml.Recipient {
|
85
|
+
xml.MemberOffice recipient.fetch(:member_office)
|
86
|
+
|
87
|
+
if recipient[:is_response_requested]
|
88
|
+
xml.IsResponseRequested "Y"
|
89
|
+
end
|
90
|
+
|
91
|
+
if recipient[:newsletter_opt_in]
|
92
|
+
xml.NewsletterOptIn "Y"
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def constituent_section(xml)
|
98
|
+
xml.Constituent {
|
99
|
+
xml.Prefix constituent.fetch(:prefix)
|
100
|
+
xml.FirstName constituent.fetch(:first_name)
|
101
|
+
|
102
|
+
if constituent[:middle_name]
|
103
|
+
xml.MiddleName constituent[:middle_name]
|
104
|
+
end
|
105
|
+
|
106
|
+
xml.LastName constituent.fetch(:last_name)
|
107
|
+
|
108
|
+
if constituent[:suffix]
|
109
|
+
xml.Suffix constituent[:suffix]
|
110
|
+
end
|
111
|
+
|
112
|
+
if constituent[:title]
|
113
|
+
xml.Title constituent[:title]
|
114
|
+
end
|
115
|
+
|
116
|
+
if constituent[:organization]
|
117
|
+
xml.Organization constituent[:organization]
|
118
|
+
end
|
119
|
+
|
120
|
+
xml.Address1 Array(constituent.fetch(:address))[0]
|
121
|
+
if Array(constituent[:address])[1]
|
122
|
+
xml.Address2 Array(constituent[:address])[1]
|
123
|
+
|
124
|
+
if Array(constituent[:address])[2]
|
125
|
+
xml.Address3 Array(constituent[:address])[2]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
xml.City constituent.fetch(:city)
|
129
|
+
xml.StateAbbreviation constituent.fetch(:state_abbreviation)
|
130
|
+
xml.Zip constituent.fetch(:zip)
|
131
|
+
|
132
|
+
if constituent[:phone]
|
133
|
+
xml.Phone constituent[:phone]
|
134
|
+
end
|
135
|
+
|
136
|
+
if constituent[:address_validation]
|
137
|
+
xml.AddressValidation "Y"
|
138
|
+
end
|
139
|
+
|
140
|
+
xml.Email constituent.fetch(:email)
|
141
|
+
|
142
|
+
if constituent[:email_validation]
|
143
|
+
xml.EmailValidation "Y"
|
144
|
+
end
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def message_section(xml)
|
149
|
+
xml.Message {
|
150
|
+
xml.Subject message.fetch(:subject)
|
151
|
+
|
152
|
+
xml.LibraryOfCongressTopics {
|
153
|
+
message.fetch(:library_of_congress_topics).each do |topic|
|
154
|
+
xml.LibraryOfCongressTopic topic
|
155
|
+
end
|
156
|
+
}
|
157
|
+
|
158
|
+
Array(message[:bills]).each do |bill|
|
159
|
+
xml.Bill {
|
160
|
+
xml.BillCongress bill[:congress]
|
161
|
+
xml.BillTypeAbbreviation bill[:type_abbreviation]
|
162
|
+
xml.BillNumber bill[:number]
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
if message[:pro_or_con]
|
167
|
+
xml.ProOrCon message[:pro_or_con]
|
168
|
+
end
|
169
|
+
|
170
|
+
if message[:organization_statement]
|
171
|
+
xml.OrganizationStatement message[:organization_statement]
|
172
|
+
end
|
173
|
+
|
174
|
+
if message[:constituent_message]
|
175
|
+
xml.ConstituentMessage message[:constituent_message]
|
176
|
+
end
|
177
|
+
|
178
|
+
if message[:more_info]
|
179
|
+
xml.MoreInfo message[:more_info]
|
180
|
+
end
|
181
|
+
}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|