atomicshop 2.16.42__py3-none-any.whl → 2.16.43__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.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/basics/bytes_arrays.py +24 -0
- atomicshop/dns.py +11 -0
- atomicshop/http_parse.py +4 -4
- atomicshop/mitm/connection_thread_worker.py +2 -7
- atomicshop/mitm/mitm_main.py +9 -4
- atomicshop/web.py +5 -2
- atomicshop/websocket_parse.py +176 -0
- atomicshop/wrappers/factw/install/install_after_restart.py +13 -2
- atomicshop/wrappers/mongodbw/mongodbw.py +5 -2
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/scenarios.py +14 -5
- atomicshop/wrappers/winregw/winreg_network.py +45 -16
- {atomicshop-2.16.42.dist-info → atomicshop-2.16.43.dist-info}/METADATA +2 -1
- {atomicshop-2.16.42.dist-info → atomicshop-2.16.43.dist-info}/RECORD +18 -17
- {atomicshop-2.16.42.dist-info → atomicshop-2.16.43.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.16.42.dist-info → atomicshop-2.16.43.dist-info}/WHEEL +0 -0
- {atomicshop-2.16.42.dist-info → atomicshop-2.16.43.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -53,6 +53,30 @@ def convert_sequence_of_bytes_to_sequence_of_strings(byte_sequence: bytes) -> li
|
|
|
53
53
|
return result
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def convert_sequence_of_bytes_to_exact_string(
|
|
57
|
+
byte_sequence: bytes,
|
|
58
|
+
add_space_between_bytes: bool = False,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Convert sequence of bytes to exact string.
|
|
62
|
+
Example: b'\xc0\x00' -> 'c000'
|
|
63
|
+
|
|
64
|
+
:param byte_sequence: bytes, sequence of bytes.
|
|
65
|
+
:param add_space_between_bytes: bool, add space between bytes.
|
|
66
|
+
Example: b'\xc0\x00' -> 'c0 00'
|
|
67
|
+
:return: string.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Convert to hex string and format
|
|
71
|
+
byte_list: list = []
|
|
72
|
+
for byte in byte_sequence:
|
|
73
|
+
byte_list.append(f'{byte:02x}')
|
|
74
|
+
|
|
75
|
+
result = ''.join(byte_list)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
56
80
|
def find_position(target: bytes, file_path: str = None, file_bytes: bytes = None, chunk_size: int = None, starting_position: int = 0) -> int:
|
|
57
81
|
"""
|
|
58
82
|
Find position of the target bytes string in the file.
|
atomicshop/dns.py
CHANGED
|
@@ -77,6 +77,17 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
|
|
|
77
77
|
return is_dynamic, dns_servers
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def get_default_dns_gateway_with_dns_resolver() -> list[str]:
|
|
81
|
+
"""
|
|
82
|
+
Get the default DNS gateway from the system using dns.resolver.
|
|
83
|
+
:return: tuple(is dynamic boolean, list of DNS server IPv4s).
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
resolver = dns.resolver.Resolver()
|
|
87
|
+
dns_servers = list(resolver.nameservers)
|
|
88
|
+
return dns_servers
|
|
89
|
+
|
|
90
|
+
|
|
80
91
|
def set_connection_dns_gateway_static(
|
|
81
92
|
dns_servers: list[str],
|
|
82
93
|
connection_name: str = None,
|
atomicshop/http_parse.py
CHANGED
|
@@ -2,7 +2,6 @@ from http.server import BaseHTTPRequestHandler
|
|
|
2
2
|
from http.client import HTTPResponse
|
|
3
3
|
import http
|
|
4
4
|
from io import BytesIO
|
|
5
|
-
import socket
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
@@ -50,8 +49,8 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
|
50
49
|
"""
|
|
51
50
|
|
|
52
51
|
# noinspection PyMissingConstructor
|
|
53
|
-
def __init__(self,
|
|
54
|
-
self.
|
|
52
|
+
def __init__(self, request_bytes: bytes):
|
|
53
|
+
self.request_bytes: bytes = request_bytes
|
|
55
54
|
|
|
56
55
|
# noinspection PyTypeChecker
|
|
57
56
|
self.rfile = None
|
|
@@ -107,7 +106,7 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
|
107
106
|
error: str = str()
|
|
108
107
|
info: str = str()
|
|
109
108
|
|
|
110
|
-
self.rfile = BytesIO(self.
|
|
109
|
+
self.rfile = BytesIO(self.request_bytes)
|
|
111
110
|
self.raw_requestline = self.rfile.readline()
|
|
112
111
|
self.error_code = self.error_message = None
|
|
113
112
|
self.parse_request()
|
|
@@ -152,6 +151,7 @@ class FakeSocket:
|
|
|
152
151
|
def __init__(self, response_bytes):
|
|
153
152
|
self._file = BytesIO(response_bytes)
|
|
154
153
|
|
|
154
|
+
# noinspection PyUnusedLocal
|
|
155
155
|
def makefile(self, mode='rb', buffering=-1) -> BytesIO:
|
|
156
156
|
"""
|
|
157
157
|
Mimics the socket's makefile method, returning the BytesIO object.
|
|
@@ -100,15 +100,12 @@ def thread_worker_main(
|
|
|
100
100
|
client_message.protocol = 'HTTP'
|
|
101
101
|
client_message.request_raw_decoded = request_decoded
|
|
102
102
|
print_api(request_parsing_info, logger=network_logger, logger_method='info')
|
|
103
|
-
network_logger.info(f"Method: {request_decoded.command}")
|
|
104
|
-
network_logger.info(f"Path: {request_decoded.path}")
|
|
105
|
-
# If there was error - the request is really HTTP, but there's a problem with its structure.
|
|
103
|
+
network_logger.info(f"Method: {request_decoded.command} | Path: {request_decoded.path}")
|
|
106
104
|
else:
|
|
107
|
-
# client_message.error = request_parsing_error
|
|
108
|
-
print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
|
|
109
105
|
# It doesn't matter if we have HTTP Parsing error, since the request may not be really HTTP, so it is OK
|
|
110
106
|
# not to log it into statistics.
|
|
111
107
|
# statistics_error_list.append(error_message)
|
|
108
|
+
print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
|
|
112
109
|
|
|
113
110
|
def finish_thread():
|
|
114
111
|
# At this stage there could be several times that the same socket was used to the service server - we need to
|
|
@@ -204,8 +201,6 @@ def thread_worker_main(
|
|
|
204
201
|
client_message.request_raw_bytes = client_received_raw_data
|
|
205
202
|
|
|
206
203
|
parse_http()
|
|
207
|
-
if client_message.protocol != 'HTTP':
|
|
208
|
-
pass
|
|
209
204
|
|
|
210
205
|
# Custom parser, should parse HTTP body or the whole message if not HTTP.
|
|
211
206
|
parser_instance = parser(client_message)
|
atomicshop/mitm/mitm_main.py
CHANGED
|
@@ -19,6 +19,7 @@ from . import config_static, recs_files
|
|
|
19
19
|
|
|
20
20
|
NETWORK_INTERFACE_IS_DYNAMIC: bool = bool()
|
|
21
21
|
NETWORK_INTERFACE_IPV4_ADDRESS_LIST: list[str] = list()
|
|
22
|
+
IS_SET_DNS_GATEWAY: bool = False
|
|
22
23
|
# noinspection PyTypeChecker
|
|
23
24
|
RECS_PROCESS_INSTANCE: multiprocessing.Process = None
|
|
24
25
|
|
|
@@ -36,7 +37,7 @@ except win_console.NotWindowsConsoleError:
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def exit_cleanup():
|
|
39
|
-
if permissions.is_admin():
|
|
40
|
+
if permissions.is_admin() and IS_SET_DNS_GATEWAY:
|
|
40
41
|
is_dns_dynamic, current_dns_gateway = dns.get_default_dns_gateway()
|
|
41
42
|
status_string = 'Dynamic' if is_dns_dynamic else 'Static'
|
|
42
43
|
print_api.print_api(f'Current DNS Gateway: {status_string}, {current_dns_gateway}')
|
|
@@ -347,10 +348,14 @@ def mitm_server(config_file_path: str):
|
|
|
347
348
|
dns_gateway_server_list = [base.DEFAULT_IPV4]
|
|
348
349
|
set_dns_gateway = True
|
|
349
350
|
|
|
350
|
-
# Get current network interface state.
|
|
351
|
-
global NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST
|
|
352
|
-
NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST = dns.get_default_dns_gateway()
|
|
353
351
|
if set_dns_gateway:
|
|
352
|
+
global IS_SET_DNS_GATEWAY
|
|
353
|
+
IS_SET_DNS_GATEWAY = True
|
|
354
|
+
|
|
355
|
+
# Get current network interface state.
|
|
356
|
+
global NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST
|
|
357
|
+
NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST = dns.get_default_dns_gateway()
|
|
358
|
+
|
|
354
359
|
# Set the DNS gateway to the specified one only if the DNS gateway is dynamic, or it is static but different
|
|
355
360
|
# from the one specified in the configuration file.
|
|
356
361
|
if (NETWORK_INTERFACE_IS_DYNAMIC or (not NETWORK_INTERFACE_IS_DYNAMIC and
|
atomicshop/web.py
CHANGED
|
@@ -90,10 +90,13 @@ def get_page_bytes(
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def get_page_content(
|
|
93
|
-
url: str,
|
|
93
|
+
url: str,
|
|
94
|
+
get_method: str = 'urllib',
|
|
95
|
+
path: str = None,
|
|
94
96
|
playwright_pdf_format: str = 'A4',
|
|
95
97
|
playwright_html_txt_convert_to_bytes: bool = True,
|
|
96
|
-
print_kwargs: dict = None
|
|
98
|
+
print_kwargs: dict = None
|
|
99
|
+
) -> any:
|
|
97
100
|
"""
|
|
98
101
|
Function returns the page content from the given URL.
|
|
99
102
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from websockets.server import ServerProtocol
|
|
5
|
+
from websockets.client import ClientProtocol
|
|
6
|
+
from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory, ClientPerMessageDeflateFactory
|
|
7
|
+
from websockets.http11 import Request, Response
|
|
8
|
+
from websockets.frames import Frame, Opcode, Close
|
|
9
|
+
from websockets.uri import parse_uri
|
|
10
|
+
from websockets.exceptions import InvalidHeaderValue
|
|
11
|
+
from websockets.protocol import OPEN
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebsocketRequestParse:
|
|
15
|
+
"""
|
|
16
|
+
Parse the websocket request and return the data
|
|
17
|
+
"""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
enable_logging: bool = False,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the websocket parser.
|
|
24
|
+
|
|
25
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
26
|
+
"""
|
|
27
|
+
# noinspection PyTypeChecker
|
|
28
|
+
self.request_bytes: bytes = None
|
|
29
|
+
|
|
30
|
+
# Set up extensions
|
|
31
|
+
permessage_deflate_factory = ServerPerMessageDeflateFactory()
|
|
32
|
+
|
|
33
|
+
# Create the protocol instance
|
|
34
|
+
self.protocol = ServerProtocol(
|
|
35
|
+
extensions=[permessage_deflate_factory],
|
|
36
|
+
)
|
|
37
|
+
# At this state the protocol.state is State.CONNECTING
|
|
38
|
+
|
|
39
|
+
if enable_logging:
|
|
40
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
41
|
+
self.protocol.logger.setLevel(logging.DEBUG)
|
|
42
|
+
|
|
43
|
+
def parse(
|
|
44
|
+
self,
|
|
45
|
+
request_bytes: bytes
|
|
46
|
+
) -> Union[str, bytes, Request]:
|
|
47
|
+
"""
|
|
48
|
+
Parse the websocket request and return the data
|
|
49
|
+
|
|
50
|
+
:param request_bytes: bytes: The raw bytes of the websocket request.
|
|
51
|
+
:return: Request: The parsed request object.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
self.protocol.receive_data(request_bytes)
|
|
55
|
+
events = self.protocol.events_received()
|
|
56
|
+
for event in events:
|
|
57
|
+
if isinstance(event, Request):
|
|
58
|
+
# Accept the handshake.
|
|
59
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
60
|
+
# Only after this state we can parse frames.
|
|
61
|
+
response = self.protocol.accept(event)
|
|
62
|
+
self.protocol.send_response(response)
|
|
63
|
+
return event
|
|
64
|
+
elif isinstance(event, Frame):
|
|
65
|
+
frame = event
|
|
66
|
+
if frame.opcode == Opcode.TEXT:
|
|
67
|
+
message = frame.data.decode('utf-8')
|
|
68
|
+
return message
|
|
69
|
+
elif frame.opcode == Opcode.BINARY:
|
|
70
|
+
return frame.data
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
# Handle control frames, these are here for the future references.
|
|
74
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
75
|
+
close_info = Close.parse(frame.data)
|
|
76
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
77
|
+
# Send a close frame in response if not already sent
|
|
78
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
79
|
+
self.protocol.send_close()
|
|
80
|
+
elif frame.opcode == Opcode.PING:
|
|
81
|
+
# Respond to ping with pong
|
|
82
|
+
self.protocol.send_pong(frame.data)
|
|
83
|
+
elif frame.opcode == Opcode.PONG:
|
|
84
|
+
print("Received pong")
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class WebsocketResponseParse:
|
|
89
|
+
"""
|
|
90
|
+
Parse the websocket response and return the data
|
|
91
|
+
"""
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
enable_logging: bool = False,
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Initialize the websocket parser.
|
|
98
|
+
|
|
99
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
100
|
+
"""
|
|
101
|
+
# noinspection PyTypeChecker
|
|
102
|
+
self.response_bytes: bytes = None
|
|
103
|
+
|
|
104
|
+
# Set up extensions
|
|
105
|
+
permessage_deflate_factory = ClientPerMessageDeflateFactory()
|
|
106
|
+
|
|
107
|
+
# Parse the WebSocket URI.
|
|
108
|
+
# Since we're parsing the response, we don't need the URI, but the protocol object requires it.
|
|
109
|
+
# So we will just use a dummy URI.
|
|
110
|
+
wsuri = parse_uri('ws://example.com/websocket')
|
|
111
|
+
|
|
112
|
+
# Create the protocol instance
|
|
113
|
+
self.protocol = ClientProtocol(
|
|
114
|
+
wsuri,
|
|
115
|
+
extensions=[permessage_deflate_factory],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if enable_logging:
|
|
119
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
120
|
+
# self.protocol.logger.setLevel(logging.DEBUG)
|
|
121
|
+
self.protocol.debug = True
|
|
122
|
+
|
|
123
|
+
# Perform the handshake and emulate the connection and request sending.
|
|
124
|
+
request = self.protocol.connect()
|
|
125
|
+
self.protocol.send_request(request)
|
|
126
|
+
_ = self.protocol.data_to_send()
|
|
127
|
+
# At this state the protocol.state is State.CONNECTING
|
|
128
|
+
|
|
129
|
+
def parse(
|
|
130
|
+
self,
|
|
131
|
+
response_bytes: bytes
|
|
132
|
+
) -> Union[str, bytes, Response]:
|
|
133
|
+
"""
|
|
134
|
+
Parse the websocket response and return the data
|
|
135
|
+
|
|
136
|
+
:param response_bytes: bytes: The raw bytes of the websocket response.
|
|
137
|
+
:return: The parsed response.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
self.protocol.receive_data(response_bytes)
|
|
141
|
+
events = self.protocol.events_received()
|
|
142
|
+
for event in events:
|
|
143
|
+
if isinstance(event, Response):
|
|
144
|
+
# Accept the handshake.
|
|
145
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
146
|
+
# Only after this state we can parse frames.
|
|
147
|
+
try:
|
|
148
|
+
self.protocol.process_response(event)
|
|
149
|
+
except InvalidHeaderValue as e:
|
|
150
|
+
headers = event.headers
|
|
151
|
+
self.protocol.extensions = self.protocol.process_extensions(headers)
|
|
152
|
+
self.protocol.subprotocol = self.protocol.process_subprotocol(headers)
|
|
153
|
+
self.protocol.state = OPEN
|
|
154
|
+
return event
|
|
155
|
+
elif isinstance(event, Frame):
|
|
156
|
+
frame = event
|
|
157
|
+
if frame.opcode == Opcode.TEXT:
|
|
158
|
+
message = frame.data.decode('utf-8')
|
|
159
|
+
return message
|
|
160
|
+
elif frame.opcode == Opcode.BINARY:
|
|
161
|
+
return frame.data
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
# Handle control frames, these are here for the future references.
|
|
165
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
166
|
+
close_info = Close.parse(frame.data)
|
|
167
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
168
|
+
# Send a close frame in response if not already sent
|
|
169
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
170
|
+
self.protocol.send_close()
|
|
171
|
+
elif frame.opcode == Opcode.PING:
|
|
172
|
+
# Respond to ping with pong
|
|
173
|
+
self.protocol.send_pong(frame.data)
|
|
174
|
+
elif frame.opcode == Opcode.PONG:
|
|
175
|
+
print("Received pong")
|
|
176
|
+
"""
|
|
@@ -7,7 +7,12 @@ from .. import config_install
|
|
|
7
7
|
|
|
8
8
|
def install_after_restart(
|
|
9
9
|
installation_directory: str,
|
|
10
|
-
install_type: Union[
|
|
10
|
+
install_type: Union[
|
|
11
|
+
None,
|
|
12
|
+
Literal['backend', 'frontend', 'db']] = None,
|
|
13
|
+
log_level: Union[
|
|
14
|
+
None,
|
|
15
|
+
Literal['DEBUG', 'INFO', 'WARNING', 'ERROR']] = None
|
|
11
16
|
):
|
|
12
17
|
"""
|
|
13
18
|
This function will continue the installation the FACT_core after the restart of the computer.
|
|
@@ -20,6 +25,9 @@ def install_after_restart(
|
|
|
20
25
|
--backend: Distributed setup, Install the FACT_core backend.
|
|
21
26
|
--frontend: Distributed setup, Install the FACT_core frontend.
|
|
22
27
|
--db: Distributed setup, Install the FACT_core database.
|
|
28
|
+
:param log_level: string, the log level to use for the installation.
|
|
29
|
+
The same as using the '--log-level' parameter in the 'install.py' script.
|
|
30
|
+
The default is 'INFO' in the 'install.py' script.
|
|
23
31
|
:return:
|
|
24
32
|
"""
|
|
25
33
|
|
|
@@ -28,8 +36,11 @@ def install_after_restart(
|
|
|
28
36
|
if install_type:
|
|
29
37
|
install_command = install_command + ' --' + install_type
|
|
30
38
|
|
|
39
|
+
if log_level:
|
|
40
|
+
install_command = install_command + ' --log-level ' + log_level
|
|
41
|
+
|
|
31
42
|
# Install the FACT_core repo.
|
|
32
43
|
process.execute_with_live_output(cmd=install_command, verbose=True)
|
|
33
44
|
# Remove the FACT_core installation log.
|
|
34
|
-
working_directory_path: str = filesystem.get_working_directory()
|
|
45
|
+
# working_directory_path: str = filesystem.get_working_directory()
|
|
35
46
|
# filesystem.remove_file(str(Path(working_directory_path, config_install.INSTALL_LOG_FILE_NAME)))
|
|
@@ -174,6 +174,7 @@ class MongoDBWrapper:
|
|
|
174
174
|
self,
|
|
175
175
|
collection_name: str,
|
|
176
176
|
filter_query: dict = None,
|
|
177
|
+
projection: dict = None,
|
|
177
178
|
page: int = None,
|
|
178
179
|
items: int = None,
|
|
179
180
|
sort: dict[str, Literal[
|
|
@@ -192,6 +193,7 @@ class MongoDBWrapper:
|
|
|
192
193
|
filter_query = None
|
|
193
194
|
|
|
194
195
|
CHECK MORE EXAMPLES IN THE DOCSTRING OF THE FUNCTION 'find' BELOW which is not in this class.
|
|
196
|
+
:param projection: dict, the only fields to return or exclude.
|
|
195
197
|
:param page: int, the page number (Optional).
|
|
196
198
|
The results are filtered after results are fetched from db.
|
|
197
199
|
:param items: int, the number of results per page (Optional).
|
|
@@ -229,7 +231,8 @@ class MongoDBWrapper:
|
|
|
229
231
|
|
|
230
232
|
entries: list[dict] = find(
|
|
231
233
|
database=self.db, collection_name=collection_name,
|
|
232
|
-
filter_query=filter_query,
|
|
234
|
+
filter_query=filter_query, projection=projection,
|
|
235
|
+
page=page, items=items, sort=sort,
|
|
233
236
|
convert_object_id_to_str=convert_object_id_to_str, key_convert_to_dict=keys_convert_to_dict,
|
|
234
237
|
mongo_client=self.client, close_client=False)
|
|
235
238
|
|
|
@@ -827,7 +830,7 @@ def find(
|
|
|
827
830
|
|
|
828
831
|
entries: list[dict] = list(collection_items)
|
|
829
832
|
|
|
830
|
-
if convert_object_id_to_str:
|
|
833
|
+
if entries and convert_object_id_to_str and '_id' in entries[0]:
|
|
831
834
|
for entry_index, entry in enumerate(entries):
|
|
832
835
|
entries[entry_index]['_id'] = str(entry['_id'])
|
|
833
836
|
|
|
@@ -44,8 +44,12 @@ def get_page_text_content(page) -> str:
|
|
|
44
44
|
:return: string, text content of the page.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
# Full javascript.
|
|
48
|
+
# text_content: str = page.evaluate('''() => {
|
|
49
|
+
# return document.body.innerText;
|
|
50
|
+
# }''')
|
|
51
|
+
|
|
52
|
+
# Short javascript.
|
|
53
|
+
text_content: str = page.evaluate("document.body.innerText")
|
|
50
54
|
|
|
51
55
|
return text_content
|
|
@@ -43,8 +43,12 @@ def get_text_from_html_tag(url: str, tag_name: str, attribute: str, value: str)
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def get_page_content(
|
|
46
|
-
url: str,
|
|
47
|
-
|
|
46
|
+
url: str,
|
|
47
|
+
page_format: str = 'html',
|
|
48
|
+
path: str = None,
|
|
49
|
+
pdf_format: str = 'A4',
|
|
50
|
+
html_txt_convert_to_bytes: bool = True,
|
|
51
|
+
print_kwargs: dict = None
|
|
48
52
|
) -> any:
|
|
49
53
|
"""
|
|
50
54
|
The function receives playwright engine and page object, navigates to URL, gets page content in specified format,
|
|
@@ -57,10 +61,10 @@ def get_page_content(
|
|
|
57
61
|
'png' - returns png binary.
|
|
58
62
|
'jpeg' - returns jpeg binary.
|
|
59
63
|
:param path: string of path to save the file to. Default is None.
|
|
60
|
-
:param print_kwargs: dict, that contains all the arguments for 'print_api' function.
|
|
61
64
|
:param pdf_format: string of pdf format, applicable only if 'page_format=pdf'. Default is 'A4'.
|
|
62
65
|
:param html_txt_convert_to_bytes: boolean, applicable only if 'page_format=html' or 'page_format=txt'.
|
|
63
66
|
Default is True.
|
|
67
|
+
:param print_kwargs: dict, that contains all the arguments for 'print_api' function.
|
|
64
68
|
|
|
65
69
|
:return: any page content in specified format.
|
|
66
70
|
"""
|
|
@@ -95,8 +99,13 @@ def get_page_content(
|
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
def get_page_content_in_thread(
|
|
98
|
-
url: str,
|
|
99
|
-
|
|
102
|
+
url: str,
|
|
103
|
+
page_format: str = 'html',
|
|
104
|
+
path: str = None,
|
|
105
|
+
pdf_format: str = 'A4',
|
|
106
|
+
html_txt_convert_to_bytes: bool = True,
|
|
107
|
+
print_kwargs: dict = None
|
|
108
|
+
):
|
|
100
109
|
"""
|
|
101
110
|
The function uses 'threads.thread_wrap_var' function in order to wrap the function 'get_page_content' and
|
|
102
111
|
execute it in a thread with arguments and return the result.
|
|
@@ -144,8 +144,23 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
|
|
|
144
144
|
:return: tuple(is dynamic boolean, list of DNS server IPv4s).
|
|
145
145
|
"""
|
|
146
146
|
|
|
147
|
+
def get_current_interface_status(current_interface_settings: dict) -> tuple[bool, list[str]]:
|
|
148
|
+
if current_interface_settings['NameServer']:
|
|
149
|
+
result = (False, current_interface_settings['NameServer'].split(','))
|
|
150
|
+
else:
|
|
151
|
+
result = (True, current_interface_settings['DhcpNameServer'].split(','))
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
|
|
147
156
|
# Get current default IPv4 address of the interface that is being used for internet.
|
|
148
157
|
default_ipv4_address: str = socket.gethostbyname(socket.gethostname())
|
|
158
|
+
# If the default IPv4 address is localhost, then it could mean that the system is not connected to the internet.
|
|
159
|
+
# Or there is no network adapter at all.
|
|
160
|
+
default_dns_gateway_list: list[str] = []
|
|
161
|
+
if default_ipv4_address == '127.0.0.1':
|
|
162
|
+
from ... import dns
|
|
163
|
+
default_dns_gateway_list = dns.get_default_dns_gateway_with_dns_resolver()
|
|
149
164
|
|
|
150
165
|
# Get all network interface settings from the registry.
|
|
151
166
|
all_interfaces_configurations = get_network_interfaces_settings()
|
|
@@ -153,22 +168,36 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
|
|
|
153
168
|
# Find the interface that has this IPv4 assigned.
|
|
154
169
|
function_result: tuple = tuple()
|
|
155
170
|
for interface_guid, interface_settings in all_interfaces_configurations.items():
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
if not interface_settings:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
if ' ' in interface_settings['NameServer']:
|
|
175
|
+
interface_settings['NameServer'] = interface_settings['NameServer'].replace(' ', ',')
|
|
176
|
+
if ' ' in interface_settings['DhcpNameServer']:
|
|
177
|
+
interface_settings['DhcpNameServer'] = interface_settings['DhcpNameServer'].replace(' ', ',')
|
|
178
|
+
|
|
179
|
+
if not default_dns_gateway_list:
|
|
180
|
+
current_interface_static_ipv4_address: list = interface_settings.get('IPAddress', None)
|
|
181
|
+
current_interface_dynamic_ipv4_address: str = interface_settings.get('DhcpIPAddress', None)
|
|
182
|
+
|
|
183
|
+
static_and_ip_match: bool = (
|
|
184
|
+
current_interface_static_ipv4_address and
|
|
185
|
+
current_interface_static_ipv4_address[0] == default_ipv4_address)
|
|
186
|
+
dynamic_and_ip_match: bool = (
|
|
187
|
+
current_interface_dynamic_ipv4_address and
|
|
188
|
+
current_interface_dynamic_ipv4_address == default_ipv4_address)
|
|
189
|
+
if static_and_ip_match or dynamic_and_ip_match:
|
|
190
|
+
function_result = get_current_interface_status(interface_settings)
|
|
191
|
+
|
|
192
|
+
break
|
|
193
|
+
else:
|
|
194
|
+
current_interface_name_server_list: list[str] = interface_settings['NameServer'].split(',')
|
|
195
|
+
current_interface_dhcp_name_server_list: list[str] = interface_settings['DhcpNameServer'].split(',')
|
|
196
|
+
if (current_interface_name_server_list == default_dns_gateway_list or
|
|
197
|
+
current_interface_dhcp_name_server_list == default_dns_gateway_list):
|
|
198
|
+
function_result = get_current_interface_status(interface_settings)
|
|
199
|
+
|
|
200
|
+
break
|
|
172
201
|
|
|
173
202
|
# noinspection PyTypeChecker
|
|
174
203
|
return function_result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: atomicshop
|
|
3
|
-
Version: 2.16.
|
|
3
|
+
Version: 2.16.43
|
|
4
4
|
Summary: Atomic functions and classes to make developer life easier
|
|
5
5
|
Author: Denis Kras
|
|
6
6
|
License: MIT License
|
|
@@ -60,6 +60,7 @@ Requires-Dist: SoundCard
|
|
|
60
60
|
Requires-Dist: soundfile
|
|
61
61
|
Requires-Dist: SpeechRecognition
|
|
62
62
|
Requires-Dist: tldextract
|
|
63
|
+
Requires-Dist: websockets
|
|
63
64
|
Requires-Dist: pywin32 ; platform_system == "Windows"
|
|
64
65
|
|
|
65
66
|
<h1 align="center">Atomic Workshop</h1>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=eN5mJMelXPzsx3RapgUonBJKKZKyH4jUURpdEWoMogA,124
|
|
2
2
|
atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
|
|
3
3
|
atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
|
|
4
4
|
atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
|
|
@@ -10,7 +10,7 @@ atomicshop/console_output.py,sha256=AOSJjrRryE97PAGtgDL03IBtWSi02aNol8noDnW3k6M,
|
|
|
10
10
|
atomicshop/console_user_response.py,sha256=31HIy9QGXa7f-GVR8MzJauQ79E_ZqAeagF3Ks4GGdDU,3234
|
|
11
11
|
atomicshop/datetimes.py,sha256=IQZ66lmta-ZqxYbyHzm_9eugbJFSilXK1e0kfMgoXGg,18371
|
|
12
12
|
atomicshop/diff_check.py,sha256=Q9RCqRa-jEgo7Fujx08_JTuZ6qcgttMI6aNYB6zN9Ik,27173
|
|
13
|
-
atomicshop/dns.py,sha256=
|
|
13
|
+
atomicshop/dns.py,sha256=5Gimq_WY2arqg7BeGmR7P--fGfnH0Dsh8lrOt_H0jRY,6817
|
|
14
14
|
atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
|
|
15
15
|
atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
|
|
16
16
|
atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
|
|
@@ -19,7 +19,7 @@ atomicshop/functions.py,sha256=pK8hoCE9z61PtWCxQJsda7YAphrLH1wxU5x-1QJP-sY,499
|
|
|
19
19
|
atomicshop/get_process_list.py,sha256=8cxb7gKe9sl4R6H2yMi8J6oe-RkonTvCdKjRFqi-Fs4,6075
|
|
20
20
|
atomicshop/get_process_name_cmd_dll.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
|
|
21
21
|
atomicshop/hashing.py,sha256=Le8qGFyt3_wX-zGTeQShz7L2HL_b6nVv9PnawjglyHo,3474
|
|
22
|
-
atomicshop/http_parse.py,sha256=
|
|
22
|
+
atomicshop/http_parse.py,sha256=WoUoz5HeiIjPzFQYn7d2aeuAKa0kVWvrswyKMZT6VUg,10039
|
|
23
23
|
atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
|
|
24
24
|
atomicshop/ip_addresses.py,sha256=Hvi4TumEFoTEpKWaq5WNF-YzcRzt24IxmNgv-Mgax1s,1190
|
|
25
25
|
atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
|
|
@@ -44,7 +44,8 @@ atomicshop/timer.py,sha256=7Zw1KRV0acHCRATMnanyX2MLBb63Hc-6us3rCZ9dNlY,2345
|
|
|
44
44
|
atomicshop/urls.py,sha256=aJ0NGS9qqaKeqjkkWBs80jaBBg6MYBiPuLIyPGxscVc,1557
|
|
45
45
|
atomicshop/uuids.py,sha256=JSQdm3ZTJiwPQ1gYe6kU0TKS_7suwVrHc8JZDGYlydM,2214
|
|
46
46
|
atomicshop/virtualization.py,sha256=LPP4vjE0Vr10R6DA4lqhfX_WaNdDGRAZUW0Am6VeGco,494
|
|
47
|
-
atomicshop/web.py,sha256=
|
|
47
|
+
atomicshop/web.py,sha256=GLdTXgMxg1_0UQaXC4bOvARVyuFg7SPIeJdsCHV8rNE,11662
|
|
48
|
+
atomicshop/websocket_parse.py,sha256=Ykva7XI_t-f4H_z0mKxLuyQ04LxZBblHZN_PbYB9idY,6968
|
|
48
49
|
atomicshop/a_installs/ubuntu/docker_rootless.py,sha256=9IPNtGZYjfy1_n6ZRt7gWz9KZgR6XCgevjqq02xk-o0,281
|
|
49
50
|
atomicshop/a_installs/ubuntu/docker_sudo.py,sha256=JzayxeyKDtiuT4Icp2L2LyFRbx4wvpyN_bHLfZ-yX5E,281
|
|
50
51
|
atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py,sha256=yRB-l1zBxdiN6av-FwNkhcBlaeu4zrDPjQ0uPGgpK2I,244
|
|
@@ -84,7 +85,7 @@ atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
84
85
|
atomicshop/basics/ansi_escape_codes.py,sha256=WtIkm-BjSZS5J5irDUdAMBNvdX-qXFZcTX98jcBMpJE,3140
|
|
85
86
|
atomicshop/basics/argparse_template.py,sha256=horwgSf3MX1ZgRnYxtmmQuz9OU_vKrKggF65gmjlmfg,5836
|
|
86
87
|
atomicshop/basics/booleans.py,sha256=V36NaMf8AffhCom_ovQeOZlYcdtGyIcQwWKki6h7O0M,1745
|
|
87
|
-
atomicshop/basics/bytes_arrays.py,sha256=
|
|
88
|
+
atomicshop/basics/bytes_arrays.py,sha256=tU7KF0UAFSErGNcan4Uz1GJxeIYVRuUu9sHVZHgyNnw,6542
|
|
88
89
|
atomicshop/basics/classes.py,sha256=T0Bm13hKvkXG3med68ptL7XuoWiCi3TE-K5TMINDlrY,10655
|
|
89
90
|
atomicshop/basics/dicts.py,sha256=DeYHIh940pMMBrFhpXt4dsigFVYzTrlqWymNo4Pq_Js,14049
|
|
90
91
|
atomicshop/basics/dicts_nested.py,sha256=StYxYnYPa0SEJr1lmEwAv5zfERWWqoULeyG8e0zRAwE,4107
|
|
@@ -125,11 +126,11 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
|
|
|
125
126
|
atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
127
|
atomicshop/mitm/config_static.py,sha256=ROAtbibSWSsF3BraUbhu-QO3MPIFqYY5KUKgsQbiSkk,7813
|
|
127
128
|
atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
|
|
128
|
-
atomicshop/mitm/connection_thread_worker.py,sha256=
|
|
129
|
+
atomicshop/mitm/connection_thread_worker.py,sha256=DxHCgDtCbSQCIX80WK9hJhp3v37pMjmm-mr3y6tyJMY,17581
|
|
129
130
|
atomicshop/mitm/import_config.py,sha256=0Ij14aISTllTOiWYJpIUMOWobQqGofD6uafui5uWllE,9272
|
|
130
131
|
atomicshop/mitm/initialize_engines.py,sha256=VyJE8QnzlgD3QbX5inz5o6rC3zQ3is9CeTL7-B10g1w,8292
|
|
131
132
|
atomicshop/mitm/message.py,sha256=URR5JKSuAT8XmGIkyprEjlPW2GW4ef_gfUz_GgcFseE,2184
|
|
132
|
-
atomicshop/mitm/mitm_main.py,sha256=
|
|
133
|
+
atomicshop/mitm/mitm_main.py,sha256=5c-9oxBiLueTbZr4Dyd4EEOorEUix5vSWxX9p5O1fBs,23375
|
|
133
134
|
atomicshop/mitm/recs_files.py,sha256=mMyO1kPB-VkS_pbWCDhZHKdbWzlPbYSout61QuzHOao,3077
|
|
134
135
|
atomicshop/mitm/shared_functions.py,sha256=l6oEyv4ug5D_03V3QLADYoocbcL2Ml_dYVW2WKM21l4,1818
|
|
135
136
|
atomicshop/mitm/statistic_analyzer.py,sha256=5_sAYGX2Xunzo_pS2W5WijNCwr_BlGJbbOO462y_wN4,27533
|
|
@@ -228,7 +229,7 @@ atomicshop/wrappers/factw/fact_extractor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
|
|
|
228
229
|
atomicshop/wrappers/factw/fact_extractor/docker_image.py,sha256=2FyYjnw8gxFNwISQ83OwH-iGivkFi6EAluyCZ0loHEQ,2501
|
|
229
230
|
atomicshop/wrappers/factw/fact_extractor/get_extractor.py,sha256=2mfOAftHIlCcGt1s7MWdq7DsDCuI6wX3MtvcEZ4SK-0,756
|
|
230
231
|
atomicshop/wrappers/factw/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
231
|
-
atomicshop/wrappers/factw/install/install_after_restart.py,sha256
|
|
232
|
+
atomicshop/wrappers/factw/install/install_after_restart.py,sha256=zsghES-pyCNiGU2Yix_fFBiDEOWfQ0dtbmyVeZozgL8,2012
|
|
232
233
|
atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py,sha256=GFsO9MTH0czKoxkiPJtjalilUwsmFLBCcx9Znv37S4M,5945
|
|
233
234
|
atomicshop/wrappers/factw/postgresql/__init__.py,sha256=xMBn2d3Exo23IPP2F_9-SXmOlhFbwWDgS9KwozSTjA0,162
|
|
234
235
|
atomicshop/wrappers/factw/postgresql/analysis.py,sha256=2Rxzy2jyq3zEKIo53z8VkjuslKE_i5mq2ZpmJAvyd6U,716
|
|
@@ -257,7 +258,7 @@ atomicshop/wrappers/mongodbw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
257
258
|
atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=pmI9AwWJ2cv5h8GionSpSJkllg6kfp0M381pk6y4Y5U,4015
|
|
258
259
|
atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=3ZPqrXxj3lC-PnAKGXclylLuOqsbyXYeUpb5iGjdeUU,6626
|
|
259
260
|
atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
|
|
260
|
-
atomicshop/wrappers/mongodbw/mongodbw.py,sha256=
|
|
261
|
+
atomicshop/wrappers/mongodbw/mongodbw.py,sha256=IkEw86QFyVRU-5p5s6_6yupvSxmaQxr59GKNgSEkAm4,52617
|
|
261
262
|
atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
262
263
|
atomicshop/wrappers/nodejsw/install_nodejs.py,sha256=TKGa3jSlSqZTL2NA0nMkWDFtlkz7rxGGn44ywCg7MN8,5228
|
|
263
264
|
atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -266,11 +267,11 @@ atomicshop/wrappers/playwrightw/base.py,sha256=WeRpx8otdXuKSr-BjY-uCJTze21kbPpfi
|
|
|
266
267
|
atomicshop/wrappers/playwrightw/combos.py,sha256=215y7wugyyBrFK9_0WutnMXsF1jqJ2PLm9eHEa2PMz0,7145
|
|
267
268
|
atomicshop/wrappers/playwrightw/engine.py,sha256=fapOeaqSMNoxJucTvhzp30cKfKNSVe45drhZJcJ-fOk,15191
|
|
268
269
|
atomicshop/wrappers/playwrightw/infra.py,sha256=ncp62l6CgAgLz4lqBtKEWZO_6ES8cqnbzJBov-tlpBo,97
|
|
269
|
-
atomicshop/wrappers/playwrightw/javascript.py,sha256=
|
|
270
|
+
atomicshop/wrappers/playwrightw/javascript.py,sha256=_bW7CAtm0Y8IHYrAalg5HpPFnk6juiqicmkvZ9dITaA,1235
|
|
270
271
|
atomicshop/wrappers/playwrightw/keyboard.py,sha256=zN3YddGO-qUkn6C0CRVFejP4cTuaUwXLDNFhFREjERY,422
|
|
271
272
|
atomicshop/wrappers/playwrightw/locators.py,sha256=6wsLywZxDuii7mwv-zQsRbqQC8r7j96Bma5b5_7ZoVo,2411
|
|
272
273
|
atomicshop/wrappers/playwrightw/mouse.py,sha256=-2FZbQtjgH7tdXWld6ZPGqlKFUdf5in0ujN0hewxa50,656
|
|
273
|
-
atomicshop/wrappers/playwrightw/scenarios.py,sha256=
|
|
274
|
+
atomicshop/wrappers/playwrightw/scenarios.py,sha256=OzI3SV0QgazRwMZ0hMEopDHUYG-aygBSxZ50w78lIP8,5310
|
|
274
275
|
atomicshop/wrappers/playwrightw/waits.py,sha256=PBFdz_PoM7Fo7O8hLqMrxNPzBEYgPoXwZceFFCGGeu8,7182
|
|
275
276
|
atomicshop/wrappers/psutilw/cpus.py,sha256=w6LPBMINqS-T_X8vzdYkLS2Wzuve28Ydp_GafTCngrc,236
|
|
276
277
|
atomicshop/wrappers/psutilw/disks.py,sha256=3ZSVoommKH1TWo37j_83frB-NqXF4Nf5q5mBCX8G4jE,9221
|
|
@@ -313,9 +314,9 @@ atomicshop/wrappers/socketw/socket_wrapper.py,sha256=WtylpezgIIBuz-A6PfM0hO1sm9E
|
|
|
313
314
|
atomicshop/wrappers/socketw/ssl_base.py,sha256=kmiif84kMhBr5yjQW17p935sfjR5JKG0LxIwBA4iVvU,2275
|
|
314
315
|
atomicshop/wrappers/socketw/statistics_csv.py,sha256=w1AH-zf4mBuT4euf28UKij9ihM-b1BRU9Qfby0QDdqI,2957
|
|
315
316
|
atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
316
|
-
atomicshop/wrappers/winregw/winreg_network.py,sha256=
|
|
317
|
-
atomicshop-2.16.
|
|
318
|
-
atomicshop-2.16.
|
|
319
|
-
atomicshop-2.16.
|
|
320
|
-
atomicshop-2.16.
|
|
321
|
-
atomicshop-2.16.
|
|
317
|
+
atomicshop/wrappers/winregw/winreg_network.py,sha256=ChnVG8ZecKJ-DMF8nUHRiifWJq2M4slEwKat6FBfPfE,8685
|
|
318
|
+
atomicshop-2.16.43.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
319
|
+
atomicshop-2.16.43.dist-info/METADATA,sha256=QVoeyIJQUE341VUy8UhCrEqSabPP8SLi8qUJjyi6f98,10500
|
|
320
|
+
atomicshop-2.16.43.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
321
|
+
atomicshop-2.16.43.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
322
|
+
atomicshop-2.16.43.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|