mass-client 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []