multirandom 0.0.4__tar.gz → 0.0.5__tar.gz
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.
- {multirandom-0.0.4/src/multirandom.egg-info → multirandom-0.0.5}/PKG-INFO +2 -1
- {multirandom-0.0.4 → multirandom-0.0.5}/pyproject.toml +3 -2
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/hash_rand/sha_rand.py +6 -3
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/true_rand/rand_using_online_api.py +1 -1
- multirandom-0.0.5/src/multirandom/true_rand/weather_rand.py +281 -0
- {multirandom-0.0.4 → multirandom-0.0.5/src/multirandom.egg-info}/PKG-INFO +2 -1
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom.egg-info/requires.txt +1 -0
- multirandom-0.0.4/src/multirandom/true_rand/weather_rand.py +0 -2
- {multirandom-0.0.4 → multirandom-0.0.5}/LICENSE +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/README.md +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/setup.cfg +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/__init__.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/hash_rand/__init__.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/hash_rand/original_random.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/true_rand/__init__.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/true_rand/rand_via_clicks.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/virt_rand/__init__.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/virt_rand/flash_rand_and_reverse.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/virt_rand/rand_using_virt_lcg.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom/virt_rand/xor_shift.py +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom.egg-info/SOURCES.txt +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom.egg-info/dependency_links.txt +0 -0
- {multirandom-0.0.4 → multirandom-0.0.5}/src/multirandom.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multirandom
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: 🎲 The truth external PyPI random package, have true_rand, virt_rand, hash_rand, ...
|
|
5
5
|
Author-email: BlackCatOfficialytb <quanvm0501@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -12,6 +12,7 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: aiohttp
|
|
14
14
|
Requires-Dist: pyautogui
|
|
15
|
+
Requires-Dist: geopy
|
|
15
16
|
Dynamic: license-file
|
|
16
17
|
|
|
17
18
|
# MultiRandom 🎲
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "multirandom"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.5"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="BlackCatOfficialytb", email="quanvm0501@gmail.com" },
|
|
10
10
|
]
|
|
@@ -18,7 +18,8 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
dependencies = [
|
|
20
20
|
"aiohttp",
|
|
21
|
-
|
|
21
|
+
"pyautogui",
|
|
22
|
+
"geopy"
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[project.urls]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import random
|
|
2
|
+
sha1 = None
|
|
2
3
|
_sha224 = None
|
|
3
4
|
_sha256 = None
|
|
4
5
|
_sha384 = None
|
|
@@ -50,9 +51,11 @@ def _get_sha512():
|
|
|
50
51
|
# fallback to official implementation
|
|
51
52
|
from hashlib import sha512 as _sha512
|
|
52
53
|
return _sha512
|
|
53
|
-
class _SHARandom:
|
|
54
|
+
class _SHARandom(random.Random):
|
|
54
55
|
def __init__(self, seed=None):
|
|
55
|
-
|
|
56
|
+
if seed is None:
|
|
57
|
+
seed = random.getrandbits(64)
|
|
58
|
+
super().__init__(seed)
|
|
56
59
|
self.counter = 0
|
|
57
60
|
|
|
58
61
|
def _get_hash(self):
|
|
@@ -3,7 +3,7 @@ import asyncio
|
|
|
3
3
|
|
|
4
4
|
async def fetch_random_org_integers(num: int = 1, min_val: int = 1, max_val: int = 100):
|
|
5
5
|
"""Fetches true random integers from random.org using their plain text API."""
|
|
6
|
-
url = "https://www.random.org/integers
|
|
6
|
+
url = "https://www.random.org/integers/?"
|
|
7
7
|
params = {
|
|
8
8
|
"num": num,
|
|
9
9
|
"min": min_val,
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import asyncio
|
|
3
|
+
import random
|
|
4
|
+
import hashlib
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import Optional, Tuple
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
A random source using Open-Meteo weather API data.
|
|
10
|
+
While still not fully random and can be spoofed, it's much more reliable than any others cuz it uses hash random from random() too.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
class OpenMeteoAPI:
|
|
14
|
+
"""
|
|
15
|
+
Entropy source using Open-Meteo weather API data.
|
|
16
|
+
|
|
17
|
+
Fetches weather data from random locations and times to generate entropy
|
|
18
|
+
for seeding random.Random. Only provides entropy - use RandomUsingOpenMeteoAPI
|
|
19
|
+
for the full random interface.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
BASE_URL = "https://api.open-meteo.com/v1/forecast"
|
|
23
|
+
|
|
24
|
+
WEATHER_VARIABLES = [
|
|
25
|
+
"temperature_2m",
|
|
26
|
+
"relative_humidity_2m",
|
|
27
|
+
"precipitation",
|
|
28
|
+
"surface_pressure",
|
|
29
|
+
"wind_speed_10m",
|
|
30
|
+
"wind_direction_10m",
|
|
31
|
+
"cloud_cover"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
def __init__(self, seed: Optional[int] = None):
|
|
35
|
+
self._rng = random.Random(seed)
|
|
36
|
+
self._current_location: Optional[Tuple[float, float]] = None
|
|
37
|
+
|
|
38
|
+
def set_current_location(self, latitude: float, longitude: float):
|
|
39
|
+
"""Set the current location for weather-based entropy."""
|
|
40
|
+
self._current_location = (latitude, longitude)
|
|
41
|
+
|
|
42
|
+
def _generate_random_location(self) -> Tuple[float, float]:
|
|
43
|
+
"""Generate a random latitude and longitude."""
|
|
44
|
+
latitude = self._rng.uniform(-60, 70)
|
|
45
|
+
longitude = self._rng.uniform(-180, 180)
|
|
46
|
+
return (latitude, longitude)
|
|
47
|
+
|
|
48
|
+
def _generate_random_time(self, days_back: int = 7) -> datetime:
|
|
49
|
+
"""Generate a random time within the past few days."""
|
|
50
|
+
now = datetime.utcnow()
|
|
51
|
+
random_hours = self._rng.randint(0, days_back * 24)
|
|
52
|
+
return now - timedelta(hours=random_hours)
|
|
53
|
+
|
|
54
|
+
async def _fetch_weather_data(
|
|
55
|
+
self,
|
|
56
|
+
latitude: float,
|
|
57
|
+
longitude: float,
|
|
58
|
+
target_time: Optional[datetime] = None
|
|
59
|
+
) -> Optional[dict]:
|
|
60
|
+
"""Fetch weather data from Open-Meteo API."""
|
|
61
|
+
params = {
|
|
62
|
+
"latitude": latitude,
|
|
63
|
+
"longitude": longitude,
|
|
64
|
+
"hourly": ",".join(self.WEATHER_VARIABLES),
|
|
65
|
+
"timezone": "UTC"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if target_time:
|
|
69
|
+
date_str = target_time.strftime("%Y-%m-%d")
|
|
70
|
+
params["start_date"] = date_str
|
|
71
|
+
params["end_date"] = date_str
|
|
72
|
+
|
|
73
|
+
async with aiohttp.ClientSession() as session:
|
|
74
|
+
try:
|
|
75
|
+
async with session.get(self.BASE_URL, params=params) as response:
|
|
76
|
+
if response.status == 200:
|
|
77
|
+
return await response.json()
|
|
78
|
+
return None
|
|
79
|
+
except Exception:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
async def _fetch_current_location(self) -> Optional[Tuple[float, float]]:
|
|
83
|
+
"""Fetch approximate current location using IP geolocation via geopy."""
|
|
84
|
+
try:
|
|
85
|
+
from geopy.geocoders import Nominatim
|
|
86
|
+
|
|
87
|
+
# First get public IP location using ipinfo.io (free, no key needed)
|
|
88
|
+
async with aiohttp.ClientSession() as session:
|
|
89
|
+
async with session.get("https://ipinfo.io/json") as response:
|
|
90
|
+
if response.status == 200:
|
|
91
|
+
data = await response.json()
|
|
92
|
+
loc = data.get("loc", "")
|
|
93
|
+
if loc:
|
|
94
|
+
lat, lon = loc.split(",")
|
|
95
|
+
return (float(lat), float(lon))
|
|
96
|
+
|
|
97
|
+
# Fallback: use geopy to geocode a random city for entropy
|
|
98
|
+
geolocator = Nominatim(user_agent="multirandom_weather")
|
|
99
|
+
# Pick a random major city for location entropy
|
|
100
|
+
cities = ["Tokyo", "New York", "London", "Sydney", "Mumbai", "São Paulo"]
|
|
101
|
+
city = self._rng.choice(cities)
|
|
102
|
+
location = geolocator.geocode(city)
|
|
103
|
+
if location:
|
|
104
|
+
return (location.latitude, location.longitude)
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
except Exception:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
def _extract_entropy(self, weather_data: dict, target_time: Optional[datetime] = None) -> bytes:
|
|
111
|
+
"""Extract entropy bytes from weather data."""
|
|
112
|
+
entropy_values = []
|
|
113
|
+
hourly = weather_data.get("hourly", {})
|
|
114
|
+
|
|
115
|
+
hour_index = 0
|
|
116
|
+
if target_time and "time" in hourly:
|
|
117
|
+
times = hourly.get("time", [])
|
|
118
|
+
target_hour = target_time.strftime("%Y-%m-%dT%H:00")
|
|
119
|
+
if target_hour in times:
|
|
120
|
+
hour_index = times.index(target_hour)
|
|
121
|
+
else:
|
|
122
|
+
hour_index = self._rng.randint(0, max(0, len(times) - 1))
|
|
123
|
+
elif "time" in hourly:
|
|
124
|
+
times = hourly.get("time", [])
|
|
125
|
+
hour_index = self._rng.randint(0, max(0, len(times) - 1))
|
|
126
|
+
|
|
127
|
+
for var in self.WEATHER_VARIABLES:
|
|
128
|
+
values = hourly.get(var, [])
|
|
129
|
+
if values and hour_index < len(values) and values[hour_index] is not None:
|
|
130
|
+
entropy_values.append(values[hour_index])
|
|
131
|
+
|
|
132
|
+
entropy_values.extend([
|
|
133
|
+
weather_data.get("latitude", 0),
|
|
134
|
+
weather_data.get("longitude", 0),
|
|
135
|
+
weather_data.get("elevation", 0),
|
|
136
|
+
datetime.utcnow().timestamp()
|
|
137
|
+
])
|
|
138
|
+
|
|
139
|
+
entropy_str = "|".join(str(v) for v in entropy_values)
|
|
140
|
+
return hashlib.sha256(entropy_str.encode()).digest()
|
|
141
|
+
|
|
142
|
+
async def get_seed(
|
|
143
|
+
self,
|
|
144
|
+
use_random_location: bool = True,
|
|
145
|
+
use_current_location: bool = False,
|
|
146
|
+
use_random_time: bool = True,
|
|
147
|
+
num_random_locations: int = 2
|
|
148
|
+
) -> int:
|
|
149
|
+
"""
|
|
150
|
+
Get a seed value from weather entropy.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
use_random_location: Fetch weather from random locations
|
|
154
|
+
use_current_location: Fetch weather from current location
|
|
155
|
+
use_random_time: Use random times for weather data
|
|
156
|
+
num_random_locations: Number of random locations to sample
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Integer seed derived from weather data
|
|
160
|
+
"""
|
|
161
|
+
entropy_pool = b""
|
|
162
|
+
tasks = []
|
|
163
|
+
|
|
164
|
+
# Random locations
|
|
165
|
+
if use_random_location:
|
|
166
|
+
for _ in range(num_random_locations):
|
|
167
|
+
lat, lon = self._generate_random_location()
|
|
168
|
+
target_time = self._generate_random_time() if use_random_time else None
|
|
169
|
+
tasks.append((self._fetch_weather_data(lat, lon, target_time), target_time))
|
|
170
|
+
|
|
171
|
+
# Current location
|
|
172
|
+
if use_current_location:
|
|
173
|
+
if self._current_location:
|
|
174
|
+
lat, lon = self._current_location
|
|
175
|
+
else:
|
|
176
|
+
location = await self._fetch_current_location()
|
|
177
|
+
if location:
|
|
178
|
+
lat, lon = location
|
|
179
|
+
self._current_location = location
|
|
180
|
+
else:
|
|
181
|
+
lat, lon = 0, 0
|
|
182
|
+
|
|
183
|
+
target_time = self._generate_random_time() if use_random_time else None
|
|
184
|
+
tasks.append((self._fetch_weather_data(lat, lon, target_time), target_time))
|
|
185
|
+
|
|
186
|
+
# Gather all weather data
|
|
187
|
+
for task, target_time in tasks:
|
|
188
|
+
weather_data = await task
|
|
189
|
+
if weather_data:
|
|
190
|
+
entropy = self._extract_entropy(weather_data, target_time)
|
|
191
|
+
entropy_pool = hashlib.sha256(entropy_pool + entropy).digest()
|
|
192
|
+
|
|
193
|
+
# Fallback if no data gathered
|
|
194
|
+
if not entropy_pool:
|
|
195
|
+
entropy_pool = hashlib.sha256(str(datetime.utcnow().timestamp()).encode()).digest()
|
|
196
|
+
|
|
197
|
+
return int.from_bytes(entropy_pool, 'big')
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class RandomUsingOpenMeteoAPI(random.Random):
|
|
201
|
+
"""
|
|
202
|
+
A random.Random subclass seeded with weather entropy from Open-Meteo API.
|
|
203
|
+
|
|
204
|
+
Uses weather data to generate a seed, then delegates all random generation
|
|
205
|
+
to the standard Mersenne Twister (random.Random).
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def __init__(self, auto_seed: bool = True):
|
|
209
|
+
"""
|
|
210
|
+
Initialize the random generator.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
auto_seed: If True, automatically seeds with weather entropy.
|
|
214
|
+
"""
|
|
215
|
+
super().__init__()
|
|
216
|
+
self._api = OpenMeteoAPI()
|
|
217
|
+
|
|
218
|
+
if auto_seed:
|
|
219
|
+
self.seed_from_weather()
|
|
220
|
+
|
|
221
|
+
def _run_async(self, coro):
|
|
222
|
+
"""Helper to run async code synchronously."""
|
|
223
|
+
try:
|
|
224
|
+
asyncio.get_running_loop()
|
|
225
|
+
import concurrent.futures
|
|
226
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
227
|
+
future = pool.submit(asyncio.run, coro)
|
|
228
|
+
return future.result()
|
|
229
|
+
except RuntimeError:
|
|
230
|
+
return asyncio.run(coro)
|
|
231
|
+
|
|
232
|
+
def seed_from_weather(
|
|
233
|
+
self,
|
|
234
|
+
use_random_location: bool = True,
|
|
235
|
+
use_current_location: bool = True,
|
|
236
|
+
use_random_time: bool = True
|
|
237
|
+
):
|
|
238
|
+
"""Seed the random generator using weather entropy mixed with hash random."""
|
|
239
|
+
try:
|
|
240
|
+
# Get weather-based seed
|
|
241
|
+
weather_seed = self._run_async(self._api.get_seed(
|
|
242
|
+
use_random_location=use_random_location,
|
|
243
|
+
use_current_location=use_current_location,
|
|
244
|
+
use_random_time=use_random_time
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
# Mix with original random module's entropy via SHA256
|
|
248
|
+
from ..hash_rand.sha_rand import SHA256Random
|
|
249
|
+
import time
|
|
250
|
+
import os
|
|
251
|
+
|
|
252
|
+
# Create SHA256 random seeded with os.urandom + time
|
|
253
|
+
sha_seed = int.from_bytes(os.urandom(32), 'big') ^ int(time.time() * 1000000)
|
|
254
|
+
sha_rng = SHA256Random(seed=sha_seed)
|
|
255
|
+
sha_entropy = int.from_bytes(sha_rng.getrandbytes(32), 'big')
|
|
256
|
+
|
|
257
|
+
# Also get some entropy from Python's random module
|
|
258
|
+
orig_entropy = int.from_bytes(os.urandom(16), 'big')
|
|
259
|
+
|
|
260
|
+
# Combine all entropy sources with XOR and hash
|
|
261
|
+
combined = weather_seed ^ sha_entropy ^ orig_entropy
|
|
262
|
+
final_seed = int.from_bytes(
|
|
263
|
+
hashlib.sha256(combined.to_bytes(64, 'big')).digest(),
|
|
264
|
+
'big'
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
super().seed(final_seed)
|
|
268
|
+
except Exception:
|
|
269
|
+
import time
|
|
270
|
+
super().seed(int(time.time() * 1000000))
|
|
271
|
+
|
|
272
|
+
def seed(self, a=None, version=2):
|
|
273
|
+
"""Seed the generator. If a is None, seeds from weather."""
|
|
274
|
+
if a is None:
|
|
275
|
+
self.seed_from_weather()
|
|
276
|
+
else:
|
|
277
|
+
super().seed(a, version)
|
|
278
|
+
|
|
279
|
+
def reseed_from_weather(self):
|
|
280
|
+
"""Force a reseed from fresh weather data."""
|
|
281
|
+
self.seed_from_weather()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multirandom
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: 🎲 The truth external PyPI random package, have true_rand, virt_rand, hash_rand, ...
|
|
5
5
|
Author-email: BlackCatOfficialytb <quanvm0501@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -12,6 +12,7 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: aiohttp
|
|
14
14
|
Requires-Dist: pyautogui
|
|
15
|
+
Requires-Dist: geopy
|
|
15
16
|
Dynamic: license-file
|
|
16
17
|
|
|
17
18
|
# MultiRandom 🎲
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|