chromate-rb 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +39 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +71 -0
- data/dockerfiles/Dockerfile +85 -0
- data/dockerfiles/docker-entrypoint.sh +15 -0
- data/docs/browser.md +163 -0
- data/lib/chromate/actions/dom.rb +39 -0
- data/lib/chromate/actions/navigate.rb +71 -0
- data/lib/chromate/actions/screenshot.rb +56 -0
- data/lib/chromate/browser.rb +113 -0
- data/lib/chromate/c_logger.rb +22 -0
- data/lib/chromate/client.rb +120 -0
- data/lib/chromate/configuration.rb +119 -0
- data/lib/chromate/element.rb +194 -0
- data/lib/chromate/elements/select.rb +19 -0
- data/lib/chromate/exceptions.rb +9 -0
- data/lib/chromate/hardwares/mouse_controller.rb +67 -0
- data/lib/chromate/hardwares/mouses/linux_controller.rb +79 -0
- data/lib/chromate/hardwares/mouses/mac_os_controller.rb +133 -0
- data/lib/chromate/hardwares/mouses/virtual_controller.rb +81 -0
- data/lib/chromate/hardwares.rb +33 -0
- data/lib/chromate/helpers.rb +23 -0
- data/lib/chromate/user_agent.rb +34 -0
- data/lib/chromate/version.rb +5 -0
- data/lib/chromate.rb +17 -0
- data/results/brotector.png +0 -0
- data/results/cloudflare.png +0 -0
- data/sig/chromate.rbs +4 -0
- metadata +111 -0
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
class Element
|
5
|
+
class NotFoundError < StandardError
|
6
|
+
def initialize(selector, root_id)
|
7
|
+
super("Element not found with selector: #{selector} under root_id: #{root_id}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class InvalidSelectorError < StandardError
|
12
|
+
def initialize(selector)
|
13
|
+
super("Unable to resolve element with selector: #{selector}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
attr_reader :selector, :client, :mouse
|
17
|
+
|
18
|
+
# @param [String] selector
|
19
|
+
# @param [Chromate::Client] client
|
20
|
+
# @option [Integer] node_id
|
21
|
+
# @option [String] object_id
|
22
|
+
# @option [Integer] root_id
|
23
|
+
def initialize(selector, client, node_id: nil, object_id: nil, root_id: nil)
|
24
|
+
@selector = selector
|
25
|
+
@client = client
|
26
|
+
@object_id = object_id
|
27
|
+
@node_id = node_id
|
28
|
+
@object_id, @node_id = find(selector, root_id) unless @object_id && @node_id
|
29
|
+
@root_id = root_id || document['root']['nodeId']
|
30
|
+
@mouse = Hardwares.mouse(client: client, element: self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
value = selector.length > 20 ? "#{selector[0..20]}..." : selector
|
35
|
+
"#<Chromate::Element:#{value}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
def text
|
39
|
+
return @text if @text
|
40
|
+
|
41
|
+
result = client.send_message('Runtime.callFunctionOn', functionDeclaration: 'function() { return this.innerText; }', objectId: @object_id)
|
42
|
+
@text = result['result']['value']
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def html
|
47
|
+
return @html if @html
|
48
|
+
|
49
|
+
@html = client.send_message('DOM.getOuterHTML', objectId: @object_id)
|
50
|
+
@html = @html['outerHTML']
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Hash]
|
54
|
+
def attributes
|
55
|
+
return @attributes if @attributes
|
56
|
+
|
57
|
+
result = client.send_message('DOM.getAttributes', nodeId: @node_id)
|
58
|
+
@attributes = Hash[*result['attributes']]
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [String] name
|
62
|
+
# @param [String] value
|
63
|
+
# @return [self]
|
64
|
+
def set_attribute(name, value)
|
65
|
+
client.send_message('DOM.setAttributeValue', nodeId: @node_id, name: name, value: value)
|
66
|
+
dispatch_event('change')
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Hash]
|
70
|
+
def bounding_box
|
71
|
+
return @bounding_box if @bounding_box
|
72
|
+
|
73
|
+
result = client.send_message('DOM.getBoxModel', objectId: @object_id)
|
74
|
+
@bounding_box = result['model']
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Integer]
|
78
|
+
def x
|
79
|
+
bounding_box['content'][0]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Integer]
|
83
|
+
def y
|
84
|
+
bounding_box['content'][1]
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Integer]
|
88
|
+
def width
|
89
|
+
bounding_box['width']
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Integer]
|
93
|
+
def height
|
94
|
+
bounding_box['height']
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [self]
|
98
|
+
def click
|
99
|
+
mouse.click
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [self]
|
105
|
+
def hover
|
106
|
+
mouse.hover
|
107
|
+
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [String] text
|
112
|
+
def type(text)
|
113
|
+
client.send_message('Runtime.callFunctionOn', functionDeclaration: "function(value) { this.value = value; this.dispatchEvent(new Event('input')); }",
|
114
|
+
objectId: @object_id, arguments: [{ value: text }])
|
115
|
+
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [String] selector
|
120
|
+
# @return [Chromate::Element]
|
121
|
+
def find_element(selector)
|
122
|
+
find_elements(selector, max: 1).first
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [String] selector
|
126
|
+
# @option [Integer] max
|
127
|
+
# @return [Array<Chromate::Element>]
|
128
|
+
def find_elements(selector, max: 0)
|
129
|
+
results = client.send_message('DOM.querySelectorAll', nodeId: @node_id, selector: selector)
|
130
|
+
results['nodeIds'].each_with_index.filter_map do |node_id, idx|
|
131
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: node_id)
|
132
|
+
next unless node_info['object']
|
133
|
+
break if max.positive? && idx >= max
|
134
|
+
|
135
|
+
Element.new(selector, client, node_id: node_id, object_id: node_info['object']['objectId'], root_id: @node_id)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Integer]
|
140
|
+
def shadow_root_id
|
141
|
+
return @shadow_root_id if @shadow_root_id
|
142
|
+
|
143
|
+
node_info = client.send_message('DOM.describeNode', nodeId: @node_id)
|
144
|
+
@shadow_root_id = node_info.dig('node', 'shadowRoots', 0, 'nodeId')
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Boolean]
|
148
|
+
def shadow_root?
|
149
|
+
!!shadow_root_id
|
150
|
+
end
|
151
|
+
|
152
|
+
# @param [String] selector
|
153
|
+
# @return [Chromate::Element|NilClass]
|
154
|
+
def find_shadow_child(selector)
|
155
|
+
find_shadow_children(selector).first
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param [String] selector
|
159
|
+
# @return [Array<Chromate::Element>]
|
160
|
+
def find_shadow_children(selector)
|
161
|
+
return [] unless shadow_root?
|
162
|
+
|
163
|
+
results = client.send_message('DOM.querySelectorAll', nodeId: shadow_root_id, selector: selector)
|
164
|
+
(results&.dig('nodeIds') || []).map do |node_id|
|
165
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: node_id)
|
166
|
+
next unless node_info['object']
|
167
|
+
|
168
|
+
Element.new(selector, client, node_id: node_id, object_id: node_info['object']['objectId'], root_id: shadow_root_id)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def dispatch_event(event)
|
175
|
+
client.send_message('DOM.dispatchEvent', nodeId: @node_id, type: event)
|
176
|
+
end
|
177
|
+
|
178
|
+
# @return [Array] [object_id, node_id]
|
179
|
+
def find(selector, root_id = nil)
|
180
|
+
@root_id = root_id || document['root']['nodeId']
|
181
|
+
result = client.send_message('DOM.querySelector', nodeId: @root_id, selector: selector)
|
182
|
+
raise NotFoundError.new(selector, @root_id) unless result['nodeId']
|
183
|
+
|
184
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: result['nodeId'])
|
185
|
+
raise InvalidSelectorError, selector unless node_info['object']
|
186
|
+
|
187
|
+
[node_info['object']['objectId'], result['nodeId']]
|
188
|
+
end
|
189
|
+
|
190
|
+
def document
|
191
|
+
@document ||= client.send_message('DOM.getDocument')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
class Select < Element
|
8
|
+
# @param [String] selector
|
9
|
+
def select_option(value)
|
10
|
+
click
|
11
|
+
opt = find_elements('option').find do |option|
|
12
|
+
option.attributes['value'] == value
|
13
|
+
end
|
14
|
+
opt.set_attribute('selected', 'true')
|
15
|
+
click
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
module Hardwares
|
5
|
+
class MouseController
|
6
|
+
CLICK_DURATION_RANGE = (0.01..0.1)
|
7
|
+
DOUBLE_CLICK_DURATION_RANGE = (0.1..0.5)
|
8
|
+
|
9
|
+
attr_accessor :element, :client, :mouse_position
|
10
|
+
|
11
|
+
# @param [Chromate::Element] element
|
12
|
+
# @param [Chromate::Client] client
|
13
|
+
def initialize(element: nil, client: nil)
|
14
|
+
@element = element
|
15
|
+
@client = client
|
16
|
+
@mouse_position = { x: 0, y: 0 }
|
17
|
+
end
|
18
|
+
|
19
|
+
def hover
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def click
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def double_click
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def right_click
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def position_x
|
36
|
+
mouse_position[:x]
|
37
|
+
end
|
38
|
+
|
39
|
+
def position_y
|
40
|
+
mouse_position[:y]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def target_x
|
46
|
+
element.x + (element.width / 2)
|
47
|
+
end
|
48
|
+
|
49
|
+
def target_y
|
50
|
+
element.y + (element.height / 2)
|
51
|
+
end
|
52
|
+
|
53
|
+
def bezier_curve(steps: 50) # rubocop:disable Metrics/AbcSize
|
54
|
+
control_x = (target_x / 2)
|
55
|
+
control_y = (target_y / 2)
|
56
|
+
|
57
|
+
(0..steps).map do |t|
|
58
|
+
t /= steps.to_f
|
59
|
+
# Compute the position on the quadratic Bezier curve
|
60
|
+
new_x = (((1 - t)**2) * position_x) + (2 * (1 - t) * t * control_x) + ((t**2) * target_x)
|
61
|
+
new_y = (((1 - t)**2) * position_y) + (2 * (1 - t) * t * control_y) + ((t**2) * target_y)
|
62
|
+
{ x: new_x, y: new_y }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/helpers'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Chromate
|
7
|
+
module Hardwares
|
8
|
+
module Mouses
|
9
|
+
class LinuxController < MouseController
|
10
|
+
class InvalidPlatformError < StandardError; end
|
11
|
+
include Helpers
|
12
|
+
|
13
|
+
LEFT_BUTTON = 1
|
14
|
+
RIGHT_BUTTON = 3
|
15
|
+
|
16
|
+
def initialize(element: nil, client: nil)
|
17
|
+
raise InvalidPlatformError, 'MouseController is only supported on Linux' unless linux?
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def hover
|
23
|
+
focus_chrome_window
|
24
|
+
# system("xdotool mousemove_relative --sync -- #{x} #{y}")
|
25
|
+
system("xdotool mousemove #{target_x} #{target_y}")
|
26
|
+
current_mouse_position
|
27
|
+
end
|
28
|
+
|
29
|
+
def click
|
30
|
+
hover
|
31
|
+
simulate_button_event(LEFT_BUTTON, true)
|
32
|
+
simulate_button_event(LEFT_BUTTON, false)
|
33
|
+
end
|
34
|
+
|
35
|
+
def right_click
|
36
|
+
simulate_button_event(RIGHT_BUTTON, true)
|
37
|
+
simulate_button_event(RIGHT_BUTTON, false)
|
38
|
+
end
|
39
|
+
|
40
|
+
def double_click
|
41
|
+
click
|
42
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
43
|
+
click
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def focus_chrome_window
|
49
|
+
# Recherche de la fenêtre Chrome avec xdotool
|
50
|
+
chrome_window_id = `xdotool search --onlyvisible --name "Chrome"`.strip
|
51
|
+
|
52
|
+
if chrome_window_id.empty?
|
53
|
+
puts 'Aucune fenêtre Chrome trouvée'
|
54
|
+
else
|
55
|
+
# Active la fenêtre Chrome avec xdotool
|
56
|
+
system("xdotool windowactivate #{chrome_window_id}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def current_mouse_position
|
61
|
+
x = nil
|
62
|
+
y = nil
|
63
|
+
Open3.popen3('xdotool getmouselocation --shell') do |_, stdout, _, _|
|
64
|
+
output = stdout.read
|
65
|
+
x = output.match(/X=(\d+)/)[1].to_i
|
66
|
+
y = output.match(/Y=(\d+)/)[1].to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
{ x: x, y: y }
|
70
|
+
end
|
71
|
+
|
72
|
+
def simulate_button_event(button, press)
|
73
|
+
action = press ? 'mousedown' : 'mouseup'
|
74
|
+
system("xdotool #{action} #{button}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require 'chromate/helpers'
|
5
|
+
module Chromate
|
6
|
+
module Hardwares
|
7
|
+
module Mouses
|
8
|
+
class MacOsController < MouseController
|
9
|
+
class InvalidPlatformError < StandardError; end
|
10
|
+
include Helpers
|
11
|
+
extend FFI::Library
|
12
|
+
|
13
|
+
ffi_lib '/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices'
|
14
|
+
|
15
|
+
class CGPoint < FFI::Struct
|
16
|
+
layout :x, :float,
|
17
|
+
:y, :float
|
18
|
+
end
|
19
|
+
|
20
|
+
attach_function :CGEventCreateMouseEvent, [:pointer, :uint32, CGPoint.by_value, :uint32], :pointer
|
21
|
+
attach_function :CGEventPost, %i[uint32 pointer], :void
|
22
|
+
attach_function :CGEventSetType, %i[pointer uint32], :void
|
23
|
+
attach_function :CFRelease, [:pointer], :void
|
24
|
+
attach_function :CGMainDisplayID, [], :uint32
|
25
|
+
attach_function :CGEventCreate, [:pointer], :pointer
|
26
|
+
attach_function :CGEventGetLocation, [:pointer], CGPoint.by_value
|
27
|
+
attach_function :CGDisplayPixelsHigh, [:uint32], :size_t
|
28
|
+
|
29
|
+
class CGSize < FFI::Struct
|
30
|
+
layout :width, :float,
|
31
|
+
:height, :float
|
32
|
+
end
|
33
|
+
|
34
|
+
LEFT_DOWN = 1
|
35
|
+
LEFT_UP = 2
|
36
|
+
RIGHT_DOWN = 3
|
37
|
+
RIGHT_UP = 4
|
38
|
+
MOUSE_MOVED = 5
|
39
|
+
|
40
|
+
def initialize(element: nil, client: nil)
|
41
|
+
raise InvalidPlatformError, 'MouseController is only supported on macOS' unless mac?
|
42
|
+
|
43
|
+
super
|
44
|
+
@main_display = CGMainDisplayID()
|
45
|
+
@display_height = CGDisplayPixelsHigh(@main_display).to_f
|
46
|
+
@scale_factor = determine_scale_factor
|
47
|
+
end
|
48
|
+
|
49
|
+
def hover
|
50
|
+
point = convert_coordinates(target_x, target_y)
|
51
|
+
create_and_post_event(MOUSE_MOVED, point)
|
52
|
+
current_mouse_position
|
53
|
+
end
|
54
|
+
|
55
|
+
def click
|
56
|
+
current_pos = current_mouse_position
|
57
|
+
create_and_post_event(LEFT_DOWN, current_pos)
|
58
|
+
create_and_post_event(LEFT_UP, current_pos)
|
59
|
+
end
|
60
|
+
|
61
|
+
def right_click
|
62
|
+
current_pos = current_mouse_position
|
63
|
+
create_and_post_event(RIGHT_DOWN, current_pos)
|
64
|
+
create_and_post_event(RIGHT_UP, current_pos)
|
65
|
+
end
|
66
|
+
|
67
|
+
def double_click
|
68
|
+
click
|
69
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
70
|
+
click
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def create_and_post_event(event_type, point)
|
76
|
+
event = CGEventCreateMouseEvent(nil, event_type, point, 0)
|
77
|
+
CGEventPost(0, event)
|
78
|
+
CFRelease(event)
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_mouse_position
|
82
|
+
event = CGEventCreate(nil)
|
83
|
+
return CGPoint.new if event.null?
|
84
|
+
|
85
|
+
system_point = CGEventGetLocation(event)
|
86
|
+
CFRelease(event)
|
87
|
+
|
88
|
+
# Convertir les coordonnées système en coordonnées navigateur
|
89
|
+
browser_x = system_point[:x] / @scale_factor
|
90
|
+
browser_y = (@display_height - system_point[:y]) / @scale_factor
|
91
|
+
|
92
|
+
@mouse_position = {
|
93
|
+
x: browser_x,
|
94
|
+
y: browser_y
|
95
|
+
}
|
96
|
+
|
97
|
+
# Retourner un nouveau CGPoint avec les coordonnées système
|
98
|
+
CGPoint.new.tap do |p|
|
99
|
+
p[:x] = system_point[:x]
|
100
|
+
p[:y] = system_point[:y]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def convert_coordinates(browser_x, browser_y)
|
105
|
+
# Convertir les coordonnées du navigateur en coordonnées système
|
106
|
+
system_x = browser_x * @scale_factor
|
107
|
+
system_y = @display_height - (browser_y * @scale_factor)
|
108
|
+
|
109
|
+
CGPoint.new.tap do |p|
|
110
|
+
p[:x] = system_x
|
111
|
+
p[:y] = system_y
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def determine_scale_factor
|
116
|
+
# Obtenir le facteur d'échelle en comparant les dimensions logiques et physiques
|
117
|
+
# Par défaut, utiliser 2.0 pour les écrans Retina
|
118
|
+
|
119
|
+
`system_profiler SPDisplaysDataType | grep -i "retina"`.empty? ? 1.0 : 2.0
|
120
|
+
rescue StandardError
|
121
|
+
2.0 # Par défaut pour les écrans Retina modernes
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Test
|
129
|
+
# require 'chromate/hardwares/mouses/mac_os_controller'
|
130
|
+
# require 'ostruct'
|
131
|
+
# element = OpenStruct.new(x: 500, y: 300, width: 100, height: 100)
|
132
|
+
# mouse = Chromate::Hardwares::Mouse::MacOsController.new(element: element)
|
133
|
+
# mouse.hover
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
module Hardwares
|
5
|
+
module Mouses
|
6
|
+
class VirtualController < Chromate::Hardwares::MouseController
|
7
|
+
def hover
|
8
|
+
steps = rand(25..50)
|
9
|
+
points = bezier_curve(steps: steps)
|
10
|
+
duration = rand(0.1..0.3)
|
11
|
+
|
12
|
+
points.each do |point|
|
13
|
+
dispatch_mouse_event('mouseMoved', point[:x], point[:y])
|
14
|
+
sleep(duration / steps)
|
15
|
+
end
|
16
|
+
|
17
|
+
update_mouse_position(points.last[:x], points.last[:y])
|
18
|
+
end
|
19
|
+
|
20
|
+
def click
|
21
|
+
hover
|
22
|
+
click!
|
23
|
+
end
|
24
|
+
|
25
|
+
def double_click
|
26
|
+
click
|
27
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
28
|
+
click
|
29
|
+
end
|
30
|
+
|
31
|
+
def right_click
|
32
|
+
hover
|
33
|
+
dispatch_mouse_event('mousePressed', target_x, target_y, button: 'right', click_count: 1)
|
34
|
+
sleep(rand(CLICK_DURATION_RANGE))
|
35
|
+
dispatch_mouse_event('mouseReleased', target_x, target_y, button: 'right', click_count: 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def click!
|
41
|
+
dispatch_mouse_event('mousePressed', target_x, target_y, button: 'left', click_count: 1)
|
42
|
+
sleep(rand(CLICK_DURATION_RANGE))
|
43
|
+
dispatch_mouse_event('mouseReleased', target_x, target_y, button: 'left', click_count: 1)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] type mouseMoved, mousePressed, mouseReleased
|
47
|
+
# @param [Integer] target_x
|
48
|
+
# @param [Integer] target_y
|
49
|
+
# @option [String] button
|
50
|
+
# @option [Integer] click_count
|
51
|
+
def dispatch_mouse_event(type, target_x, target_y, button: 'none', click_count: 0)
|
52
|
+
params = {
|
53
|
+
type: type,
|
54
|
+
x: target_x,
|
55
|
+
y: target_y,
|
56
|
+
button: button,
|
57
|
+
clickCount: click_count,
|
58
|
+
deltaX: 0,
|
59
|
+
deltaY: 0,
|
60
|
+
modifiers: 0,
|
61
|
+
timestamp: (Time.now.to_f * 1000).to_i
|
62
|
+
}
|
63
|
+
|
64
|
+
client.send_message('Input.dispatchMouseEvent', params)
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_mouse_position(target_x, target_y)
|
68
|
+
@mouse_position[:x] = target_x
|
69
|
+
@mouse_position[:y] = target_y
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Test
|
77
|
+
# require 'chromate/hardwares/mouses/virtual_controller'
|
78
|
+
# require 'ostruct'
|
79
|
+
# element = OpenStruct.new(x: 500, y: 300, width: 100, height: 100)
|
80
|
+
# mouse = Chromate::Hardwares::Mouse::VirtualController.new(element: element)
|
81
|
+
# mouse.hover
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/c_logger'
|
4
|
+
require 'chromate/hardwares/mouse_controller'
|
5
|
+
require 'chromate/hardwares/mouses/virtual_controller'
|
6
|
+
require 'chromate/helpers'
|
7
|
+
|
8
|
+
module Chromate
|
9
|
+
module Hardwares
|
10
|
+
extend Helpers
|
11
|
+
|
12
|
+
def mouse(**args)
|
13
|
+
browser = args[:client].browser
|
14
|
+
if browser.options[:native_control]
|
15
|
+
if mac?
|
16
|
+
Chromate::CLogger.log('🐁 Loading MacOs mouse controller')
|
17
|
+
require 'chromate/hardwares/mouses/mac_os_controller'
|
18
|
+
return Mouses::MacOsController.new(**args)
|
19
|
+
end
|
20
|
+
if linux?
|
21
|
+
Chromate::CLogger.log('🐁 Loading Linux mouse controller')
|
22
|
+
require 'chromate/hardwares/mouses/linux_controller'
|
23
|
+
return Mouses::LinuxController.new(**args)
|
24
|
+
end
|
25
|
+
raise 'Native mouse controller is not supported on Windows' if windows?
|
26
|
+
else
|
27
|
+
Chromate::CLogger.log('🐁 Loading Virtual mouse controller')
|
28
|
+
Mouses::VirtualController.new(**args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module_function :mouse
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
module Helpers
|
5
|
+
def linux?
|
6
|
+
RbConfig::CONFIG['host_os'] =~ /linux|bsd/i
|
7
|
+
end
|
8
|
+
|
9
|
+
def mac?
|
10
|
+
RbConfig::CONFIG['host_os'] =~ /darwin/i
|
11
|
+
end
|
12
|
+
|
13
|
+
def windows?
|
14
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_available_port
|
18
|
+
server = TCPServer.new('127.0.0.1', 0)
|
19
|
+
port = server.addr[1]
|
20
|
+
server.close
|
21
|
+
port
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
class UserAgent
|
5
|
+
def self.call
|
6
|
+
new.call
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :os
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@os = find_os
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_os
|
22
|
+
case RUBY_PLATFORM
|
23
|
+
when /darwin/
|
24
|
+
'Macintosh'
|
25
|
+
when /linux/
|
26
|
+
'Linux'
|
27
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
28
|
+
'Windows'
|
29
|
+
else
|
30
|
+
'Unknown'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/chromate.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'chromate/version'
|
4
|
+
require_relative 'chromate/browser'
|
5
|
+
require_relative 'chromate/configuration'
|
6
|
+
|
7
|
+
module Chromate
|
8
|
+
class << self
|
9
|
+
def configure
|
10
|
+
yield configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|