chromate-rb 0.0.2.pre → 0.0.3.pre

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/CHANGELOG.md +20 -2
  4. data/Rakefile +2 -2
  5. data/docker_root/app.rb +6 -11
  6. data/docs/BOT_BROWSER.md +74 -0
  7. data/docs/browser.md +20 -55
  8. data/docs/client.md +126 -0
  9. data/docs/element.md +77 -1
  10. data/docs/elements/checkbox.md +69 -0
  11. data/docs/elements/radio.md +57 -0
  12. data/lib/bot_browser/downloader.rb +38 -26
  13. data/lib/bot_browser/installer.rb +27 -9
  14. data/lib/bot_browser.rb +5 -1
  15. data/lib/chromate/actions/dom.rb +24 -35
  16. data/lib/chromate/actions/navigate.rb +3 -0
  17. data/lib/chromate/actions/screenshot.rb +52 -14
  18. data/lib/chromate/actions/stealth.rb +38 -23
  19. data/lib/chromate/binary.rb +83 -0
  20. data/lib/chromate/browser.rb +70 -26
  21. data/lib/chromate/c_logger.rb +1 -0
  22. data/lib/chromate/client.rb +25 -8
  23. data/lib/chromate/configuration.rb +2 -2
  24. data/lib/chromate/element.rb +62 -9
  25. data/lib/chromate/elements/checkbox.rb +40 -0
  26. data/lib/chromate/elements/option.rb +43 -0
  27. data/lib/chromate/elements/radio.rb +37 -0
  28. data/lib/chromate/elements/select.rb +10 -18
  29. data/lib/chromate/elements/tags.rb +29 -0
  30. data/lib/chromate/exceptions.rb +2 -0
  31. data/lib/chromate/files/agents.json +11 -0
  32. data/lib/chromate/files/stealth.js +199 -0
  33. data/lib/chromate/hardwares/keyboard_controller.rb +11 -0
  34. data/lib/chromate/hardwares/mouse_controller.rb +8 -0
  35. data/lib/chromate/hardwares.rb +4 -4
  36. data/lib/chromate/user_agent.rb +14 -12
  37. data/lib/chromate/version.rb +1 -1
  38. data/results/bot.png +0 -0
  39. data/results/brotector.png +0 -0
  40. data/results/cloudflare.png +0 -0
  41. data/results/pixelscan.png +0 -0
  42. metadata +27 -2
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Script de Stealth pour masquer l'automatisation
3
+ *
4
+ * Ce script est destiné à être injecté via la commande
5
+ * Page.addScriptToEvaluateOnNewDocument afin qu'il s'exécute
6
+ * avant le chargement du contenu de la page.
7
+ *
8
+ * Il redéfinit plusieurs propriétés du navigateur pour
9
+ * réduire les indices susceptibles d'indiquer qu'une automatisation est en cours.
10
+ */
11
+ (() => {
12
+ 'use strict';
13
+
14
+ // 1) Masquer navigator.webdriver
15
+ (function removeWebDriverProperty() {
16
+ // 1) Récupérer le prototype de Navigator
17
+ const proto = Object.getPrototypeOf(navigator);
18
+
19
+ // Vérifier si la propriété webdriver existe sur le prototype
20
+ if ('webdriver' in proto) {
21
+ try {
22
+ // 2) Tenter de supprimer la propriété si elle est configurable
23
+ const webdriverDescriptor = Object.getOwnPropertyDescriptor(proto, 'webdriver');
24
+ if (webdriverDescriptor && webdriverDescriptor.configurable) {
25
+ delete proto.webdriver;
26
+ } else {
27
+ // 3) Sinon, on essaye de la redéfinir pour qu'elle retourne undefined et ne soit pas énumérable
28
+ Object.defineProperty(proto, 'webdriver', {
29
+ get: () => undefined,
30
+ configurable: false, // on la rend non-configurable pour éviter d'autres re-déclarations
31
+ enumerable: false
32
+ });
33
+ }
34
+ } catch (err) {
35
+ // 4) En cas d'échec (non-configurable), on peut tenter un hack sur l'opérateur 'in'
36
+ // => ATTENTION : ceci peut avoir des effets de bord dans d'autres scripts
37
+ patchInOperatorForNavigator('webdriver');
38
+ }
39
+ } else {
40
+ // Si la propriété n'existe pas sur le prototype, vérifier si elle existe directement sur navigator
41
+ if ('webdriver' in navigator) {
42
+ try {
43
+ delete navigator.webdriver;
44
+ } catch (err) {
45
+ patchInOperatorForNavigator('webdriver');
46
+ }
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Hack optionnel pour empêcher `'webdriver' in navigator` de renvoyer true.
52
+ * On redéfinit la méthode hasOwnProperty / l'opérateur in pour l'objet navigator.
53
+ * Cette approche peut avoir des effets de bord, donc à utiliser en dernier recours.
54
+ */
55
+ function patchInOperatorForNavigator(propName) {
56
+ const originalHasOwn = Object.prototype.hasOwnProperty;
57
+ Object.prototype.hasOwnProperty = function (property) {
58
+ // Si c'est navigator et qu'on teste la propriété 'webdriver', on la cache
59
+ if (this === navigator && property === propName) {
60
+ return false;
61
+ }
62
+ return originalHasOwn.call(this, property);
63
+ };
64
+
65
+ // Variante plus radicale : proxyfier l'objet navigator pour intercepter l'opérateur 'in'.
66
+ // (Non présenté ici, car encore plus invasif.)
67
+ }
68
+ })();
69
+
70
+ // 2) Surcharger navigator.permissions.query (pour ne pas renvoyer "default")
71
+ if (navigator.permissions && navigator.permissions.query) {
72
+ const originalQuery = navigator.permissions.query;
73
+ navigator.permissions.query = (params) => {
74
+ // Ex. : si on interroge les notifications, on renvoie "granted"
75
+ if (params.name === 'notifications') {
76
+ return Promise.resolve({ state: 'granted' });
77
+ }
78
+ // Pour les autres, on tente la requête d'origine, sinon "granted"
79
+ return originalQuery(params).catch(() => ({ state: 'granted' }));
80
+ };
81
+ }
82
+
83
+ // 3) Créer un PluginArray réaliste
84
+ // - On réutilise le prototype existant pour ne pas éveiller de soupçons
85
+ const pluginArrayProto = Object.getPrototypeOf(navigator.plugins);
86
+ function FakePlugin(name, description, filename) {
87
+ this.name = name;
88
+ this.description = description;
89
+ this.filename = filename;
90
+ }
91
+ // On pointe vers Plugin.prototype pour se comporter comme un plugin "réel"
92
+ FakePlugin.prototype = Plugin.prototype;
93
+
94
+ const fakePlugins = [
95
+ new FakePlugin('Chrome PDF Plugin', 'Portable Document Format', 'internal-pdf-viewer'),
96
+ new FakePlugin('Chrome PDF Viewer', '', 'mhjfbmdgcfjbbpaeojofohoefgiehjai')
97
+ ];
98
+
99
+ Object.defineProperty(navigator, 'plugins', {
100
+ get() {
101
+ const pluginArray = Object.create(pluginArrayProto);
102
+ // On copie les faux plugins dans l’objet
103
+ for (let i = 0; i < fakePlugins.length; i++) {
104
+ pluginArray[i] = fakePlugins[i];
105
+ }
106
+ pluginArray.length = fakePlugins.length;
107
+ return pluginArray;
108
+ }
109
+ });
110
+
111
+ // 4) Aligner navigator.languages avec vos entêtes Accept-Language
112
+ Object.defineProperty(navigator, 'languages', {
113
+ get: () => ['en-US', 'en'],
114
+ configurable: true
115
+ });
116
+
117
+ // 5) Override de getContext pour forcer un contexte WebGL factice
118
+ const originalGetContext = HTMLCanvasElement.prototype.getContext;
119
+ HTMLCanvasElement.prototype.getContext = function (type, ...args) {
120
+ if (['webgl', 'experimental-webgl', 'webgl2'].includes(type)) {
121
+ // Tenter d'obtenir le contexte natif (au cas où)
122
+ let ctx = originalGetContext.apply(this, [type, ...args]);
123
+ if (ctx) {
124
+ // Si un contexte natif est obtenu, retourner ce contexte
125
+ return ctx;
126
+ }
127
+ console.log("No native WebGL context found, returning fake context");
128
+ // Forcer la création d'un contexte factice
129
+ const proto = (window.WebGLRenderingContext && window.WebGLRenderingContext.prototype) || {};
130
+ const fakeContext = Object.create(proto);
131
+
132
+ fakeContext.getParameter = function (param) {
133
+ if (param === 37445) return 'Intel Inc.';
134
+ if (param === 37446) return 'Intel Iris OpenGL Engine';
135
+ return null;
136
+ };
137
+ fakeContext.getSupportedExtensions = function () {
138
+ return ['WEBGL_debug_renderer_info'];
139
+ };
140
+ fakeContext.getExtension = function (name) {
141
+ if (name === 'WEBGL_debug_renderer_info') {
142
+ return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 };
143
+ }
144
+ return null;
145
+ };
146
+
147
+ // Ajout de stubs pour d'autres méthodes WebGL essentielles
148
+ fakeContext.clear = function () { };
149
+ fakeContext.clearColor = function () { };
150
+ fakeContext.viewport = function () { };
151
+ fakeContext.createShader = function () { return {}; };
152
+ fakeContext.shaderSource = function () { };
153
+ fakeContext.compileShader = function () { };
154
+ fakeContext.createProgram = function () { return {}; };
155
+ fakeContext.attachShader = function () { };
156
+ fakeContext.linkProgram = function () { };
157
+ fakeContext.useProgram = function () { };
158
+
159
+ return fakeContext;
160
+ }
161
+ return originalGetContext.apply(this, [type, ...args]);
162
+ };
163
+
164
+ // 6) Optionnel : Override de toDataURL pour renvoyer une image fixe
165
+ const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
166
+ HTMLCanvasElement.prototype.toDataURL = function (...args) {
167
+ const gl = this.getContext('webgl') || this.getContext('experimental-webgl') || this.getContext('webgl2');
168
+ if (gl) {
169
+ return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=";
170
+ }
171
+ return originalToDataURL.apply(this, args);
172
+ };
173
+
174
+ // 7) Correction des dimensions d’images
175
+ Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', {
176
+ get() { return 128; }
177
+ });
178
+ Object.defineProperty(HTMLImageElement.prototype, 'naturalHeight', {
179
+ get() { return 128; }
180
+ });
181
+
182
+ // 8) Randomisation pour varier l'injection
183
+ (function () {
184
+ const randomSuffix = Math.random().toString(36).substring(2);
185
+ document.documentElement.setAttribute('data-stealth', randomSuffix);
186
+ })();
187
+
188
+ // 9) Masquer la modification des fonctions natives
189
+ (function () {
190
+ const originalToString = Function.prototype.toString;
191
+ Function.prototype.toString = function () {
192
+ if (this === navigator.permissions.query) {
193
+ return "function query() { [native code] }";
194
+ }
195
+ return originalToString.apply(this, arguments);
196
+ };
197
+ })();
198
+
199
+ })();
@@ -13,6 +13,15 @@ module Chromate
13
13
  @type_interval = rand(0.05..0.1)
14
14
  end
15
15
 
16
+ # @param [Chromate::Element] element
17
+ # @return [self]
18
+ def set_element(element) # rubocop:disable Naming/AccessorMethodName
19
+ @element = element
20
+ @type_interval = rand(0.05..0.1)
21
+
22
+ self
23
+ end
24
+
16
25
  # @param [String] key
17
26
  # @return [self]
18
27
  def press_key(_key)
@@ -23,6 +32,8 @@ module Chromate
23
32
  # @return [self]
24
33
  def type(text)
25
34
  text.each_char do |char|
35
+ sleep(rand(0.01..0.05)) if rand(10).zero?
36
+
26
37
  press_key(char)
27
38
  sleep(@type_interval)
28
39
  end
@@ -19,6 +19,14 @@ module Chromate
19
19
  @client = client
20
20
  end
21
21
 
22
+ # @param [Chromate::Element] element
23
+ # @return [self]
24
+ def set_element(element) # rubocop:disable Naming/AccessorMethodName
25
+ @element = element
26
+
27
+ self
28
+ end
29
+
22
30
  # @return [Hash]
23
31
  def mouse_position
24
32
  @@mouse_position ||= { x: 0, y: 0 } # rubocop:disable Style/ClassVars
@@ -19,18 +19,18 @@ module Chromate
19
19
  browser = args[:client].browser
20
20
  if browser.options[:native_control]
21
21
  if mac?
22
- Chromate::CLogger.log('🐁 Loading MacOs mouse controller')
22
+ Chromate::CLogger.log('👨🏼‍💻🐁 Loading MacOs mouse controller')
23
23
  require 'chromate/hardwares/mouses/mac_os_controller'
24
24
  return Mouses::MacOsController.new(**args)
25
25
  end
26
26
  if linux?
27
- Chromate::CLogger.log('🐁 Loading Linux mouse controller')
27
+ Chromate::CLogger.log('👨🏼‍💻🐁 Loading Linux mouse controller')
28
28
  require 'chromate/hardwares/mouses/linux_controller'
29
29
  return Mouses::LinuxController.new(**args)
30
30
  end
31
31
  raise 'Native mouse controller is not supported on Windows' if windows?
32
32
  else
33
- Chromate::CLogger.log('🐁 Loading Virtual mouse controller')
33
+ Chromate::CLogger.log('🤖🐁 Loading Virtual mouse controller')
34
34
  Mouses::VirtualController.new(**args)
35
35
  end
36
36
  end
@@ -41,7 +41,7 @@ module Chromate
41
41
  # @option args [Chromate::Element] :element
42
42
  # @return [Chromate::Hardwares::KeyboardController]
