fantomas 0.1.2__py3-none-any.whl

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.
fantomas/proxy.py ADDED
@@ -0,0 +1,193 @@
1
+ import base64
2
+ import asyncio
3
+ import time
4
+ import os
5
+ import subprocess
6
+ from billiard import Process, Queue
7
+ from mitmproxy import http
8
+ from mitmproxy.tools.dump import DumpMaster
9
+ from mitmproxy.options import Options
10
+ from urllib.parse import urlparse
11
+ import requests
12
+ from .utils import get_value_or_default, load_config
13
+
14
+ class Proxy:
15
+ def __init__(self,config_proxy=None):
16
+ self.config_proxy = config_proxy
17
+ self.upstream_enabled = 0
18
+ self.upstream_provider = ""
19
+ self.upstream_request_type = "broker"
20
+ self.proxy_process = None
21
+ self.count_data_queue = None
22
+ self.modifiers_array = []
23
+ self.retrievers_array = []
24
+ self.modifiers_request_array = []
25
+
26
+ if self.config_proxy:
27
+ self._parse_config_proxy()
28
+
29
+
30
+ def launch_proxy(self):
31
+ self._clean_previous_old_proxy()
32
+ time.sleep(0.3)
33
+
34
+ if self.upstream_enabled:
35
+ upstream_proxy_url = self.fetch_upstream_proxy()
36
+ else:
37
+ upstream_proxy_url = None
38
+
39
+ self.set_data_queue()
40
+
41
+ self.proxy_process = SubprocessedMitmProxy(
42
+ upstream_proxy_url=upstream_proxy_url,
43
+ count_data_queue=self.count_data_queue,
44
+ upstream_enabled=self.upstream_enabled,
45
+ modifiers_array = self.modifiers_array,
46
+ retrievers_array=self.retrievers_array,
47
+ modifiers_request_array=self.modifiers_request_array
48
+ )
49
+ self.proxy_process.start()
50
+ return self
51
+
52
+ def exit_local_proxy(self):
53
+ self.proxy_process.terminate()
54
+
55
+ def switch_upstream_proxy(self):
56
+ self.exit_local_proxy()
57
+ self.launch_proxy()
58
+
59
+ def set_data_queue(self):
60
+ self.count_data_queue = Queue()
61
+ return self.count_data_queue
62
+
63
+ def get_data_count(self):
64
+ for _ in range(self.count_data_queue.qsize()):
65
+ element = self.count_data_queue.get()
66
+ return element
67
+
68
+ def retrieve(self,queue_id):
69
+ for retriever in self.retrievers_array:
70
+ if retriever["queue_id"]==queue_id:
71
+ for _ in range(retriever["queue"].qsize()):
72
+ element = retriever["queue"].get()
73
+ return element
74
+
75
+ def _clean_previous_old_proxy(self):
76
+ result = subprocess.run(f"lsof -t -i:{8081}", shell=True, capture_output=True, text=True)
77
+ if result.stdout.strip():
78
+ pid = result.stdout.strip()
79
+ os.system(f"kill -9 {pid}")
80
+ else:
81
+ pass
82
+
83
+
84
+ def _parse_config_proxy(self):
85
+ self.config_proxy = load_config(self.config_proxy) if isinstance(self.config_proxy, str) else self.config_proxy
86
+ self.enabled = get_value_or_default(self.config_proxy.get("proxy_enabled"), self.upstream_enabled)
87
+ self.upstream_enabled = get_value_or_default(self.config_proxy.get("proxy_upstream_enabled"), self.upstream_enabled)
88
+ self.upstream_provider = get_value_or_default(self.config_proxy.get("proxy_upstream_provider"), self.upstream_provider)
89
+ self.upstream_request_type = get_value_or_default(self.config_proxy.get("proxy_upstream_request_type"), self.upstream_request_type)
90
+
91
+
92
+ def fetch_upstream_proxy(self):
93
+ return UpstreamProxyFetcher(self.upstream_provider,self.upstream_request_type).fetch_proxy()
94
+
95
+ def add_retriever(self,queue_id: str,retriever_function):
96
+ queue = Queue()
97
+ proxy_queue_dict = {"queue_id":queue_id,"queue":queue,"retriever_function":retriever_function}
98
+ self.retrievers_array.append(proxy_queue_dict)
99
+
100
+ def add_modifier(self,modifier_function):
101
+ self.modifiers_array.append(modifier_function)
102
+
103
+ def add_request_modifier(self,modifier_request_function):
104
+ self.modifiers_request_array.append(modifier_request_function)
105
+
106
+ class SubprocessedMitmProxy(Process):
107
+ def __init__(self, *, upstream_proxy_url: str, count_data_queue, upstream_enabled,modifiers_array,retrievers_array,modifiers_request_array):
108
+ super().__init__()
109
+ self.upstream_proxy_url = upstream_proxy_url
110
+ self.count_data_queue = count_data_queue
111
+ self.upstream_enabled = upstream_enabled
112
+ self.modifiers_array = modifiers_array
113
+ self.retrievers_array = retrievers_array
114
+ self.modifiers_request_array = modifiers_request_array
115
+
116
+ def run(self):
117
+ asyncio.run(self.asyncio_run())
118
+
119
+ async def asyncio_run(self):
120
+ upstream_mode = "upstream:http://"+self.upstream_proxy_url.split('@')[1]
121
+ if self.upstream_enabled:
122
+ opts = Options(listen_port=8081, mode=[upstream_mode], ssl_insecure=False)
123
+ else:
124
+ opts = Options(listen_port=8081, ssl_insecure=False)
125
+ master = DumpMaster(opts, with_termlog=False, with_dumper=False)
126
+ master.addons.add(ProxyAddOn(self.upstream_proxy_url, self.count_data_queue, self.modifiers_array, self.retrievers_array, self.modifiers_request_array))
127
+ await master.run()
128
+
129
+
130
+ class ProxyAddOn:
131
+ def __init__(self, upstream_proxy_url, count_data_queue, modifiers_array,retrievers_array,modifiers_request_array):
132
+ self.total_data = 0
133
+ self.upstream_proxy_url = upstream_proxy_url
134
+ self.count_data_queue = count_data_queue
135
+ self.proxy_parsed_url = None
136
+ self.modifiers_array = modifiers_array
137
+ self.retrievers_array = retrievers_array
138
+ self.modifiers_request_array = modifiers_request_array
139
+
140
+ def http_connect_upstream(self, flow: http.HTTPFlow):
141
+ self.proxy_parsed_url = urlparse(self.upstream_proxy_url)
142
+ if self.proxy_parsed_url.username or self.proxy_parsed_url.password:
143
+ credentials = f"{self.proxy_parsed_url.username}:{self.proxy_parsed_url.password}"
144
+ encoded_credentials = base64.b64encode(credentials.encode()).decode()
145
+ flow.request.headers["proxy-authorization"] = f"Basic {encoded_credentials}"
146
+
147
+ def response(self, flow: http.HTTPFlow) -> None:
148
+
149
+ #Data Count
150
+ if flow.response:
151
+ content_length = flow.response.headers.get('Content-Length')
152
+ if content_length:
153
+ self.total_data += int(content_length)
154
+ self.count_data_queue.put(self.total_data)
155
+
156
+ #Executing retrievers
157
+ for j in self.retrievers_array:
158
+ j["retriever_function"](flow, j["queue"])
159
+
160
+
161
+ #Executing modifiers
162
+ for i in self.modifiers_array:
163
+ i(flow)
164
+
165
+ def request(self, flow: http.HTTPFlow) -> None:
166
+ for i in self.modifiers_request_array:
167
+ i(flow)
168
+
169
+ class UpstreamProxyFetcher:
170
+ def __init__(self, provider, upstream_request_type):
171
+ self.ip = None
172
+ self.url = None
173
+ self.provider = provider
174
+ self.upstream_request_type = upstream_request_type
175
+
176
+ def fetch_proxy(self):
177
+ if self.upstream_request_type == "broker":
178
+ response = requests.get(self.provider)
179
+ if response.status_code == 200:
180
+ result = response.json()
181
+ self.ip = result.get('ip', 'No IP returned')
182
+ self.url = result.get('url', 'No URL returned')
183
+ else:
184
+ raise Exception(f"Request failed with status code {response.status_code}")
185
+ elif self.upstream_request_type == "direct":
186
+ self.url = self.provider
187
+
188
+ return self.url
189
+
190
+
191
+
192
+
193
+
@@ -0,0 +1,6 @@
1
+ import time
2
+ import random
3
+
4
+
5
+ def rsleep (sleeping_time, activate_random=True,factor=None):
6
+ time.sleep(sleeping_time)
fantomas/raw_chrome.py ADDED
@@ -0,0 +1,31 @@
1
+ import os
2
+ import subprocess
3
+ import threading
4
+ import socket
5
+
6
+ class RawChrome:
7
+ def __init__(self, display_number, browser_options, temp_path):
8
+ self.display_number = display_number
9
+ self.port = self.find_free_port()
10
+ self.temp_path = temp_path
11
+ self.browser_options = browser_options
12
+
13
+ def run_app_with_gpu(self):
14
+ env = os.environ.copy()
15
+ env["DISPLAY"] = self.display_number
16
+
17
+ command = ["vglrun","google-chrome","--user-data-dir="+str(self.temp_path),"--remote-debugging-port="+str(self.port)]
18
+ command.extend(self.browser_options)
19
+
20
+ subprocess.run(command, env=env)
21
+
22
+ def start_in_thread(self):
23
+ thread = threading.Thread(target=self.run_app_with_gpu, daemon=True)
24
+ thread.start()
25
+ return {"thread":thread, "port":self.port, "display":self.display_number}
26
+
27
+ @staticmethod
28
+ def find_free_port():
29
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
30
+ s.bind(('', 0)) # Let the OS choose a free port
31
+ return s.getsockname()[1] # Get the assigned port
fantomas/raw_screen.py ADDED
@@ -0,0 +1,47 @@
1
+ import subprocess
2
+ import os
3
+ import time
4
+ import signal
5
+ import random
6
+
7
+
8
+ class RawScreen:
9
+ def __init__(self):
10
+ self.display_number = self.attribute_display_number()
11
+ self.screen_res = "1900x1060"
12
+
13
+ def attribute_display_number(self):
14
+ x_sockets = os.listdir("/tmp/.X11-unix/")
15
+ x_displays = [int(s[1:]) for s in x_sockets if s.startswith("X")]
16
+ print(x_displays)
17
+ while True:
18
+ x = random.randint(1,1000)
19
+ if x not in x_displays:
20
+ self.display_number = ":"+str(x)
21
+ return ":"+str(x)
22
+
23
+ def launch_xephyr(self):
24
+
25
+ print(f"Lancement de Xephyr sur DISPLAY {self.display_number}...")
26
+ self.xephyr_process = subprocess.Popen(["Xephyr", self.display_number, "-screen", self.screen_res], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
27
+ return self.display_number
28
+
29
+ def wait_for_display_ready(self, timeout=5):
30
+ print("Attente de l'initialisation de Xephyr...")
31
+ for _ in range(timeout * 10):
32
+ result = subprocess.run(["xdpyinfo", "-display", self.display_number],
33
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
34
+ if result.returncode == 0:
35
+ print("Xephyr est prêt.")
36
+ return True
37
+ time.sleep(0.1)
38
+ raise RuntimeError("Timeout: Xephyr ne s'est pas lancé correctement.")
39
+
40
+ def exit_xephyr(self):
41
+ print("Fermeture de Xephyr...")
42
+ self.xephyr_process.send_signal(signal.SIGTERM)
43
+ self.xephyr_process.wait()
44
+
45
+
46
+
47
+
fantomas/screen.py ADDED
@@ -0,0 +1,41 @@
1
+ import subprocess
2
+ import io
3
+ from pyvirtualdisplay import Display
4
+ from .utils import get_value_or_default, load_config
5
+
6
+
7
+ class Screen:
8
+ def __init__(self, screen_params=None):
9
+ self.screen_params = screen_params
10
+ self.visible = 1
11
+ self.height = 1200
12
+ self.width = 800
13
+ self.display = None
14
+ if self.screen_params:
15
+ self.parse_screen_params()
16
+
17
+ def parse_screen_params(self):
18
+ self.screen_params = load_config(self.screen_params)
19
+ self.visible = get_value_or_default(self.screen_params.get("screen_visible"), self.visible)
20
+ self.height = get_value_or_default(self.screen_params.get("screen_height"), self.height)
21
+ self.width = get_value_or_default(self.screen_params.get("screen_width"), self.width)
22
+
23
+ def screenshot_screen(self):
24
+ screenshot_process = subprocess.run(
25
+ ["import", "-window", "root", "png:-"],
26
+ stdout=subprocess.PIPE,
27
+ stderr=subprocess.DEVNULL)
28
+ if not screenshot_process.stdout:
29
+ raise ValueError("Empty Screenshot")
30
+
31
+ image_bytes = io.BytesIO(screenshot_process.stdout)
32
+ image_bytes.seek(0)
33
+ return image_bytes
34
+
35
+ def launch_screen(self):
36
+ self.display = Display(visible=self.visible, size=(self.width, self.height))
37
+ self.display.start()
38
+ return self
39
+
40
+ def stop_screen(self):
41
+ self.display.stop()
@@ -0,0 +1,27 @@
1
+ from .fantomas_no_driver import FantomasNoDriver
2
+ import time
3
+
4
+
5
+ class SecondaryFlow():
6
+
7
+ def __init__(self, sf_ctx,sf_function):
8
+ self.sf_ctx = sf_ctx
9
+ self.sf_no_driver_instance = sf_ctx.sf_no_driver_instance
10
+ self.sf_browser = sf_ctx.sf_browser
11
+ self.sf_function = sf_function
12
+
13
+ def launch_secondary_flow(self):
14
+ import multiprocessing
15
+ sf_queue = multiprocessing.Queue()
16
+ sf_process = multiprocessing.Process(target=self.launch_secondary_browser_sync, args=(sf_queue,self.sf_function))
17
+ sf_process.start()
18
+ sf_process.join()
19
+ queue_result = sf_queue.get()
20
+ return queue_result
21
+
22
+ def launch_secondary_browser_sync(self, sf_queue,sf_function):
23
+ self.sf_no_driver_instance.loop().run_until_complete(self.launch_secondary_browser_async(sf_queue,sf_function))
24
+
25
+ async def launch_secondary_browser_async(self,sf_queue,sf_function):
26
+ await sf_function(self.sf_browser, self.sf_ctx, sf_queue)
27
+
fantomas/utils.py ADDED
@@ -0,0 +1,21 @@
1
+ import json
2
+
3
+
4
+ def get_value_or_default(value, default):
5
+ """Return value if not None, otherwise return default."""
6
+ return value if value is not None else default
7
+
8
+
9
+ def load_config(config):
10
+ """Load configuration from file path or return dict as-is.
11
+
12
+ Args:
13
+ config: Either a file path (str) to a JSON file or a dict
14
+
15
+ Returns:
16
+ dict: The configuration dictionary
17
+ """
18
+ if isinstance(config, str):
19
+ with open(config, 'r', encoding='utf-8') as json_file:
20
+ return json.load(json_file)
21
+ return config
@@ -0,0 +1,76 @@
1
+ import numpy as np
2
+
3
+ class VirtualCursorPath:
4
+ def __init__(self):
5
+ pass
6
+
7
+ def get_virtual_cursor_path(self,current_position,desired_position,viewport_width,viewport_height):
8
+ path = self._calculate_path(current_position[0],current_position[1],desired_position[0],desired_position[1],viewport_width,viewport_height)
9
+ path[0]=self._avoid_bounds(path[0], 0,viewport_width)
10
+ path[1]=self._avoid_bounds(path[1], 0,viewport_height)
11
+ return path
12
+
13
+
14
+ @staticmethod
15
+ def _avoid_bounds(array_of_cursor_coordinates,minima_coordinate,maxima_coordinate):
16
+ array_avoiding_minima = [1 if cursor_coordinate == minima_coordinate else cursor_coordinate for cursor_coordinate in array_of_cursor_coordinates]
17
+ array_avoiding_maxima = [maxima_coordinate-1 if cursor_coordinate == maxima_coordinate else cursor_coordinate for cursor_coordinate in array_avoiding_minima]
18
+ return array_avoiding_maxima
19
+
20
+ @staticmethod
21
+ def _calculate_path(start_x, start_y,dest_x, dest_y, x_max, y_max, G_0=5, W_0=10, M_0=15, D_0=30):
22
+ sqrt3 = np.sqrt(3)
23
+ sqrt5 = np.sqrt(5)
24
+ x_array = []
25
+ y_array = []
26
+ current_x,current_y = start_x,start_y
27
+ v_x = v_y = W_x = W_y = 0
28
+ while True:
29
+ dist = np.hypot(dest_x - start_x, dest_y - start_y)
30
+ if dist < 1:
31
+ break
32
+
33
+ # Force calculation
34
+ W_mag = min(W_0, dist)
35
+ if dist >= D_0:
36
+ W_x = W_x / sqrt3 + (2 * np.random.random() - 1) * W_mag / sqrt5
37
+ W_y = W_y / sqrt3 + (2 * np.random.random() - 1) * W_mag / sqrt5
38
+ else:
39
+ W_x /= sqrt3
40
+ W_y /= sqrt3
41
+ if M_0 < 3:
42
+ M_0 = np.random.random() * 3 + 3
43
+ else:
44
+ M_0 /= sqrt5
45
+
46
+ # Speed calculation
47
+ v_x += (W_x + G_0 * (dest_x - start_x) / dist)
48
+ v_y += (W_y + G_0 * (dest_y - start_y) / dist)
49
+
50
+ # Limit the maximum speed
51
+ v_mag = np.hypot(v_x, v_y)
52
+ if v_mag > M_0:
53
+ v_x = (v_x / v_mag) * M_0
54
+ v_y = (v_y / v_mag) * M_0
55
+
56
+ # Update positions
57
+ start_x += v_x
58
+ start_y += v_y
59
+
60
+ # Limit positions to [0, x_max] and [0, y_max]
61
+ start_x = max(0, min(start_x, x_max))
62
+ start_y = max(0, min(start_y, y_max))
63
+
64
+ move_x = int(np.round(start_x))
65
+ move_y = int(np.round(start_y))
66
+
67
+ if current_x != move_x or current_y != move_y:
68
+ current_x = move_x
69
+ current_y = move_y
70
+ x_array.append(current_x)
71
+ y_array.append(current_y)
72
+ return [x_array,y_array]
73
+
74
+
75
+ if __name__ == "__main__":
76
+ path = VirtualCursorPath().get_virtual_cursor_path([0,0],[100,200],1000,1000)