Class: Mass::Profile

Inherits:
BlackStack::Base
  • Object
show all
Defined in:
lib/base-line/profile.rb

Direct Known Subclasses

ProfileAPI, ProfileMTA, ProfileRPA

Constant Summary collapse

LOCKFILE_PATH =

DROPBOX LOCKFILE PARAMETERS

'/tmp/dropbox_upload.lock'
LOCK_TIMEOUT =

Path to your lockfile

30

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(h) ⇒ Profile

Returns a new instance of Profile.



163
164
165
166
# File 'lib/base-line/profile.rb', line 163

def initialize(h)
    super(h)
    self.type = Mass::ProfileType.new(h['profile_type_desc']).child_class_instance
end

Instance Attribute Details

#typeObject

Returns the value of attribute type.



3
4
5
# File 'lib/base-line/profile.rb', line 3

def type
  @type
end

Class Method Details

.object_nameObject



159
160
161
# File 'lib/base-line/profile.rb', line 159

def self.object_name
    'profile'
end

Instance Method Details

#acquire_lockObject

DROPBOX LOCKFILE FUNCTIONS



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/base-line/profile.rb', line 10

def acquire_lock
    begin
        Timeout.timeout(LOCK_TIMEOUT) do
        # Wait until the lockfile can be created (i.e., it's not already taken)
        while File.exist?(LOCKFILE_PATH)
            sleep 0.1
        end
        # Create the lockfile
        File.open(LOCKFILE_PATH, 'w') {}
        end
    rescue Timeout::Error
        raise "Timeout while waiting for lockfile."
    end
end

#child_class_instanceObject