43
43
  def keyboard(**args)
44
- Chromate::CLogger.log('⌨️ Loading Virtual keyboard controller')
44
+ Chromate::CLogger.log('🤖⌨️ Loading Virtual keyboard controller')
45
45
  Keyboards::VirtualController.new(**args)
46
46
  end
47
47
  module_function :keyboard
@@ -30,29 +30,31 @@ module Chromate
30
30
  end
31
31
  end
32
32
 
33
- def self.os_version
34
- case os
35
- when 'Linux'
36
- '5.15'
37
- when 'Mac'
38
- '13.0'
39
- when 'Windows'
40
- '10.0'
33
+ def self.arch
34
+ case RUBY_PLATFORM
35
+ when /x64-mingw32/
36
+ 'x64'
37
+ when /x86_64-mingw32/
38
+ 'x86_64'
41
39
  else
42
- raise 'Unknown OS'
40
+ 'x86'
43
41
  end
44
42
  end
45
43
 
44
+ def self.agents
45
+ @agents ||= JSON.parse(File.read(File.join(__dir__, 'files/agents.json')))
46
+ end
47
+
46
48
  def self.linux_agent
47
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
49
+ agents['linux'].sample
48
50
  end
49
51
 
50
52
  def self.mac_agent
51
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
53
+ agents['mac'].sample
52
54
  end
53
55
 
54
56
  def self.windows_agent
55
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
57
+ agents['windows'].sample
56
58
  end
57
59
  end
58
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chromate
4
- VERSION = '0.0.2.pre'
4
+ VERSION = '0.0.3.pre'
5
5
  end
data/results/bot.png CHANGED
Binary file
Binary file
Binary file
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chromate-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.pre
4
+ version: 0.0.3.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eth3rnit3
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-24 00:00:00.000000000 Z
11
+ date: 2025-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.17.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: user_agent_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.18.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.18.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: websocket-client-simple
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -61,9 +75,13 @@ files:
61
75
  - dockerfiles/Dockerfile
62
76
  - dockerfiles/README.md
63
77
  - dockerfiles/docker-entrypoint.sh
78
+ - docs/BOT_BROWSER.md
64
79
  - docs/README.md
65
80
  - docs/browser.md
81
+ - docs/client.md
66
82
  - docs/element.md
83
+ - docs/elements/checkbox.md
84
+ - docs/elements/radio.md
67
85
  - lib/bot_browser.rb
68
86
  - lib/bot_browser/downloader.rb
69
87
  - lib/bot_browser/installer.rb
@@ -72,13 +90,20 @@ files:
72
90
  - lib/chromate/actions/navigate.rb
73
91
  - lib/chromate/actions/screenshot.rb
74
92
  - lib/chromate/actions/stealth.rb
93
+ - lib/chromate/binary.rb
75
94
  - lib/chromate/browser.rb
76
95
  - lib/chromate/c_logger.rb
77
96
  - lib/chromate/client.rb
78
97
  - lib/chromate/configuration.rb
79
98
  - lib/chromate/element.rb
99
+ - lib/chromate/elements/checkbox.rb
100
+ - lib/chromate/elements/option.rb
101
+ - lib/chromate/elements/radio.rb
80
102
  - lib/chromate/elements/select.rb
103
+ - lib/chromate/elements/tags.rb
81
104
  - lib/chromate/exceptions.rb
105
+ - lib/chromate/files/agents.json
106
+ - lib/chromate/files/stealth.js
82
107
  - lib/chromate/hardwares.rb
83
108
  - lib/chromate/hardwares/keyboard_controller.rb
84
109
  - lib/chromate/hardwares/keyboards/virtual_controller.rb