mass-client 1.0.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,375 @@
1
+ module Mass
2
+ class ProfileRPA < Mass::Profile
3
+ @@buffer_driver = nil
4
+ attr_accessor :headless
5
+
6
+ def self.buffer_driver
7
+ @@buffer_driver
8
+ end
9
+
10
+ def self.buffer_driver=(o)
11
+ @@buffer_driver = o
12
+ end
13
+
14
+ def initialize(desc)
15
+ super(desc)
16
+ headless = true
17
+ end
18
+
19
+ def self.domain
20
+ raise "Method domain of ProfileRPA must be implemented on each child class."
21
+ end
22
+
23
+ # resolving the redirection from facebook_2 to facebook, or linkedin_2 to
24
+ # more infromation here: https://github.com/VyMECO/mass.public/issues/93
25
+ #
26
+ # Parameters:
27
+ # - lead_descriptor: hash descriptor of a lead
28
+ # - field_1: field for storing the URL type 1 that must be realized.
29
+ # - field_2: field for storing the URL type 2 that redirets to the URL type .
30
+ # - logger: object to write log. Optional. Default: nil.
31
+ #
32
+ def resolve_dual_url(lead_descriptor,
33
+ field_1:,
34
+ field_2:,
35
+ logger: nil
36
+ )
37
+ l = logger || BlackStack::DummyLogger.new(nil)
38
+ k = lead_descriptor
39
+
40
+ l.logs "Resolving dual-url redirection #{k[field_2].to_s.blue}... "
41
+ if !k[field_2].nil? && k[field_1].nil?
42
+ h_filters = {}
43
+ h_filters[field_2.to_s] = k[field_2]
44
+ lead = Mass::Lead.page(
45
+ page: 1,
46
+ limit: 1,
47
+ filters: h_filters
48
+ ).first
49
+ if lead
50
+ l.logf 'done'.green + " (field_2 already registered in the database.)"
51
+ else
52
+ driver.get(k[field_2])
53
+ sleep(5)
54
+ k[field_1] = driver.current_url
55
+ #ret.select { |k2| k2['lead'][field_2] == }
56
+ l.logf 'done'.green + " (#{k[field_1].to_s.blue})"
57
+ end
58
+ else
59
+ if k[field_2].nil?
60
+ l.logf 'skip'.yellow + " (no field_2)"
61
+ else
62
+ l.logf 'skip'.yellow + " (no dual-url redirection needed)"
63
+ end
64
+ end
65
+ end # def resolve_dual_url
66
+
67
+ # for internal use only. users should call `send_request` instead.
68
+ def close_popups(logger:nil)
69
+ raise "Method close_popups of ProfileRPA must be implemented on each child class."
70
+ end # def close_popups
71
+
72
+ # start the adspower server for headless mode, if it is not already started and if headless is activated.
73
+ def self.start_headless_server(headless: true, logger:nil)
74
+ l = logger || BlackStack::DummyLogger.new(nil)
75
+ # check if the rpa.rb process is running manually
76
+ l.logs "Starting AdsPower headless server?... "
77
+ unless headless
78
+ l.logf "no".yellow + " (headless is disabled in the command line)"
79
+ else
80
+ l.logf "yes".green + " (headless is enabled)"
81
+
82
+ # creating an AdsPower client
83
+ l.logs "Creating AdsPower client... "
84
+ c = AdsPowerClient.new(key: ADSPOWER_API_KEY)
85
+ l.logf 'done'.green
86
+
87
+ # starting adspower server
88
+ l.logs "Is headless server already running?... "
89
+ if c.online?
90
+ l.logf "yes".green
91
+ else
92
+ l.logf "no".yellow
93
+
94
+ l.logs "Starting AdsPower server... "
95
+ c.server_start
96
+ l.logf 'done'.green
97
+ end
98
+ end # if parser.value('manual')
99
+ end
100
+
101
+ # stop the adspower server for headless mode, if it is not already stopped and if headless is activated.
102
+ def self.clear_driver
103
+ self.class.buffer_driver = nil
104
+ end
105
+
106
+ # close all tabs except one
107
+ # do this only if there are more than one tab
108
+ # do this in order to avoid the browser show up if it is not necesasry.
109
+ def one_tab
110
+ handles = self.class.buffer_driver.window_handles
111
+ if handles.size > 1
112
+ chosen = handles.pop
113
+ handles.each { |handle|
114
+ self.class.buffer_driver.switch_to.window(handle)
115
+ self.class.buffer_driver.close # don't use quit, because you don't want to kill the chromedriver
116
+ }
117
+ self.class.buffer_driver.switch_to.window(chosen)
118
+ end
119
+ end # def one_tab
120
+ =begin
121
+ # scroll down n times
122
+ def scroll_down(n)
123
+ driver = self.driver
124
+ i = 0
125
+ while (i<n)
126
+ i += 1
127
+ step = self.desc['scrolling_step'] + rand(self.desc['scrolling_step_random'].to_i)
128
+
129
+ # scroll down to the 3th post
130
+ #
131
+ driver.execute_script("window.scrollBy(0, #{i*1000});");
132
+
133
+ # add delay to wait for the AJAX
134
+ #
135
+ sleep(10+rand(5))
136
+ end
137
+ end # def scroll_down
138
+ =end
139
+ # Allow or Block a site to perform multiple downloads.
140
+ # Parameters:
141
+ # - status: :allow or :block
142
+ # - domain: the domain of the site to allow or block.
143
+ # - secure: true or false
144
+ # - www: true or false
145
+ #
146
+ # Reference:
147
+ # - https://github.com/VyMECO/mass/issues/43
148
+ # - https://stackoverflow.com/questions/66659465/python-manually-config-chrome-settings-by-selenium-remotedriver
149
+ #
150
+ def automatic_downloads(
151
+ status:,
152
+ domain:,
153
+ secure:true,
154
+ www:true
155
+ )
156
+ err = []
157
+ err << "The status must be :allow or :block." if ![:allow, :block].include?(status)
158
+ err << "#{domain.to_s} is not a valid domain" if !domain.to_s.valid_domain?
159
+ raise err.join("\n") if err.size > 0
160
+
161
+ site_url = secure ? "https://" : "http://"
162
+ site_url += www ? "www." : ""
163
+ site_url += domain.to_s
164
+ site_url += secure ? ":443" : ""
165
+
166
+ url = "chrome://settings/content/siteDetails?site=#{CGI.escape(site_url)}"
167
+ self.driver.get(url)
168
+ temp = self.driver.execute_script("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main#main').shadowRoot.querySelector('settings-basic-page')")
169
+ rules = self.driver.execute_script("return arguments[0].shadowRoot.querySelector('settings-privacy-page').shadowRoot.querySelector('settings-animated-pages#pages settings-subpage site-details').shadowRoot.querySelector('div.list-frame:not(div>div.list-frame)')", temp)
170
+ automaticDownloads = self.driver.execute_script("return arguments[0].querySelector('site-details-permission[label=\"Automatic downloads\"]').shadowRoot.querySelector('#permission')", rules)
171
+
172
+ self.driver.execute_script("arguments[0].scrollIntoView()", automaticDownloads)
173
+ automaticDownloads.click()
174
+ automaticDownloads.find_element(:id => status.to_s).click()
175
+ end # def automatic_downloads
176
+
177
+ # download image from Selenium using JavaScript and upload to Dropbox
178
+ # return the URL of the screenshot
179
+ #
180
+ # Parameters:
181
+ # - url: Internet address of the image to download from the website and upload to dropbox.
182
+ # - dropbox_folder: Dropbox folder name to store the image.
183
+ #
184
+ def download_image_0(url, dropbox_folder=nil)
185
+ raise "Either dropbox_folder parameter or self.desc['id_account'] are required." if dropbox_folder.nil? && self.desc['id_account'].nil?
186
+ dropbox_folder = self.desc['id_account'] if dropbox_folder.nil?
187
+ # parameters
188
+ id = SecureRandom.uuid
189
+ filename = "#{id}.png"
190
+ tmp_path = ["#{Mass.download_path}/#{filename}"] if Mass.download_path.is_a?(String)
191
+ tmp_paths = Mass.download_path.map { |s| "#{s}/#{filename}" } if Mass.download_path.is_a?(Array)
192
+
193
+ # excute JavaScript function downloadImage with the parameter src
194
+
195
+ # this function is to download images from the same browser, using the proxy of the browser.
196
+ # never download images from the server, because sites can track the IP.
197
+ js0 = "
198
+ async function downloadImage(imageSrc, filename) {
199
+ const image = await fetch(imageSrc)
200
+ const imageBlog = await image.blob()
201
+ const imageURL = URL.createObjectURL(imageBlog)
202
+
203
+ const link = document.createElement('a')
204
+ link.href = imageURL
205
+ link.download = filename
206
+ document.body.appendChild(link)
207
+ link.click()
208
+ document.body.removeChild(link)
209
+ }
210
+ downloadImage('#{url}', '#{filename}')
211
+ "
212
+ driver.execute_script(js0)
213
+
214
+ # code
215
+ year = Time.now.year.to_s.rjust(4,'0')
216
+ month = Time.now.month.to_s.rjust(2,'0')
217
+ folder = "/massprospecting.rpa/#{dropbox_folder}.#{year}.#{month}"
218
+ path = "#{folder}/#{filename}"
219
+ BlackStack::DropBox.dropbox_create_folder(folder)
220
+
221
+ # find the tmp_path that exists
222
+ tmp_path = nil
223
+ tmp_paths.each { |s|
224
+ if File.exist?(s)
225
+ tmp_path = s
226
+ break
227
+ end
228
+ }
229
+ raise "The file #{filename} was not downloaded." if tmp_path.nil?
230
+
231
+ # upload the file to dropbox
232
+ BlackStack::DropBox.dropbox_upload_file(tmp_path, path)
233
+ # delete the file
234
+ File.delete(tmp_path)
235
+ # return the URL of the file in dropbox
236
+ BlackStack::DropBox.get_file_url(path).gsub('&dl=1', '&dl=0')
237
+ end # def download_image_0
238
+
239
+ # download image from Selenium using JavaScript and upload to Dropbox
240
+ # return the URL of the screenshot
241
+ #
242
+ # Parameters:
243
+ # - img: Selenium image element to download from the website and upload to dropbox.
244
+ # - dropbox_folder: Dropbox folder name to store the image.
245
+ #
246
+ def download_image(img, dropbox_folder=nil)
247
+ download_image_0(img.attribute('src'), dropbox_folder)
248
+ end # def download_image
249
+
250
+ # take screenshot and upload it to dropbox
251
+ # return the URL of the screenshot
252
+ def screenshot(dropbox_folder=nil)
253
+ raise "Either dropbox_folder parameter or self.desc['id_account'] are required." if dropbox_folder.nil? && self.desc['id_account'].nil?
254
+ dropbox_folder = self.desc['id_account'] if dropbox_folder.nil?
255
+ # parameters
256
+ id = SecureRandom.uuid
257
+ filename = "#{id}.png"
258
+ tmp_path = "/tmp/#{filename}"
259
+ # take screenshot using selenium driver and save it into the /tmp folder
260
+ self.driver.save_screenshot(tmp_path)
261
+ # code
262
+ year = Time.now.year.to_s.rjust(4,'0')
263
+ month = Time.now.month.to_s.rjust(2,'0')
264
+ folder = "/massprospecting.rpa/#{dropbox_folder}.#{year}.#{month}"
265
+ path = "#{folder}/#{filename}"
266
+ BlackStack::DropBox.dropbox_create_folder(folder)
267
+ BlackStack::DropBox.dropbox_upload_file(tmp_path, path)
268
+ File.delete(tmp_path)
269
+ BlackStack::DropBox.get_file_url(path).gsub('&dl=1', '&dl=0')
270
+ end # def screenshot
271
+
272
+ # create a file in the cloud with the HTML of the current page
273
+ # return the URL of the file
274
+ def snapshot(dropbox_folder=nil)
275
+ raise "Either dropbox_folder parameter or self.desc['id_account'] are required." if dropbox_folder.nil? && self.desc['id_account'].nil?
276
+ dropbox_folder = self.desc['id_account'] if dropbox_folder.nil?
277
+ # parameters
278
+ id = SecureRandom.uuid
279
+ filename = "#{id}.html"
280
+ tmp_path = "/tmp/#{filename}"
281
+ # take screenshot using selenium driver and save it into the /tmp folder
282
+ File.write(tmp_path, self.driver.page_source)
283
+ # code
284
+ year = Time.now.year.to_s.rjust(4,'0')
285
+ month = Time.now.month.to_s.rjust(2,'0')
286
+ folder = "/massprospecting.bots/#{dropbox_folder}.#{year}.#{month}"
287
+ path = "#{folder}/#{filename}"
288
+ BlackStack::DropBox.dropbox_create_folder(folder)
289
+ BlackStack::DropBox.dropbox_upload_file(tmp_path, path)
290
+ File.delete(tmp_path)
291
+ BlackStack::DropBox.get_file_url(path).gsub('&dl=1', '&dl=0')
292
+ end # def snapshot
293
+
294
+ # return a selnium driver for this profile
295
+ def driver()
296
+ class_name = self.class.name.gsub('Mass::', '')
297
+ allow_intercept_js = self.type.desc['allow_intercept_js']
298
+
299
+ #l = BlackStack::DummyLogger.new(nil) if l.nil?
300
+
301
+ c = AdsPowerClient.new(key: ADSPOWER_API_KEY)
302
+
303
+ self.class.buffer_driver = nil if !c.check(self.desc['ads_power_id'])
304
+ sleep(1)
305
+
306
+ if self.class.buffer_driver.nil?
307
+ self.class.buffer_driver = c.driver(self.desc['ads_power_id'], headless)
308
+ self.class.buffer_driver.manage.window.resize_to(self.desc['browser_width'], self.desc['browser_height'])
309
+ self.one_tab
310
+
311
+ #if self.desc['allow_browser_to_download_multiple_files']
312
+ # self.automatic_downloads(status: :allow, domain: self.domain, secure: true, www: true)
313
+ #else
314
+ # self.automatic_downloads(status: :block, domain: self.domain, secure: true, www: true)
315
+ #end
316
+
317
+ # Get source code of intercept.js library
318
+ #l.logs "Getting source code of intercept.js library... "
319
+ uri = URI.parse('https://raw.githubusercontent.com/leandrosardi/intercept/main/lib/intercept.js')
320
+ js1 = Net::HTTP.get(uri)
321
+ #l.logf "done".green
322
+
323
+
324
+ # call ls command to get array of files in the folder
325
+ # iterate all the *.js files inside the folder $RUBYLIB/extensions/mass.subaccount/js
326
+ js2 = ''
327
+ filenames = `ls #{CODE_PATH}/extensions/mass.subaccount/js/*.js`.split("\n")
328
+ filenames.each { |filename|
329
+ # Get the source code of the scraper
330
+ js2 += `cat #{filename}`
331
+ js2 += "\n\n"
332
+ }
333
+
334
+ #l.logf "done".green + " (#{filename.blue})"
335
+
336
+ # Initializing the interceptor
337
+ js3 = ""
338
+ =begin
339
+ js3 = "
340
+ $$.init({
341
+ parse: function(xhr) {
342
+ #{class_name}.scrape(xhr);
343
+ }
344
+ });
345
+ "
346
+ =end
347
+ # Execute the scraper
348
+ # Inject the library into the page
349
+ #
350
+ # Selenium: How to Inject/execute a Javascript in to a Page before loading/executing any other scripts of the page?
351
+ #
352
+ # Reference:
353
+ # - https://stackoverflow.com/questions/31354352/selenium-how-to-inject-execute-a-javascript-in-to-a-page-before-loading-executi
354
+ #
355
+ # Documentation:
356
+ # - https://www.selenium.dev/documentation/webdriver/bidirectional/chrome_devtools/cdp_endpoint/
357
+ #l.logs "Injecting the library into the page... "
358
+
359
+ # setup a profile_type to work or not with intercept.js
360
+ # References:
361
+ # - https://github.com/VyMECO/mass.public/issues/6
362
+ # - https://github.com/VyMECO/mass.public/issues/13
363
+ #
364
+ if allow_intercept_js
365
+ self.class.buffer_driver.execute_cdp("Page.addScriptToEvaluateOnNewDocument", source: js1+js2+js3)
366
+ end
367
+ #l.logf "done".green
368
+ end
369
+
370
+ # return
371
+ return self.class.buffer_driver
372
+ end # def driver
373
+
374
+ end # class ProfileRPA
375
+ end # module Mass
@@ -0,0 +1,53 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'blackstack-core'
5
+ require 'simple_cloud_logging'
6
+ require 'simple_command_line_parser'
7
+ require 'colorize'
8
+
9
+ # base classes
10
+ require 'lib/base-line/channel'
11
+
12
+ require 'lib/base-line/profile_type'
13
+ require 'lib/base-line/source_type'
14
+ require 'lib/base-line/enrichment_type'
15
+ require 'lib/base-line/outreach_type'
16
+ require 'lib/base-line/data_type'
17
+
18
+ require 'lib/base-line/headcount'
19
+ require 'lib/base-line/industry'
20
+ require 'lib/base-line/location'
21
+ require 'lib/base-line/revenue'
22
+ require 'lib/base-line/tag'
23
+ require 'lib/base-line/profile'
24
+
25
+ require 'lib/base-line/source'
26
+ require 'lib/base-line/job'
27
+ require 'lib/base-line/event'
28
+
29
+ require 'lib/base-line/outreach'
30
+ require 'lib/base-line/enrichment'
31
+
32
+ require 'lib/base-line/company'
33
+ #require 'lib/base-line/company_data'
34
+ #require 'lib/base-line/company_industry'
35
+ #require 'lib/base-line/company_naics'
36
+ #require 'lib/base-line/company_sic'
37
+ #require 'lib/base-line/company_tag'
38
+
39
+ require 'lib/base-line/lead'
40
+ #require 'lib/base-line/lead_data'
41
+ #require 'lib/base-line/lead_tag'
42
+
43
+ require 'lib/base-line/inboxcheck'
44
+ require 'lib/base-line/connectioncheck'
45
+ require 'lib/base-line/rule'
46
+ require 'lib/base-line/request'
47
+
48
+ # first line of children
49
+ require 'lib/first-line/profile_api'
50
+ require 'lib/first-line/profile_mta'
51
+ require 'lib/first-line/profile_rpa'
52
+
53
+
metadata ADDED
@@ -0,0 +1,209 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mass-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Leandro Daniel Sardi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: uri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.2
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.11.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 0.11.2
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.11.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-http
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.0
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.2.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 0.2.0
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.2.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: json
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.3
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.6.3
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.6.3
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.6.3
73
+ - !ruby/object:Gem::Dependency
74
+ name: blackstack-core
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: 1.2.19
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.19
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.2.19
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 1.2.19
93
+ - !ruby/object:Gem::Dependency
94
+ name: colorize
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: 0.8.1
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.8.1
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.8.1
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.8.1
113
+ - !ruby/object:Gem::Dependency
114
+ name: simple_cloud_logging
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: 1.2.6
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 1.2.6
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: 1.2.6
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 1.2.6
133
+ - !ruby/object:Gem::Dependency
134
+ name: simple_command_line_parser
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: 1.1.2
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 1.1.2
143
+ type: :runtime
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: 1.1.2
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.1.2
153
+ description: Ruby library for Mass API.
154
+ email: leandro@massprospecting.com
155
+ executables: []
156
+ extensions: []
157
+ extra_rdoc_files: []
158
+ files:
159
+ - lib/base-line/channel.rb
160
+ - lib/base-line/company.rb
161
+ - lib/base-line/connectioncheck.rb
162
+ - lib/base-line/data_type.rb
163
+ - lib/base-line/enrichment.rb
164
+ - lib/base-line/enrichment_type.rb
165
+ - lib/base-line/event.rb
166
+ - lib/base-line/headcount.rb
167
+ - lib/base-line/inboxcheck.rb
168
+ - lib/base-line/industry.rb
169
+ - lib/base-line/job.rb
170
+ - lib/base-line/lead.rb
171
+ - lib/base-line/location.rb
172
+ - lib/base-line/outreach.rb
173
+ - lib/base-line/outreach_type.rb
174
+ - lib/base-line/profile.rb
175
+ - lib/base-line/profile_type.rb
176
+ - lib/base-line/request.rb
177
+ - lib/base-line/revenue.rb
178
+ - lib/base-line/rule.rb
179
+ - lib/base-line/source.rb
180
+ - lib/base-line/source_type.rb
181
+ - lib/base-line/tag.rb
182
+ - lib/first-line/profile_api.rb
183
+ - lib/first-line/profile_mta.rb
184
+ - lib/first-line/profile_rpa.rb
185
+ - lib/mass-client.rb
186
+ homepage: https://github.com/massprospecting/mass-client
187
+ licenses:
188
+ - MIT
189
+ metadata: {}
190
+ post_install_message:
191
+ rdoc_options: []
192
+ require_paths:
193
+ - lib
194
+ required_ruby_version: !ruby/object:Gem::Requirement
195
+ requirements:
196
+ - - ">="
197
+ - !ruby/object:Gem::Version
198
+ version: '0'
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ requirements: []
205
+ rubygems_version: 3.3.7
206
+ signing_key:
207
+ specification_version: 4
208
+ summary: Ruby library for Mass API.
209
+ test_files: []