congress_forms 0.1.1

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.
@@ -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
@@ -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
@@ -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