sideload 1.3.0__py3-none-any.whl → 1.4.0__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 sideload might be problematic. Click here for more details.
- sideload/cli.py +37 -3
- sideload/main.py +1 -1
- sideload/scripts/cleanup_pypi.py +208 -27
- {sideload-1.3.0.dist-info → sideload-1.4.0.dist-info}/METADATA +2 -1
- sideload-1.4.0.dist-info/RECORD +11 -0
- sideload-1.3.0.dist-info/RECORD +0 -11
- {sideload-1.3.0.dist-info → sideload-1.4.0.dist-info}/WHEEL +0 -0
- {sideload-1.3.0.dist-info → sideload-1.4.0.dist-info}/entry_points.txt +0 -0
sideload/cli.py
CHANGED
|
@@ -485,9 +485,14 @@ Examples:
|
|
|
485
485
|
|
|
486
486
|
console.print(Rule("📦 Downloading Packages"))
|
|
487
487
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
488
|
+
# Use work_dir for downloads if provided, otherwise use temp directory
|
|
489
|
+
use_work_dir = args.work_dir is not None
|
|
490
|
+
if use_work_dir:
|
|
491
|
+
download_dir = args.work_dir / "wheels"
|
|
492
|
+
download_dir.mkdir(parents=True, exist_ok=True)
|
|
493
|
+
console.print(f"[yellow]📥 Downloading packages to: {download_dir}[/yellow]")
|
|
494
|
+
|
|
495
|
+
wheel_files = client.download_packages(package_names, download_dir, args.debug)
|
|
491
496
|
|
|
492
497
|
if not wheel_files:
|
|
493
498
|
console.print(
|
|
@@ -512,6 +517,35 @@ Examples:
|
|
|
512
517
|
console.print(
|
|
513
518
|
f"📊 File size: [cyan]{output_file.stat().st_size:,} bytes[/cyan]"
|
|
514
519
|
)
|
|
520
|
+
else:
|
|
521
|
+
# Use temporary directory for downloads
|
|
522
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
523
|
+
temp_path = Path(temp_dir)
|
|
524
|
+
wheel_files = client.download_packages(package_names, temp_path, args.debug)
|
|
525
|
+
|
|
526
|
+
if not wheel_files:
|
|
527
|
+
console.print(
|
|
528
|
+
"❌ No packages were downloaded successfully", style="red"
|
|
529
|
+
)
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
# Extract and reassemble
|
|
533
|
+
console.print(Rule("🔧 Reassembling File"))
|
|
534
|
+
original_filename = data.get("filename", "downloaded_file")
|
|
535
|
+
output_file = args.output / original_filename
|
|
536
|
+
|
|
537
|
+
client.extract_and_reassemble(
|
|
538
|
+
wheel_files, package_names, original_filename, output_file, args.debug, args.work_dir
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# Success!
|
|
542
|
+
console.print(Rule("✨ Complete"))
|
|
543
|
+
console.print(
|
|
544
|
+
f"🎉 File successfully downloaded to: [bold green]{output_file}[/bold green]"
|
|
545
|
+
)
|
|
546
|
+
console.print(
|
|
547
|
+
f"📊 File size: [cyan]{output_file.stat().st_size:,} bytes[/cyan]"
|
|
548
|
+
)
|
|
515
549
|
|
|
516
550
|
except KeyboardInterrupt:
|
|
517
551
|
console.print("\n⚠️ Download interrupted by user", style="yellow")
|
sideload/main.py
CHANGED
|
@@ -11,7 +11,7 @@ from sideload.jsonbin_connector import JSONBinConnector
|
|
|
11
11
|
|
|
12
12
|
JSONBIN_TOKEN = os.environ["JSONBIN_TOKEN"]
|
|
13
13
|
PYPI_TOKEN = os.environ["PYPI_TOKEN"]
|
|
14
|
-
MAX_PACKAGE_SIZE =
|
|
14
|
+
MAX_PACKAGE_SIZE = 92 * 1024 * 1024 # 95 MB
|
|
15
15
|
|
|
16
16
|
LAST_BINS: dict[str, str | None] = {}
|
|
17
17
|
|
sideload/scripts/cleanup_pypi.py
CHANGED
|
@@ -6,16 +6,32 @@ Admin script to delete all sideload-* packages from PyPI using browser automatio
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import asyncio
|
|
9
|
+
import random
|
|
9
10
|
from playwright.async_api import async_playwright
|
|
11
|
+
import pyotp
|
|
10
12
|
|
|
11
13
|
PYPI_USER = os.environ.get("PYPI_USER")
|
|
12
14
|
PYPI_PASSWORD = os.environ.get("PYPI_PASSWORD")
|
|
15
|
+
PYPI_TOTP = os.environ.get("PYPI_TOTP") # Optional: TOTP secret key
|
|
13
16
|
|
|
14
17
|
if not PYPI_USER or not PYPI_PASSWORD:
|
|
15
18
|
print("❌ PYPI_USER and PYPI_PASSWORD environment variables must be set")
|
|
16
19
|
sys.exit(1)
|
|
17
20
|
|
|
18
21
|
|
|
22
|
+
async def human_like_mouse_movement(page):
|
|
23
|
+
"""Simulate human-like mouse movements across the page"""
|
|
24
|
+
viewport_size = page.viewport_size
|
|
25
|
+
width = viewport_size.get('width', 1280) if viewport_size else 1280
|
|
26
|
+
height = viewport_size.get('height', 720) if viewport_size else 720
|
|
27
|
+
|
|
28
|
+
for _ in range(random.randint(3, 6)):
|
|
29
|
+
x = random.randint(50, width - 50)
|
|
30
|
+
y = random.randint(50, height - 50)
|
|
31
|
+
await page.mouse.move(x, y)
|
|
32
|
+
await asyncio.sleep(random.uniform(0.15, 0.4))
|
|
33
|
+
|
|
34
|
+
|
|
19
35
|
async def delete_project(page, package_name: str) -> bool:
|
|
20
36
|
"""Delete a project from PyPI using browser automation"""
|
|
21
37
|
try:
|
|
@@ -35,8 +51,34 @@ async def delete_project(page, package_name: str) -> bool:
|
|
|
35
51
|
await page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
|
|
36
52
|
await asyncio.sleep(0.5)
|
|
37
53
|
|
|
54
|
+
# Extract the exact project name from the page to ensure correct case
|
|
55
|
+
# Look for the project name in the delete section
|
|
56
|
+
exact_project_name = await page.evaluate('''() => {
|
|
57
|
+
// Find the label that mentions the project name
|
|
58
|
+
const labels = Array.from(document.querySelectorAll('label'));
|
|
59
|
+
for (const label of labels) {
|
|
60
|
+
const text = label.textContent;
|
|
61
|
+
if (text && text.includes('confirm by typing the project name')) {
|
|
62
|
+
// Extract the project name from something like "confirm by typing the project name (project-name) below"
|
|
63
|
+
const match = text.match(/\\(([^)]+)\\)/);
|
|
64
|
+
if (match) return match[1];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Fallback: get from URL
|
|
68
|
+
const path = window.location.pathname;
|
|
69
|
+
const urlMatch = path.match(/\\/manage\\/project\\/([^\\/]+)/);
|
|
70
|
+
return urlMatch ? decodeURIComponent(urlMatch[1]) : null;
|
|
71
|
+
}''')
|
|
72
|
+
|
|
73
|
+
if not exact_project_name:
|
|
74
|
+
print(f" ⚠️ Could not extract exact project name from page, using: {package_name}")
|
|
75
|
+
exact_project_name = package_name
|
|
76
|
+
else:
|
|
77
|
+
print(f" 📝 Extracted exact project name: {exact_project_name}")
|
|
78
|
+
|
|
38
79
|
# Find and check all "I understand..." checkboxes
|
|
39
80
|
checkboxes = await page.query_selector_all('input[type="checkbox"]')
|
|
81
|
+
print(f" ✓ Found {len(checkboxes)} checkboxes")
|
|
40
82
|
for checkbox in checkboxes:
|
|
41
83
|
await checkbox.check()
|
|
42
84
|
|
|
@@ -46,29 +88,55 @@ async def delete_project(page, package_name: str) -> bool:
|
|
|
46
88
|
print(f" ⚠️ Could not find confirmation input for {package_name}")
|
|
47
89
|
return False
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
# Clear and type the exact project name with human-like behavior
|
|
92
|
+
await confirm_input.clear()
|
|
93
|
+
await asyncio.sleep(random.uniform(0.2, 0.5))
|
|
94
|
+
|
|
95
|
+
# Move mouse before typing
|
|
96
|
+
await human_like_mouse_movement(page)
|
|
97
|
+
|
|
98
|
+
# Type each character with varying delays
|
|
99
|
+
for char in exact_project_name:
|
|
100
|
+
await confirm_input.type(char, delay=random.randint(80, 200))
|
|
101
|
+
await asyncio.sleep(random.uniform(0.05, 0.15))
|
|
102
|
+
|
|
103
|
+
print(f" ✓ Typed project name: {exact_project_name}")
|
|
50
104
|
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
105
|
+
# Wait a moment for the button to become enabled (human-like pause)
|
|
106
|
+
await asyncio.sleep(random.uniform(1.0, 2.0))
|
|
107
|
+
|
|
108
|
+
# Move mouse naturally
|
|
109
|
+
await human_like_mouse_movement(page)
|
|
110
|
+
|
|
111
|
+
# Find and click the delete link
|
|
112
|
+
delete_link = await page.query_selector('[data-delete-confirm-target="button"]')
|
|
54
113
|
if not delete_link:
|
|
55
|
-
print(f" ⚠️ Could not find delete
|
|
114
|
+
print(f" ⚠️ Could not find delete button")
|
|
56
115
|
return False
|
|
57
116
|
|
|
58
|
-
#
|
|
59
|
-
await
|
|
117
|
+
# Check if the button is enabled
|
|
118
|
+
is_disabled = await delete_link.evaluate('(el) => el.classList.contains("button--disabled") || el.hasAttribute("disabled")')
|
|
119
|
+
if is_disabled:
|
|
120
|
+
print(f" ⚠️ Delete button is still disabled")
|
|
121
|
+
# Take a screenshot for debugging
|
|
122
|
+
await page.screenshot(path=f"debug_{package_name}.png")
|
|
123
|
+
print(f" 📸 Screenshot saved as debug_{package_name}.png")
|
|
124
|
+
return False
|
|
60
125
|
|
|
61
|
-
|
|
62
|
-
await delete_link.click()
|
|
63
|
-
await asyncio.sleep(0.5)
|
|
126
|
+
print(f" 🖱️ Clicking delete button...")
|
|
64
127
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
|
|
128
|
+
# Move mouse to the button area naturally
|
|
129
|
+
box = await delete_link.bounding_box()
|
|
130
|
+
if box:
|
|
131
|
+
# Move to a random point within the button
|
|
132
|
+
target_x = box['x'] + random.uniform(10, box['width'] - 10)
|
|
133
|
+
target_y = box['y'] + random.uniform(10, box['height'] - 10)
|
|
134
|
+
await page.mouse.move(target_x, target_y)
|
|
135
|
+
await asyncio.sleep(random.uniform(0.2, 0.5))
|
|
70
136
|
|
|
71
|
-
|
|
137
|
+
# Click and wait for navigation
|
|
138
|
+
await delete_link.click()
|
|
139
|
+
await asyncio.sleep(random.uniform(0.5, 1.0))
|
|
72
140
|
await page.wait_for_load_state("networkidle")
|
|
73
141
|
|
|
74
142
|
print(f" ✅ Deleted {package_name}")
|
|
@@ -76,6 +144,14 @@ async def delete_project(page, package_name: str) -> bool:
|
|
|
76
144
|
|
|
77
145
|
except Exception as e:
|
|
78
146
|
print(f" ❌ Error deleting {package_name}: {e}")
|
|
147
|
+
import traceback
|
|
148
|
+
traceback.print_exc()
|
|
149
|
+
# Take screenshot on error
|
|
150
|
+
try:
|
|
151
|
+
await page.screenshot(path=f"error_{package_name}.png")
|
|
152
|
+
print(f" 📸 Error screenshot saved as error_{package_name}.png")
|
|
153
|
+
except:
|
|
154
|
+
pass
|
|
79
155
|
return False
|
|
80
156
|
|
|
81
157
|
|
|
@@ -107,6 +183,10 @@ async def get_user_projects(page) -> list[str]:
|
|
|
107
183
|
async def main():
|
|
108
184
|
print("🧹 PyPI Sideload Package Cleanup Tool")
|
|
109
185
|
print("=" * 50)
|
|
186
|
+
if PYPI_TOTP:
|
|
187
|
+
print("🔐 TOTP auto-generation enabled")
|
|
188
|
+
else:
|
|
189
|
+
print("⚠️ TOTP auto-generation disabled (set PYPI_TOTP to enable)")
|
|
110
190
|
print()
|
|
111
191
|
|
|
112
192
|
async with async_playwright() as p:
|
|
@@ -121,27 +201,128 @@ async def main():
|
|
|
121
201
|
print(" Opening login page...")
|
|
122
202
|
await page.goto('https://pypi.org/account/login/')
|
|
123
203
|
|
|
124
|
-
# Fill in login credentials
|
|
204
|
+
# Fill in login credentials (fast, no need to be slow here)
|
|
125
205
|
print(" Filling credentials...")
|
|
126
206
|
await page.fill('#username', PYPI_USER)
|
|
207
|
+
await asyncio.sleep(random.uniform(0.2, 0.4))
|
|
127
208
|
await page.fill('#password', PYPI_PASSWORD)
|
|
209
|
+
await asyncio.sleep(random.uniform(0.3, 0.6))
|
|
128
210
|
|
|
129
211
|
# Submit login form
|
|
130
212
|
await page.click('input[type="submit"]')
|
|
131
|
-
await asyncio.sleep(
|
|
213
|
+
await asyncio.sleep(2)
|
|
132
214
|
|
|
133
215
|
# Wait for user to complete TOTP if required
|
|
134
216
|
current_url = page.url
|
|
135
217
|
if '/account/two-factor/' in current_url:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
218
|
+
if PYPI_TOTP:
|
|
219
|
+
print(" 🔐 Generating TOTP code...")
|
|
220
|
+
|
|
221
|
+
# Simulate human-like delay before interacting
|
|
222
|
+
await asyncio.sleep(random.uniform(1.5, 2.5))
|
|
223
|
+
|
|
224
|
+
# Move mouse around naturally across the page
|
|
225
|
+
print(" 🖱️ Moving mouse naturally...")
|
|
226
|
+
await human_like_mouse_movement(page)
|
|
227
|
+
|
|
228
|
+
totp = pyotp.TOTP(PYPI_TOTP)
|
|
229
|
+
code = totp.now()
|
|
230
|
+
print(f" ✓ Generated TOTP code: {code}")
|
|
231
|
+
|
|
232
|
+
# Find TOTP input field and enter the code
|
|
233
|
+
totp_input = await page.query_selector('input[name="totp_value"]')
|
|
234
|
+
if not totp_input:
|
|
235
|
+
totp_input = await page.query_selector('input[type="text"]')
|
|
236
|
+
|
|
237
|
+
if totp_input:
|
|
238
|
+
# Click on the input field naturally
|
|
239
|
+
await totp_input.click()
|
|
240
|
+
await asyncio.sleep(random.uniform(0.4, 0.8))
|
|
241
|
+
|
|
242
|
+
# Type each character slowly with human-like delays
|
|
243
|
+
print(" ⌨️ Typing TOTP code slowly...")
|
|
244
|
+
for i, char in enumerate(code):
|
|
245
|
+
await totp_input.type(char, delay=random.randint(100, 250))
|
|
246
|
+
await asyncio.sleep(random.uniform(0.1, 0.25))
|
|
247
|
+
# Occasionally move mouse during typing
|
|
248
|
+
if i % 2 == 0:
|
|
249
|
+
x = random.randint(200, 600)
|
|
250
|
+
y = random.randint(200, 500)
|
|
251
|
+
await page.mouse.move(x, y)
|
|
252
|
+
|
|
253
|
+
print(" ✓ Entered TOTP code")
|
|
254
|
+
|
|
255
|
+
# Wait a bit before submitting (like a human would)
|
|
256
|
+
await asyncio.sleep(random.uniform(0.8, 1.5))
|
|
257
|
+
|
|
258
|
+
# More mouse movements
|
|
259
|
+
print(" 🖱️ Moving mouse before submit...")
|
|
260
|
+
await human_like_mouse_movement(page)
|
|
261
|
+
|
|
262
|
+
# Submit the form
|
|
263
|
+
submit_button = await page.query_selector('button[type="submit"]')
|
|
264
|
+
if not submit_button:
|
|
265
|
+
submit_button = await page.query_selector('input[type="submit"]')
|
|
266
|
+
|
|
267
|
+
if submit_button:
|
|
268
|
+
await submit_button.click()
|
|
269
|
+
print(" ✓ Submitted TOTP, waiting for response...")
|
|
270
|
+
|
|
271
|
+
# Wait for navigation
|
|
272
|
+
await asyncio.sleep(4)
|
|
273
|
+
|
|
274
|
+
# Check if we've successfully logged in
|
|
275
|
+
current_url = page.url
|
|
276
|
+
if '/account/' in current_url or '/manage/' in current_url:
|
|
277
|
+
print(" ✓ Successfully logged in!")
|
|
278
|
+
elif '/account/two-factor/' in current_url:
|
|
279
|
+
# Still on 2FA page - likely a captcha
|
|
280
|
+
print("\n" + "=" * 60)
|
|
281
|
+
print(" 🤖 CAPTCHA DETECTED!")
|
|
282
|
+
print(" 👤 Please solve the captcha in the browser window")
|
|
283
|
+
print(" ⏳ Waiting for you to complete it...")
|
|
284
|
+
print("=" * 60 + "\n")
|
|
285
|
+
|
|
286
|
+
# Wait for user to solve captcha - keep checking
|
|
287
|
+
# We need to wait until we're actually logged in (on account page or similar)
|
|
288
|
+
while True:
|
|
289
|
+
await asyncio.sleep(2)
|
|
290
|
+
current_url = page.url
|
|
291
|
+
# Check if we've successfully logged in (not just left the 2FA page)
|
|
292
|
+
if ('/account/' in current_url or '/manage/' in current_url) and '/account/two-factor/' not in current_url:
|
|
293
|
+
break
|
|
294
|
+
# Print status every 2 seconds to show we're still waiting
|
|
295
|
+
print(" ⏳ Still waiting for captcha completion...", end='\r')
|
|
296
|
+
|
|
297
|
+
print("\n ✓ Captcha solved and logged in! Continuing...")
|
|
298
|
+
else:
|
|
299
|
+
print(f" ⚠️ Unexpected page: {current_url}")
|
|
300
|
+
print(" ⏳ Waiting for login to complete...")
|
|
301
|
+
# Wait until we're on a known good page
|
|
302
|
+
while True:
|
|
303
|
+
await asyncio.sleep(2)
|
|
304
|
+
current_url = page.url
|
|
305
|
+
if '/account/' in current_url or '/manage/' in current_url:
|
|
306
|
+
break
|
|
307
|
+
print(" ⏳ Still waiting...", end='\r')
|
|
308
|
+
print("\n ✓ Login completed!")
|
|
309
|
+
else:
|
|
310
|
+
print(" ⚠️ Could not find submit button")
|
|
311
|
+
else:
|
|
312
|
+
print(" ⚠️ Could not find TOTP input field")
|
|
313
|
+
else:
|
|
314
|
+
print(" ⚠️ TOTP required - please enter your 2FA code in the browser...")
|
|
315
|
+
print(" 👤 Waiting for you to complete 2FA (and captcha if present)...")
|
|
316
|
+
while True:
|
|
317
|
+
await asyncio.sleep(2)
|
|
318
|
+
current_url = page.url
|
|
319
|
+
# Wait until we're actually logged in
|
|
320
|
+
if '/account/two-factor/' not in current_url and ('/account/' in current_url or '/manage/' in current_url):
|
|
321
|
+
break
|
|
322
|
+
print(" ⏳ Still waiting for 2FA completion...", end='\r')
|
|
323
|
+
print("\n ✓ 2FA completed!")
|
|
324
|
+
|
|
325
|
+
print("\n✅ Login completed!")
|
|
145
326
|
|
|
146
327
|
# Get all sideload projects
|
|
147
328
|
projects = await get_user_projects(page)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sideload
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Download large files via PyPI packages
|
|
5
5
|
Author: Sygmei
|
|
6
6
|
Author-email: Sygmei <3835355+Sygmei@users.noreply.github.com>
|
|
@@ -11,6 +11,7 @@ Requires-Dist: rich>=13.0.0
|
|
|
11
11
|
Requires-Dist: httpx>=0.28.1
|
|
12
12
|
Requires-Dist: pip>=25.2
|
|
13
13
|
Requires-Dist: playwright>=1.55.0
|
|
14
|
+
Requires-Dist: pyotp>=2.9.0
|
|
14
15
|
Requires-Python: >=3.12
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
sideload/__init__.py,sha256=Y3rHLtR7n0sjLXrn-BEcrbIHx-9uE1tPZkooavo7xcA,222
|
|
2
|
+
sideload/cli.py,sha256=4nG0QLwcYypN2A7ENffEE4IEh0VNzwrAdL7cX4ZxjPM,21932
|
|
3
|
+
sideload/jsonbin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
sideload/jsonbin_connector.py,sha256=IS7YdSBZeZMoTwOyA1I0T7aw36-KERYNnD324dRRI5A,2305
|
|
5
|
+
sideload/jsonbin_old.py,sha256=Rs657F3dTtdoxVYolByuyINABCIQx_VLxwxgdne1mws,8865
|
|
6
|
+
sideload/main.py,sha256=_YE0b93jhDFN8PcBfHLKbZp1EPQGfWq3wIYyX7nT7Qo,7600
|
|
7
|
+
sideload/scripts/cleanup_pypi.py,sha256=YPzYVsutFVsRoCi4qG6vZo8x5YiLzpAfJ7xKxZiZWgI,15264
|
|
8
|
+
sideload-1.4.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
|
|
9
|
+
sideload-1.4.0.dist-info/entry_points.txt,sha256=7ULrIjaVhrxMhuddTeoPjeIrqmIvVc9cSU3lZU2_YqE,44
|
|
10
|
+
sideload-1.4.0.dist-info/METADATA,sha256=VbhpNuxWsOFNVCvw9gBBUtT-mxqKvM626A5rGvzB4xE,4309
|
|
11
|
+
sideload-1.4.0.dist-info/RECORD,,
|
sideload-1.3.0.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
sideload/__init__.py,sha256=Y3rHLtR7n0sjLXrn-BEcrbIHx-9uE1tPZkooavo7xcA,222
|
|
2
|
-
sideload/cli.py,sha256=8CcoZf9_7XHzlFA9XBBZn19RRNAbrFfRshVIWWSaFhQ,20186
|
|
3
|
-
sideload/jsonbin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
sideload/jsonbin_connector.py,sha256=IS7YdSBZeZMoTwOyA1I0T7aw36-KERYNnD324dRRI5A,2305
|
|
5
|
-
sideload/jsonbin_old.py,sha256=Rs657F3dTtdoxVYolByuyINABCIQx_VLxwxgdne1mws,8865
|
|
6
|
-
sideload/main.py,sha256=EiZguc4-ug8RM6vXkZuXW_ps8jFWoAfeKBBNy7FX-F0,7600
|
|
7
|
-
sideload/scripts/cleanup_pypi.py,sha256=CouuOhnbpSbrFc1KhlOeZAuajVVpGF9d4qwFpIKkEvY,5906
|
|
8
|
-
sideload-1.3.0.dist-info/WHEEL,sha256=-neZj6nU9KAMg2CnCY6T3w8J53nx1kFGw_9HfoSzM60,79
|
|
9
|
-
sideload-1.3.0.dist-info/entry_points.txt,sha256=7ULrIjaVhrxMhuddTeoPjeIrqmIvVc9cSU3lZU2_YqE,44
|
|
10
|
-
sideload-1.3.0.dist-info/METADATA,sha256=r0lod7DO3pFy-v0M9HQQFHKTiPih1jCApRnwZWVRZ8o,4281
|
|
11
|
-
sideload-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|