weed-cli 0.1.0__tar.gz → 1.1.2__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.
- weed_cli-1.1.2/LICENSE +21 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/PKG-INFO +3 -1
- {weed_cli-0.1.0 → weed_cli-1.1.2}/node.py +20 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/pyproject.toml +1 -1
- {weed_cli-0.1.0 → weed_cli-1.1.2}/shell.py +18 -19
- {weed_cli-0.1.0 → weed_cli-1.1.2}/web_ui.py +78 -5
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed.py +14 -10
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/PKG-INFO +3 -1
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/SOURCES.txt +1 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/README.md +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/dht.py +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/discovery_relay.py +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/lightning_settle.py +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/poc_reputation.py +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/setup.cfg +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/tunnel_relay.py +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/dependency_links.txt +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/entry_points.txt +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/requires.txt +0 -0
- {weed_cli-0.1.0 → weed_cli-1.1.2}/weed_cli.egg-info/top_level.txt +0 -0
weed_cli-1.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ryan Scarbery
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weed-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Censorship-resistant video PoC — discovery/hosting/download over signed relay events, a real Kademlia DHT, or a TLS-capable NAT-traversal tunnel
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: p2p,video,censorship-resistant,discovery,dht,kademlia,nat-traversal
|
|
@@ -13,6 +13,7 @@ Classifier: Topic :: Communications :: File Sharing
|
|
|
13
13
|
Classifier: Topic :: Internet
|
|
14
14
|
Requires-Python: >=3.11
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
16
17
|
Requires-Dist: btcvm>=1.3.7
|
|
17
18
|
Requires-Dist: cryptography>=41.0
|
|
18
19
|
Provides-Extra: dev
|
|
@@ -25,6 +26,7 @@ Provides-Extra: qr
|
|
|
25
26
|
Requires-Dist: qrcode>=7.4; extra == "qr"
|
|
26
27
|
Provides-Extra: viz
|
|
27
28
|
Requires-Dist: matplotlib>=3.8; extra == "viz"
|
|
29
|
+
Dynamic: license-file
|
|
28
30
|
|
|
29
31
|
# Censorship-resistant video platform — PoC notes
|
|
30
32
|
|
|
@@ -39,6 +39,26 @@ IDENTITY_PATH = os.path.expanduser('~/.weed_identity.key')
|
|
|
39
39
|
IDENTITY_ARMOR_HEADER = '-----BEGIN WEED IDENTITY KEY-----'
|
|
40
40
|
IDENTITY_ARMOR_FOOTER = '-----END WEED IDENTITY KEY-----'
|
|
41
41
|
|
|
42
|
+
TAGLINE = 'we do in 4 what others do in 5'
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def weed_version():
|
|
46
|
+
"""Installed package version (pyproject.toml's source of truth), with a
|
|
47
|
+
fallback for running straight from source without `pip install -e .`."""
|
|
48
|
+
try:
|
|
49
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
50
|
+
try:
|
|
51
|
+
return version('weed-cli')
|
|
52
|
+
except PackageNotFoundError:
|
|
53
|
+
pass
|
|
54
|
+
except ImportError:
|
|
55
|
+
pass
|
|
56
|
+
return '0.0.0-dev'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def weed_banner():
|
|
60
|
+
return f'weed v{weed_version()} — {TAGLINE}'
|
|
61
|
+
|
|
42
62
|
|
|
43
63
|
def _armor_identity(raw_bytes):
|
|
44
64
|
"""Base64 text with a header/footer — NOT real OpenPGP armor (no
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weed-cli"
|
|
7
|
-
version = "
|
|
7
|
+
version = "1.1.2"
|
|
8
8
|
description = "Censorship-resistant video PoC — discovery/hosting/download over signed relay events, a real Kademlia DHT, or a TLS-capable NAT-traversal tunnel"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -42,7 +42,7 @@ def _bg(fn, *args, **kwargs):
|
|
|
42
42
|
|
|
43
43
|
class WeedShell(cmd.Cmd):
|
|
44
44
|
intro = (
|
|
45
|
-
'\n
|
|
45
|
+
f'\n {node.weed_banner()}\n'
|
|
46
46
|
' Type help or ? for commands. Tab completes. Ctrl-D or q to exit.\n'
|
|
47
47
|
)
|
|
48
48
|
prompt = 'weed> '
|
|
@@ -106,11 +106,12 @@ class WeedShell(cmd.Cmd):
|
|
|
106
106
|
on --relay (default: your session's default relay — see `relay`)
|
|
107
107
|
unless --no-announce is given. --price sets what download charges
|
|
108
108
|
(default free). --tunnel registers with a tunnel_relay.py instead of
|
|
109
|
-
relying on a reachable inbound port — for hosting behind NAT/CGNAT
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
["tls"]) — the relay
|
|
109
|
+
relying on a reachable inbound port — for hosting behind NAT/CGNAT.
|
|
110
|
+
Registers one control connection per file (REGISTER's token is
|
|
111
|
+
each file's own content hash), so a whole tree tunnels fine, not
|
|
112
|
+
just a single file. Prefix tls:// if the relay terminates TLS at
|
|
113
|
+
the edge (e.g. a Fly service with handlers = ["tls"]) — the relay
|
|
114
|
+
process itself never needs to know."""
|
|
114
115
|
parts = shlex.split(arg)
|
|
115
116
|
if not parts:
|
|
116
117
|
print(' usage: host <archive_dir> [--file NAME] [--port N] [--price SAT] '
|
|
@@ -148,10 +149,6 @@ class WeedShell(cmd.Cmd):
|
|
|
148
149
|
i += 1
|
|
149
150
|
|
|
150
151
|
entries = node.load_manifest_entries(archive_dir, file_name)
|
|
151
|
-
if tunnel and len(entries) > 1:
|
|
152
|
-
print(f' {len(entries)} files in {archive_dir} but --tunnel only serves one at a time '
|
|
153
|
-
f'— pass --file NAME to pick one')
|
|
154
|
-
return
|
|
155
152
|
# fail fast, before announcing anything — a manifest entry with no
|
|
156
153
|
# matching chunk data would otherwise get announced to the relay
|
|
157
154
|
# and only fail later, in the background server thread (onecmd's
|
|
@@ -165,17 +162,19 @@ class WeedShell(cmd.Cmd):
|
|
|
165
162
|
elif not relay:
|
|
166
163
|
print(' no relay set (run `relay` first, or pass --relay) — hosting without announcing')
|
|
167
164
|
if tunnel:
|
|
168
|
-
|
|
165
|
+
# one control connection per file, each registered under its
|
|
166
|
+
# own content hash — see do_host's docstring
|
|
169
167
|
relay_host, relay_port, use_tls = node._parse_tunnel(tunnel)
|
|
170
168
|
expanded_dir = os.path.expanduser(archive_dir)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
for entry in entries:
|
|
170
|
+
file_path = entry.get('last_path') or os.path.join(expanded_dir, entry['name'])
|
|
171
|
+
tt = threading.Thread(target=_bg,
|
|
172
|
+
args=(node.run_host_tunnel, relay_host, relay_port,
|
|
173
|
+
entry['sha256'], entry, all_leaves[entry['sha256']],
|
|
174
|
+
file_path, price),
|
|
175
|
+
kwargs={'use_tls': use_tls, 'quiet': True}, daemon=True)
|
|
176
|
+
tt.start()
|
|
177
|
+
self._host_threads.append(tt)
|
|
179
178
|
t = threading.Thread(target=_bg,
|
|
180
179
|
args=(node.run_host_server, archive_dir, file_name, port),
|
|
181
180
|
kwargs={'quiet': True, 'price': price}, daemon=True)
|
|
@@ -20,6 +20,7 @@ import mimetypes
|
|
|
20
20
|
import os
|
|
21
21
|
import sys
|
|
22
22
|
import threading
|
|
23
|
+
import time
|
|
23
24
|
import uuid
|
|
24
25
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
25
26
|
from urllib.parse import urlparse, parse_qs
|
|
@@ -35,6 +36,7 @@ except ImportError:
|
|
|
35
36
|
|
|
36
37
|
WEB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web')
|
|
37
38
|
DEFAULT_RELAY = 'http://127.0.0.1:9101'
|
|
39
|
+
LIBRARY_PATH = os.path.expanduser('~/.weed_library.json')
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class _JobStdout:
|
|
@@ -82,6 +84,52 @@ def _quiet():
|
|
|
82
84
|
_hosts = {} # host_id -> dict describing an actively-hosted file
|
|
83
85
|
_jobs = {} # job_id -> dict describing a download's progress/result
|
|
84
86
|
_lock = threading.Lock()
|
|
87
|
+
|
|
88
|
+
# what's been downloaded/liked/subscribed, persisted to disk so a page
|
|
89
|
+
# reload -- or a server restart -- doesn't forget any of it. Downloads
|
|
90
|
+
# are keyed by content_hash (one record per piece of content, most
|
|
91
|
+
# recent job wins); likes/subscriptions are just lists of hashes/pubkeys.
|
|
92
|
+
# All access goes through _lock, same as _hosts/_jobs.
|
|
93
|
+
_library = {'downloads': {}, 'likes': [], 'subscriptions': []}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _load_library():
|
|
97
|
+
global _library
|
|
98
|
+
try:
|
|
99
|
+
with open(LIBRARY_PATH) as f:
|
|
100
|
+
data = json.load(f)
|
|
101
|
+
_library = {
|
|
102
|
+
'downloads': data.get('downloads') or {},
|
|
103
|
+
'likes': data.get('likes') or [],
|
|
104
|
+
'subscriptions': data.get('subscriptions') or [],
|
|
105
|
+
}
|
|
106
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _save_library():
|
|
111
|
+
"""Caller must hold _lock. Written to a tmp file + os.replace so a
|
|
112
|
+
crash mid-write can't leave a half-written, unparseable JSON file
|
|
113
|
+
behind -- this runs on every like/subscribe/download-finish, not just
|
|
114
|
+
at shutdown."""
|
|
115
|
+
tmp = LIBRARY_PATH + '.tmp'
|
|
116
|
+
with open(tmp, 'w') as f:
|
|
117
|
+
json.dump(_library, f)
|
|
118
|
+
os.replace(tmp, LIBRARY_PATH)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _rehydrate_jobs_from_library():
|
|
122
|
+
"""Lets a restarted server's Downloads tab and Discover "already
|
|
123
|
+
downloaded" check keep working against the same job_ids as before --
|
|
124
|
+
_jobs is otherwise purely in-memory and would forget every finished
|
|
125
|
+
download on restart even though the files (and _library) are still
|
|
126
|
+
there."""
|
|
127
|
+
for content_hash, rec in _library['downloads'].items():
|
|
128
|
+
_jobs[rec['job_id']] = {
|
|
129
|
+
'status': 'done', 'idx': 0, 'n_chunks': None,
|
|
130
|
+
'content_hash': content_hash, 'path': rec['path'],
|
|
131
|
+
'title': rec.get('title'), 'error': None,
|
|
132
|
+
}
|
|
85
133
|
_lan_url = None # set once in run_web_ui() -- the base URL a phone on the
|
|
86
134
|
# same LAN can actually reach this server at, or None if
|
|
87
135
|
# it can't (bound to 127.0.0.1). The client can't compute
|
|
@@ -154,7 +202,7 @@ def _run_host_job(host_id, archive_dir, file_name, port, price, relay_urls, adve
|
|
|
154
202
|
_hosts[host_id].update(status='error', error=f'{type(e).__name__}: {e}')
|
|
155
203
|
|
|
156
204
|
|
|
157
|
-
def _run_download_job(job_id, content_hash, relay_urls, out_path, k, use_lightning):
|
|
205
|
+
def _run_download_job(job_id, content_hash, relay_urls, out_path, k, use_lightning, title=None):
|
|
158
206
|
def on_progress(idx, n_chunks):
|
|
159
207
|
with _lock:
|
|
160
208
|
_jobs[job_id].update(idx=idx, n_chunks=n_chunks)
|
|
@@ -165,6 +213,11 @@ def _run_download_job(job_id, content_hash, relay_urls, out_path, k, use_lightni
|
|
|
165
213
|
use_lightning=use_lightning, on_progress=on_progress)
|
|
166
214
|
with _lock:
|
|
167
215
|
_jobs[job_id].update(status='done', path=path)
|
|
216
|
+
_library['downloads'][content_hash] = {
|
|
217
|
+
'content_hash': content_hash, 'job_id': job_id, 'path': path,
|
|
218
|
+
'title': title, 'downloaded_at': time.time(),
|
|
219
|
+
}
|
|
220
|
+
_save_library()
|
|
168
221
|
except SystemExit as e:
|
|
169
222
|
with _lock:
|
|
170
223
|
_jobs[job_id].update(status='error', error=str(e))
|
|
@@ -202,6 +255,13 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
202
255
|
if path == '/api/hosts':
|
|
203
256
|
with _lock:
|
|
204
257
|
return self._json({'hosts': list(_hosts.values())})
|
|
258
|
+
if path == '/api/library':
|
|
259
|
+
with _lock:
|
|
260
|
+
return self._json({
|
|
261
|
+
'downloads': list(_library['downloads'].values()),
|
|
262
|
+
'likes': list(_library['likes']),
|
|
263
|
+
'subscriptions': list(_library['subscriptions']),
|
|
264
|
+
})
|
|
205
265
|
if path.startswith('/api/download/'):
|
|
206
266
|
with _lock:
|
|
207
267
|
job = _jobs.get(path[len('/api/download/'):])
|
|
@@ -268,13 +328,14 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
268
328
|
out_path = body.get('out_path') or f'download_{content_hash[:16]}'
|
|
269
329
|
k = int(body.get('k') or 3)
|
|
270
330
|
use_lightning = bool(body.get('lightning'))
|
|
331
|
+
title = body.get('title')
|
|
271
332
|
|
|
272
333
|
job_id = uuid.uuid4().hex[:12]
|
|
273
334
|
with _lock:
|
|
274
335
|
_jobs[job_id] = {'status': 'running', 'idx': 0, 'n_chunks': None,
|
|
275
|
-
'content_hash': content_hash, 'path': None, 'error': None}
|
|
336
|
+
'content_hash': content_hash, 'path': None, 'title': title, 'error': None}
|
|
276
337
|
threading.Thread(target=_run_download_job,
|
|
277
|
-
args=(job_id, content_hash, relay_urls, out_path, k, use_lightning),
|
|
338
|
+
args=(job_id, content_hash, relay_urls, out_path, k, use_lightning, title),
|
|
278
339
|
daemon=True).start()
|
|
279
340
|
self._json({'job_id': job_id})
|
|
280
341
|
|
|
@@ -284,7 +345,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
284
345
|
return self._json({'error': 'content_hash required'}, status=400)
|
|
285
346
|
identity = _identity()
|
|
286
347
|
event = identity.sign_event('like', content_hash=content_hash)
|
|
287
|
-
|
|
348
|
+
result = node.post_event(body.get('relay') or DEFAULT_RELAY, event)
|
|
349
|
+
with _lock:
|
|
350
|
+
if content_hash not in _library['likes']:
|
|
351
|
+
_library['likes'].append(content_hash)
|
|
352
|
+
_save_library()
|
|
353
|
+
self._json({'result': result})
|
|
288
354
|
|
|
289
355
|
def _handle_subscribe(self, body):
|
|
290
356
|
target_pubkey = body.get('target_pubkey')
|
|
@@ -292,7 +358,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
292
358
|
return self._json({'error': 'target_pubkey required'}, status=400)
|
|
293
359
|
identity = _identity()
|
|
294
360
|
event = identity.sign_event('subscribe', target_pubkey=target_pubkey)
|
|
295
|
-
|
|
361
|
+
result = node.post_event(body.get('relay') or DEFAULT_RELAY, event)
|
|
362
|
+
with _lock:
|
|
363
|
+
if target_pubkey not in _library['subscriptions']:
|
|
364
|
+
_library['subscriptions'].append(target_pubkey)
|
|
365
|
+
_save_library()
|
|
366
|
+
self._json({'result': result})
|
|
296
367
|
|
|
297
368
|
def _handle_stream(self, job_id):
|
|
298
369
|
"""Serve an already-downloaded job's file with real HTTP range
|
|
@@ -398,6 +469,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
398
469
|
|
|
399
470
|
def run_web_ui(port=8080, bind_host='127.0.0.1', quiet=False):
|
|
400
471
|
global _lan_url
|
|
472
|
+
_load_library()
|
|
473
|
+
_rehydrate_jobs_from_library()
|
|
401
474
|
srv = ThreadingHTTPServer((bind_host, port), Handler)
|
|
402
475
|
|
|
403
476
|
reachable_host = _detect_lan_ip() if bind_host == '0.0.0.0' else bind_host
|
|
@@ -49,9 +49,11 @@ def run_make(target):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def build_parser():
|
|
52
|
+
import node
|
|
52
53
|
parser = argparse.ArgumentParser(
|
|
53
54
|
prog='weed',
|
|
54
|
-
description='
|
|
55
|
+
description=f'{node.weed_banner()} — real mechanisms behind the #all-pdx brainstorm.')
|
|
56
|
+
parser.add_argument('--version', action='version', version=node.weed_banner())
|
|
55
57
|
sub = parser.add_subparsers(dest='command', required=False)
|
|
56
58
|
|
|
57
59
|
sub.add_parser('shell', help='interactive shell with tab completion (also the default with no command)')
|
|
@@ -139,9 +141,6 @@ def cmd_host(args):
|
|
|
139
141
|
import node
|
|
140
142
|
identity = node.load_or_create_identity()
|
|
141
143
|
entries = node.load_manifest_entries(args.archive_dir, args.file)
|
|
142
|
-
if args.tunnel and len(entries) > 1:
|
|
143
|
-
sys.exit(f"{len(entries)} files in {args.archive_dir} but --tunnel only serves one at a "
|
|
144
|
-
f"time — pass --file NAME to pick one")
|
|
145
144
|
# fail fast, before announcing anything — a manifest entry with no
|
|
146
145
|
# matching chunk data would otherwise get announced to the relay and
|
|
147
146
|
# only fail later, in the background server thread
|
|
@@ -153,14 +152,19 @@ def cmd_host(args):
|
|
|
153
152
|
tunnel=args.tunnel)
|
|
154
153
|
print(f"announced {entry['name']} on {relay_url}: {result}")
|
|
155
154
|
if args.tunnel:
|
|
156
|
-
|
|
155
|
+
# REGISTER's token is the file's own content hash (see
|
|
156
|
+
# run_host_tunnel/connect_via_tunnel), so a whole tree just means
|
|
157
|
+
# one control connection per file, each registered under its own
|
|
158
|
+
# hash — no protocol change needed, CONNECT already looks a
|
|
159
|
+
# downloader's requested hash up the same way.
|
|
157
160
|
relay_host, relay_port, use_tls = node._parse_tunnel(args.tunnel)
|
|
158
161
|
archive_dir = os.path.expanduser(args.archive_dir)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
for entry in entries:
|
|
163
|
+
file_path = entry.get('last_path') or os.path.join(archive_dir, entry['name'])
|
|
164
|
+
threading.Thread(target=node.run_host_tunnel,
|
|
165
|
+
args=(relay_host, relay_port, entry['sha256'], entry,
|
|
166
|
+
all_leaves[entry['sha256']], file_path, args.price),
|
|
167
|
+
kwargs={'use_tls': use_tls}, daemon=True).start()
|
|
164
168
|
node.run_host_server(args.archive_dir, args.file, args.port, price=args.price)
|
|
165
169
|
|
|
166
170
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weed-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Censorship-resistant video PoC — discovery/hosting/download over signed relay events, a real Kademlia DHT, or a TLS-capable NAT-traversal tunnel
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: p2p,video,censorship-resistant,discovery,dht,kademlia,nat-traversal
|
|
@@ -13,6 +13,7 @@ Classifier: Topic :: Communications :: File Sharing
|
|
|
13
13
|
Classifier: Topic :: Internet
|
|
14
14
|
Requires-Python: >=3.11
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
16
17
|
Requires-Dist: btcvm>=1.3.7
|
|
17
18
|
Requires-Dist: cryptography>=41.0
|
|
18
19
|
Provides-Extra: dev
|
|
@@ -25,6 +26,7 @@ Provides-Extra: qr
|
|
|
25
26
|
Requires-Dist: qrcode>=7.4; extra == "qr"
|
|
26
27
|
Provides-Extra: viz
|
|
27
28
|
Requires-Dist: matplotlib>=3.8; extra == "viz"
|
|
29
|
+
Dynamic: license-file
|
|
28
30
|
|
|
29
31
|
# Censorship-resistant video platform — PoC notes
|
|
30
32
|
|
|
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
|