crate an instance of the profile type using the class defined in the ‘desc` attribute. override the base method



177
178
179
180
181
182
183
# File 'lib/base-line/profile.rb', line 177

def child_class_instance
    profile_type = self.desc['profile_type']
    key = self.class_name_from_profile_type
    raise "Source code of profile type #{profile_type} not found. Create a class #{key} in the folder `/lib` of your mass-sdk." unless Kernel.const_defined?(key)
    ret = Kernel.const_get(key).new(self.desc)
    return ret
end

#class_name_from_profile_typeObject

convert the profile_type into the ruby class to create an instance. example: Apollo –> Mass::ApolloAPI



170
171
172
173
# File 'lib/base-line/profile.rb', line 170

def class_name_from_profile_type
    profile_type = self.desc['profile_type']
    "Mass::#{profile_type}" 
end

#connectioncheck(limit: 100, logger: nil) ⇒ Object

Scrape the inbox of the profile. Return a an array of hash descriptors of outreach records.

Parameters:

  • limit: the maximum number of connections to scrape. Default: 100.

  • logger: a logger object to log the process. Default: nil.

Example of a hash descritor into the returned array: “‘

# a scraped message is always a :performed message
'status' => :performed,
# what is the outreach type?
# e.g.: :LinkedIn_ConnectionRequest
# decide this in the child class.
'outreach_type' => nil,
# hash descriptor of the profile who is scraping the inbox
'profile' => self.desc,
# hash descriptor of the lead who is the conversation partner
'lead' => nil,
# if the message has been sent by the profile, it is :outgoing.
# if the message has been sent by the lead, it is :incoming.
'direction' => :accepted,

“‘



256
257
258
# File 'lib/base-line/profile.rb', line 256

def connectioncheck(limit: 100, logger:nil)
    []
end

#download_image(img, dropbox_folder = nil) ⇒ Object

download image from Selenium using JavaScript and upload to Dropbox return the URL of the screenshot

Parameters:

  • img: Selenium image element to download from the website and upload to dropbox.

  • dropbox_folder: Dropbox folder name to store the image.



152
153
154
# File 'lib/base-line/profile.rb', line 152

def download_image(img, dropbox_folder=nil)
    download_image_0(img.attribute('src'), dropbox_folder)
end

#download_image_0(url, dropbox_folder = nil) ⇒ Object

download image from Selenium using JavaScript and upload to Dropbox return the URL of the screenshot

Parameters:

  • url: Internet address of the image to download from the website and upload to dropbox.

  • dropbox_folder: Dropbox folder name to store the image.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/base-line/profile.rb', line 58

def download_image_0(url, dropbox_folder = nil)
    raise "Either dropbox_folder parameter or self.desc['id_account'] are required." if dropbox_folder.nil? && self.desc['id_account'].nil?
    dropbox_folder = self.desc['id_account'] if dropbox_folder.nil?
    
    # Parameters
    id = SecureRandom.uuid
    
    # JavaScript to get base64 image data
    js0 = "
        function getImageBase64(imageSrc) {
        return new Promise(async (resolve, reject) => {
            try {
            const response = await fetch(imageSrc);
            const blob = await response.blob();
            const reader = new FileReader();
            reader.onloadend = function() {
                resolve(reader.result);
            };
            reader.onerror = function(error) {
                reject(error);
            };
            reader.readAsDataURL(blob);
            } catch (error) {
            reject(error);
            }
        });
        }
        return getImageBase64('#{url}');
    "
    
    # Execute JavaScript and get base64 image data
    base64_image = driver.execute_script(js0)
    raise "Failed to retrieve image data from URL: #{url}" if base64_image.nil?
    
    # Extract MIME type and base64 data
    mime_type_match = base64_image.match(/^data:image\/([a-zA-Z0-9.+-]+);base64,/)
    if mime_type_match
        mime_subtype = mime_type_match[1] # e.g., 'png', 'jpeg', 'gif'
        # Map common MIME subtypes to file extensions
        extension = case mime_subtype
                    when 'jpeg' then 'jpg'
                    else mime_subtype
                    end
        # Remove the data URL prefix
        image_data = base64_image.sub(/^data:image\/[a-zA-Z0-9.+-]+;base64,/, '')
    else
        raise "Unsupported or invalid image data format."
    end
    
    # Update filename and paths
    filename = "#{id}.#{extension}"
    tmp_paths = if Mass.download_path.is_a?(String)
                    ["#{Mass.download_path}/#{filename}"]
                elsif Mass.download_path.is_a?(Array)
                    Mass.download_path.map { |s| "#{s}/#{filename}" }
                else
                    raise "Invalid Mass.download_path configuration."
                end
    
    # Save the image to the first available path
    tmp_path = tmp_paths.find { |path| File.writable?(File.dirname(path)) }
    raise "No writable path found in #{tmp_paths.join(', ')}." if tmp_path.nil?
    
    File.open(tmp_path, 'wb') do |file|
        file.write(Base64.decode64(image_data))
    end
    
    # Proceed with Dropbox operations
    year = Time.now.year.to_s.rjust(4, '0')
    month = Time.now.month.to_s.rjust(2, '0')
    folder = "/massprospecting.rpa/#{dropbox_folder}.#{year}.#{month}"
    path = "#{folder}/#{filename}"
    BlackStack::DropBox.dropbox_create_folder(folder)

    # Upload the file to Dropbox
    upload_to_dropbox_with_lock(tmp_path, path)

    # Delete the local file
    File.delete(tmp_path)

    # Return the URL of the file in Dropbox
    # 
    # Add a timeout to wait the file is present in the cloud.
    # Reference: https://github.com/MassProspecting/docs/issues/320
    self.wait_for_dropbox_url(path).gsub('&dl=1', '&dl=0')
end

#inboxcheck(limit: 100, only_unread: true, logger: nil) ⇒ Object

Scrape the inbox of the profile. Return a an array of hash descriptors of outreach records.

Parameters:

  • limit: the maximum number of messages to scrape. Default: 100.

  • only_unread: if true, then only the unread messages will be scraped. Default: true.

  • logger: a logger object to log the process. Default: nil.

Example of a hash descritor into the returned array: “‘

# a scraped message is always a :performed message
'status' => :performed,
# what is the outreach type?
# e.g.: :LinkedIn_DirectMessage
# decide this in the child class.
'outreach_type' => nil,
# hash descriptor of the profile who is scraping the inbox
'profile' => self.desc,
# hash descriptor of the lead who is the conversation partner
'lead' => nil,
# if the message has been sent by the profile, it is :outgoing.
# if the message has been sent by the lead, it is :incoming.
'direction' => nil, 
# the content of the message
'subject' => nil,
'body' => nil,

“‘



226
227
228
# File 'lib/base-line/profile.rb', line 226

def inboxcheck(limit: 100, only_unread:true, logger:nil)
    []
end

#release_lockObject



25
26
27
# File 'lib/base-line/profile.rb', line 25

def release_lock
    File.delete(LOCKFILE_PATH) if File.exist?(LOCKFILE_PATH)
end

#running?Boolean

return true of the profile is running if its profile type is rpa-access, then it will return true if the browser is running. else, it will return always true.

Returns:

  • (Boolean)


188
189
190
191
192
193
194
# File 'lib/base-line/profile.rb', line 188

def running?
    if self.type.desc['access'].to_sym == :rpa
        c = AdsPowerClient.new(key: ADSPOWER_API_KEY)
        return c.check(self.desc['ads_power_id'])
    end
    return true
end

#upload_to_dropbox_with_lock(tmp_path, path) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/base-line/profile.rb', line 29

def upload_to_dropbox_with_lock(tmp_path, path)
    s = ''
    acquire_lock
    begin
        # Upload the file to Dropbox
        s = BlackStack::DropBox.dropbox_upload_file(tmp_path, path)
        json = JSON.parse(s)
        if json['is_downloadable'].nil? || !json['is_downloadable']
            raise "Dropbox file upload failed. Dropbox response: #{s}"
        end
    rescue => e
        raise "Error during upload: #{e.message} - Dropbox response: #{s}"
    ensure
        # Always release the lock, even if an error occurs
        release_lock
    end
end