first commit
This commit is contained in:
commit
76add2a45a
15 changed files with 2599 additions and 0 deletions
99
.config_app/code/open_zip.py
Normal file
99
.config_app/code/open_zip.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import os
|
||||||
|
import zipfile
|
||||||
|
import hashlib
|
||||||
|
from io import BytesIO
|
||||||
|
from PyQt6.QtWidgets import QFileDialog, QMessageBox
|
||||||
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
|
public_key_path = os.path.join(".config_app","data","public_key.pem")
|
||||||
|
|
||||||
|
with open(public_key_path, "rb") as key_file:
|
||||||
|
public_key = serialization.load_pem_public_key(key_file.read())
|
||||||
|
|
||||||
|
def calcular_hash_zip_sin_firma(zip_data):
|
||||||
|
hash_sha256 = hashlib.sha256()
|
||||||
|
with zipfile.ZipFile(zip_data, 'r') as zipf:
|
||||||
|
for name in sorted(zipf.namelist()):
|
||||||
|
if name == "signature.sig":
|
||||||
|
continue
|
||||||
|
data = zipf.read(name)
|
||||||
|
hash_sha256.update(data)
|
||||||
|
return hash_sha256.digest()
|
||||||
|
|
||||||
|
def extraer_zip(zip_data, destino):
|
||||||
|
os.makedirs(destino, exist_ok=True)
|
||||||
|
with zipfile.ZipFile(zip_data) as zip_file:
|
||||||
|
for member in zip_file.namelist():
|
||||||
|
if member == "signature.sig":
|
||||||
|
continue
|
||||||
|
zip_file.extract(member, destino)
|
||||||
|
print(f"Files extracted to: {destino}")
|
||||||
|
|
||||||
|
def open_zip():
|
||||||
|
file_dialog = QFileDialog()
|
||||||
|
file_dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
|
||||||
|
file_dialog.setNameFilter("ZIP files (*.zip)")
|
||||||
|
file_dialog.setViewMode(QFileDialog.ViewMode.List)
|
||||||
|
|
||||||
|
if file_dialog.exec():
|
||||||
|
selected_files = file_dialog.selectedFiles()
|
||||||
|
if selected_files:
|
||||||
|
zip_file_path = selected_files[0]
|
||||||
|
|
||||||
|
if not zip_file_path.lower().endswith(".zip"):
|
||||||
|
QMessageBox.warning(None, "Invalid File", "Please select a valid ZIP file.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(zip_file_path, 'rb') as f:
|
||||||
|
zip_data = BytesIO(f.read())
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_data) as zip_file:
|
||||||
|
file_list = zip_file.namelist()
|
||||||
|
warning = False
|
||||||
|
|
||||||
|
if "signature.sig" not in file_list:
|
||||||
|
print("Warning: 'signature.sig' not found in the ZIP.")
|
||||||
|
warning = True
|
||||||
|
else:
|
||||||
|
signature = zip_file.read("signature.sig")
|
||||||
|
file_hash = calcular_hash_zip_sin_firma(zip_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
public_key.verify(
|
||||||
|
signature,
|
||||||
|
file_hash,
|
||||||
|
padding.PKCS1v15(),
|
||||||
|
hashes.SHA256()
|
||||||
|
)
|
||||||
|
print("ZIP is valid and correctly signed.")
|
||||||
|
destination = os.path.expanduser(".config_app/templates")
|
||||||
|
extraer_zip(zip_data, destination)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print("ZIP is altered or invalid:", e)
|
||||||
|
warning = True
|
||||||
|
|
||||||
|
if warning:
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
None,
|
||||||
|
"Signature Warning",
|
||||||
|
"This file may be tampered or unofficial.\nDo you want to continue at your own risk?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
|
destination = os.path.expanduser(".config_app/templates")
|
||||||
|
extraer_zip(zip_data, destination)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing the ZIP file → {e}")
|
||||||
|
QMessageBox.critical(None, "Error", "An error occurred while processing the ZIP file.")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
14
.config_app/data/public_key.pem
Normal file
14
.config_app/data/public_key.pem
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoWIJ82SIaUETU8UraRW2
|
||||||
|
yD3UhHoud1Y0FZyPIUG2YICCAXLt1peg7NEaoQ/Hi29iJpini/9W92Gvf0ofLaQr
|
||||||
|
yNP1klQI51rG6sr+JKn4iZYsyErFWNIy0KhllOdBrpS/wJCWN+OPRIfuBy0y9+Wf
|
||||||
|
ZDKz5J9cLlKhsC2upPrf7rWEoknu1VeIlBuRG1mEsnmI+22FQ2EcKlzJlJJinXsy
|
||||||
|
ofUK92ZrH3WQLdRLplh6M0PsEphiRknW7AIIuU0eDJLJ/ZufRJl+Ru4L3EXW8eX3
|
||||||
|
fn4Im8R9bPj66jOiO47eh1Eu0LUnyTvcQV0te46uNzzPZv5q8qCAc1xE63JGHapQ
|
||||||
|
Jy5n2ud1XzR5Lzif+T/mAJNYHGRGa2g3zY10caaKHTOtpVxlDX23tjBbmC+TigwV
|
||||||
|
jSheNLbe/VdRTu9e+pI4KdbPtYVsqxdGTk+h5O/2eP5y5A6kW2n9rOJ4VXwGb9xY
|
||||||
|
hn5ZKNrfLN1UnL1T1l1waKBLtYdIk47ia4xsR/1A/cffvoLwYVtf0nfW2pEmbA/i
|
||||||
|
BTZW3+4UdnDpFmRc4fO/edTh7iMPhX6mJBMx12h3OVmyRRds91xFkDYpPuBJ8v9f
|
||||||
|
zIFAi00cxWWTDbDGXa+eqYFNNQ+g2/xz8QeY/5+6YKsx6C8XyemNBQza+JqFNwda
|
||||||
|
lKNuiudtwpJUUprKfup/jMUCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
31
.config_app/images/bastyon.svg
Executable file
31
.config_app/images/bastyon.svg
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.21 96.47">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fdfdfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1, .cls-2, .cls-3 {
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #b7b6b6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-2" d="M20.44,85.98c-2.49,3.41-5.62,6.23-8.55,9.25-1.18,1.22-1.83,1.33-3.11.08-2.86-2.79-5.43-5.88-8.77-8.19,0-.37,0-.74,0-1.11,1.49-1.43,2.99-2.85,4.47-4.29,4.57-4.44,4.84-6.42,2-12.04C1.68,60.18-.11,50.25,2.12,39.78c5,.01,10,.03,15.01.05-.45,3.83-1.27,7.57-.87,11.52.62,5.96,3.21,11.1,5.98,16.24,2.1,6.39,1.08,12.48-1.81,18.4Z"/>
|
||||||
|
<path class="cls-2" d="M0,9.72C3.11,6.75,6.27,3.84,9.3.8c1.03-1.04,1.66-1.09,2.64,0,1.38,1.52,2.91,2.92,4.4,4.34,3.45,3.29,6.27,3.72,10.51,1.5C35.91,1.89,45.58.4,55.72,1.36c.84.08,1.65.37,2.48.56-.02,4.86-.03,9.71-.05,14.57-5.45-.84-10.89-1.19-16.34.04-4.57,1.03-8.5,3.47-12.63,5.47-6.35,1.05-12.51.73-18.31-2.33C6.78,17.04,3.9,13.11,0,10.28v-.56Z"/>
|
||||||
|
<path class="cls-1" d="M0,10.28c3.9,2.83,6.78,6.77,10.87,9.39-4.4,6.09-7.78,12.6-8.75,20.11-2.24,10.48-.45,20.41,4.35,29.9,2.84,5.62,2.56,7.61-2,12.04C2.99,83.16,1.49,84.58,0,86.01c0-25.24,0-50.49,0-75.73Z"/>
|
||||||
|
<path class="cls-2" d="M88.46,76.71c3.56,2.5,6.49,5.66,9.64,8.61.96.9,1.11,1.47.07,2.43-2.81,2.62-5.58,5.3-8.25,8.06-1.05,1.08-1.66.73-2.5-.15-1.5-1.55-3.05-3.05-4.62-4.53-3.18-2.99-6.13-3.41-10.07-1.38-9.53,4.92-19.62,6.64-30.28,5.19-.84-.11-1.65-.39-2.47-.59,0-2.12-.12-4.25,0-6.36.15-2.73-.64-5.53.61-8.18,10.35,2.42,19.91.54,28.82-4.98,6.61-1.79,12.95-1.11,19.07,1.88Z"/>
|
||||||
|
<path class="cls-2" d="M78.91,10.45c2.6-3.32,5.67-6.22,8.65-9.2,1.1-1.1,1.79-1.25,2.94-.05,2.53,2.64,5.17,5.19,7.86,7.68,1.08,1,1.16,1.67.02,2.67-1.56,1.36-2.99,2.86-4.46,4.31-3.31,3.28-3.75,5.89-1.54,10.02,5.15,9.6,6.89,19.74,5.15,30.44-.07.44-.3.86-.46,1.29-5.01-.03-10.03-.07-15.04-.1,2.47-10.09,1.03-19.53-5.03-28.17-1.59-6.51-1.17-12.82,1.9-18.88Z"/>
|
||||||
|
<path class="cls-3" d="M78.91,10.45c-3.07,6.06-3.49,12.38-1.9,18.88-4.87-6.26-11.2-10.48-18.86-12.85.02-4.86.03-9.71.05-14.57,7.71.97,14.48,4.13,20.71,8.53Z"/>
|
||||||
|
<path class="cls-3" d="M82.04,57.51c5.01.03,10.03.07,15.04.1-1.08,7.14-4.23,13.39-8.62,19.1-6.11-2.98-12.46-3.67-19.07-1.88,5.93-4.58,10.15-10.36,12.65-17.32Z"/>
|
||||||
|
<path class="cls-3" d="M40.58,79.81c-1.25,2.65-.45,5.45-.61,8.18-.12,2.11,0,4.24,0,6.36-7.29-1.07-13.64-4.21-19.53-8.37,2.89-5.92,3.91-12.01,1.81-18.4,5.16,5.44,10.9,10.04,18.33,12.23Z"/>
|
||||||
|
<path class="cls-3" d="M29.18,22c-.22,1-1.14,1.44-1.82,2.02-5.02,4.28-8.17,9.71-10.23,15.8-5-.01-10-.03-15.01-.05.97-7.51,4.35-14.02,8.75-20.11,5.8,3.06,11.96,3.38,18.31,2.33Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
22
.config_app/images/nostr.svg
Normal file
22
.config_app/images/nostr.svg
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #f6f6f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1, .cls-2 {
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<g id="nost">
|
||||||
|
<path class="cls-2" d="M99.05,4.99l.04-.4c-4.03-.73-7.56-2.4-11.1-4.12-2.37-1.14-4.81-.11-5.84,2.15-1.82,3.99-1.05,7.66,2.15,10.93,1.9,1.94,4.32,3.36,6.49,5.03,3.13,2.42,4,6.04,2.28,9.31-1.56,2.97-5.19,4.52-9.33,3.93-.99-.14-2.05-.42-2.96-.1-2.22.79-3.96.08-5.71-1.01-7.11-4.44-14.99-5.02-23.23-3.39-5.57,1.11-10.59,3.45-15.62,5.72-2.89,1.31-5.87,2.28-9.13,2.57-2.44.21-4.88.09-7.32.12-.85.01-1.66.11-1.37,1.14.31,1.13-.42.83-1.09.68-6.4-1.42-12.8,1.09-15.87,6.26-.62,1.04-1.25,2.08-1.4,3.27-.15,1.21.2,1.46,1.45.98.87-.33,1.71-.71,2.58-1.03.33-.12.74-.36,1.06,0,.23.25.05.57-.06.84-.48,1.15-.97,2.3-.9,3.55.04.65.12,1.33.94,1.58.77.23,1.26-.29,1.68-.73.8-.83,1.72-1.53,2.65-2.22.31-.23.65-.5,1.02-.27.41.25.1.64-.02.94-.34.79-.53,1.6-.53,2.43,0,1.64.65,2.04,2.14,1.11,3.2-1.99,6.9-2.59,10.61-3.18.74-.12,1.46-.04,2.08.34,4.56,2.81,9.78,4.1,15.11,5.14,1.29.25,1.66.62.74,1.6-3.08,3.24-6.42,6.22-10.84,8.04-1.74.71-2.84,1.83-3.25,3.47-.16.67-.34,1.33-.8,1.91-3.67,4.59-7.34,9.19-10.99,13.8-.81,1.02-1.8,1.66-3.24,1.83-1.53.18-2.85.81-3.85,1.87-.56.6-1.2,1.24-.91,2.09.35,1.01,1.39.09,2.02.46.03.61-.57.92-.85,1.37-1.1,1.83-2.18,3.66-1.53,5.84.14.48.28,1.04.88,1.15.75.13.81-.55,1.02-.95.68-1.29,1.45-2.54,2.54-3.6,1.37-1.34,3.04-2.51,4.12-4.01,4.39-6.07,9.4-11.73,14.47-17.36.57-.63,1.18-1.1,2.05-1.41,1.72-.62,2.97-1.71,3.51-3.31.24-.7.7-1.19,1.34-1.61,2.67-1.75,5.3-3.55,8-5.26,1.87-1.19,4.13-1.52,6.3-2.18.11.76-.36,1.13-.63,1.6-1.1,1.91-2.29,3.84-2.45,6.02-.21,2.77,2.02,3.97,4.78,2.67.68-.32,1.3-.8,2.02-1,5.75-1.63,11.48-3.33,17.32-4.71,1.29-.31,2.18-.09,2.24,1.32.02.47.21.97.47,1.38.32.52.83,1.05,1.57.89.9-.2.42-.86.34-1.35-.06-.35-.16-.75.28-.9.44-.15.67.27.9.51,1.02,1.06,2.19,1.94,3.58,2.59.54.25,1.22.53,1.7.1.52-.46.07-1.02-.24-1.46-1.24-1.74-2.5-3.46-3.76-5.19-1.86-2.54-3.27-3.08-6.59-2.22-4.63,1.19-9.22,2.49-13.84,3.73-1.56.42-3.12.83-5.13,1.36,1.18-1.69,1.94-3.08,3.6-3.87,1.07-.51,2.17-1.04,2.74-2.06.54-.97,1.4-1.23,2.57-1.22,3.56.02,7.09-.16,10.56-.97,7.42-1.72,11.44-5.99,11.14-12.39-.11-2.28.8-3.46,2.95-4.47,3.66-1.72,6.76-4.04,9.12-7.13,4.93-6.44,5.14-14.48-3.5-19.83-1.35-.84-2.76-1.66-3.82-2.81-1.5-1.62-1.08-2.74,1.17-3.29,2.56-.63,5.18-.38,7.78-.5.52-.02,1.31.18,1.48-.33.19-.58-.59-.8-1.08-1.07.4-.22.8-.44,1.19-.67Z"/>
|
||||||
|
<path class="cls-1" d="M99.81,4.99c-.4.22-.8.44-1.19.67.49.27,1.26.5,1.08,1.07-.17.52-.96.31-1.48.33-2.6.12-5.22-.13-7.78.5-2.25.55-2.67,1.67-1.17,3.29,1.06,1.15,2.47,1.97,3.82,2.81,8.64,5.35,8.42,13.39,3.5,19.83-2.36,3.08-5.46,5.41-9.12,7.13-2.14,1.01-3.06,2.19-2.95,4.47.3,6.4-3.72,10.67-11.14,12.39-3.47.8-7.01.99-10.56.97-1.18,0-2.03.26-2.57,1.22-.57,1.02-1.68,1.55-2.74,2.06-1.67.79-2.43,2.19-3.6,3.87,2-.53,3.57-.94,5.13-1.36,4.61-1.25,9.21-2.54,13.84-3.73,3.32-.85,4.72-.32,6.59,2.22,1.26,1.72,2.53,3.45,3.76,5.19.31.44.76,1.01.24,1.46-.48.42-1.16.15-1.7-.1-1.39-.65-2.56-1.53-3.58-2.59-.23-.24-.46-.66-.9-.51-.44.15-.34.55-.28.9.08.49.55,1.15-.34,1.35-.74.17-1.25-.37-1.57-.89-.26-.42-.45-.92-.47-1.38-.06-1.41-.95-1.63-2.24-1.32-5.84,1.39-11.57,3.08-17.32,4.71-.72.21-1.34.68-2.02,1-2.76,1.3-5,.1-4.78-2.67.17-2.18,1.35-4.1,2.45-6.02.27-.46.74-.84.63-1.6-2.17.66-4.43.99-6.3,2.18-2.7,1.72-5.33,3.51-8,5.26-.64.42-1.1.91-1.34,1.61-.54,1.6-1.8,2.69-3.51,3.31-.86.31-1.48.78-2.05,1.41-5.07,5.63-10.08,11.29-14.47,17.36-1.08,1.5-2.75,2.67-4.12,4.01-1.09,1.06-1.86,2.3-2.54,3.6-.21.41-.27,1.09-1.02.95-.6-.11-.74-.67-.88-1.15-.66-2.19.42-4.01,1.53-5.84.27-.45.87-.76.85-1.37-.63-.37-1.67.55-2.02-.46-.29-.85.34-1.49.91-2.09,1-1.06,2.31-1.69,3.85-1.87,1.44-.17,2.43-.8,3.24-1.83,3.64-4.61,7.32-9.2,10.99-13.8.46-.58.64-1.24.8-1.91.4-1.65,1.51-2.76,3.25-3.47,4.42-1.82,7.76-4.8,10.84-8.04.93-.97.55-1.34-.74-1.6-5.32-1.04-10.55-2.33-15.11-5.14-.62-.38-1.34-.45-2.08-.34-3.72.59-7.41,1.19-10.61,3.18-1.49.93-2.15.53-2.14-1.11,0-.84.19-1.65.53-2.43.13-.3.43-.68.02-.94-.37-.23-.71.04-1.02.27-.93.69-1.85,1.39-2.65,2.22-.42.43-.91.96-1.68.73-.82-.25-.9-.93-.94-1.58-.07-1.25.41-2.4.9-3.55.11-.27.29-.58.06-.84-.32-.36-.72-.11-1.06,0-.87.32-1.71.7-2.58,1.03-1.25.47-1.6.23-1.45-.98.15-1.2.78-2.24,1.4-3.27,3.07-5.17,9.46-7.69,15.87-6.26.66.15,1.4.44,1.09-.68-.29-1.03.52-1.13,1.37-1.14,2.44-.03,4.88.09,7.32-.12,3.26-.28,6.24-1.26,9.13-2.57,5.03-2.28,10.05-4.62,15.62-5.72,8.24-1.63,16.11-1.05,23.23,3.39,1.75,1.09,3.5,1.8,5.71,1.01.91-.32,1.97-.04,2.96.1,4.14.59,7.77-.96,9.33-3.93,1.72-3.27.85-6.89-2.28-9.31-2.17-1.67-4.59-3.09-6.49-5.03-3.2-3.27-3.97-6.93-2.15-10.93,1.03-2.26,3.47-3.29,5.84-2.15,3.55,1.71,7.07,3.39,11.1,4.12l-.04.4Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
13
.config_app/images/session.svg
Normal file
13
.config_app/images/session.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-1" d="M71.27,100H22.62c-1.77,0-3.33-.14-4.78-.42C7.74,97.6.26,89.45.05,80.2c-.21-9.6,7.22-18.09,17.69-20.19,1.45-.3,3.06-.44,4.92-.44h14.36l-21.28-10.63c-5.87-2.93-10.56-7.45-13.21-12.7C.62,32.44-.21,28.5.05,24.52c.31-4.9,2.25-9.65,5.6-13.76C11.21,3.97,20.3-.09,29.98-.09h47.23c9.13,0,17.33,4.74,20.87,12.07,1.18,2.45,1.82,5.06,1.88,7.74.06,2.78-.49,5.48-1.64,8.03-3.37,7.54-11.87,12.61-21.15,12.61h-14.23l20.02,10.05c6.02,3.02,10.75,7.39,13.67,12.64,4.26,7.67,4.5,16.4.65,23.36-1.91,3.45-4.75,6.38-8.44,8.72-5.03,3.19-11.1,4.88-17.57,4.88ZM22.65,65.62c-1.36,0-2.49.1-3.46.29-7.37,1.48-12.61,7.43-12.46,14.16.15,6.48,5.41,12.19,12.52,13.59.97.19,2.08.28,3.37.28h48.65c5.09,0,9.84-1.31,13.73-3.78,2.77-1.76,4.89-3.94,6.29-6.48,2.91-5.26,2.67-11.96-.65-17.92-2.31-4.16-6.09-7.64-10.93-10.07l-31.34-15.73c-1.33-.67-2-2.07-1.62-3.41.38-1.34,1.71-2.27,3.24-2.27h27.16c6.59,0,12.6-3.55,14.96-8.83.8-1.79,1.19-3.69,1.15-5.63-.05-1.87-.49-3.71-1.32-5.43-2.47-5.13-8.26-8.44-14.74-8.44H29.98c-7.57,0-14.65,3.14-18.96,8.4-2.58,3.15-4.06,6.78-4.3,10.5-.19,2.99.44,5.97,1.89,8.84,2.06,4.08,5.74,7.61,10.35,9.92l32.64,16.31c1.34.67,2,2.07,1.62,3.41-.38,1.34-1.71,2.27-3.24,2.27h-27.35Z"/>
|
||||||
|
<path class="cls-1" d="M50.01,65.62c-1.85,0-3.34-1.36-3.34-3.03v-25.26c0-1.67,1.5-3.03,3.34-3.03s3.34,1.36,3.34,3.03v25.26c0,1.67-1.5,3.03-3.34,3.03Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
12
.config_app/images/signal.svg
Normal file
12
.config_app/images/signal.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-1" d="M50,9.37c-20.71,0-37.5,18.21-37.48,40.64,0,7.65,1.99,15.13,5.75,21.6l-3.61,16.67,15.39-3.91c17.53,11.95,40.68,6.26,51.72-12.73,11.04-18.98,5.78-44.06-11.75-56.02-5.99-4.09-12.93-6.26-20.02-6.25h0ZM28.58,88.97l-6.2,1.56.98,4.57,4.56-1.15c3.46,2.04,7.15,3.59,10.97,4.61l1.04-4.55c-3.98-1.07-7.8-2.78-11.32-5.07l-.03.03h0ZM8.39,78.82l4.21,1.06,1.44-6.71c-2.1-3.82-3.67-7.97-4.65-12.3l-4.2,1.12c.94,4.14,2.37,8.14,4.25,11.88l-1.06,4.95h0ZM18.55,91.48l-8.93,2.26,2.08-9.68-4.21-1.07-2.08,9.68c-.55,2.52.9,5.05,3.22,5.63.65.16,1.32.16,1.98,0l8.93-2.22-.98-4.61h0ZM43.74,5.19c4.15-.68,8.38-.68,12.53,0l.65-4.63c-4.58-.75-9.25-.75-13.83,0l.65,4.63h0ZM89.53,24.15l-3.7,2.41c2.17,3.9,3.79,8.13,4.79,12.55l4.2-1.12c-1.11-4.87-2.89-9.54-5.29-13.83h0ZM16.33,23.13c2.49-3.66,5.48-6.9,8.87-9.6l-2.6-3.78c-3.72,2.97-7.01,6.53-9.75,10.56l3.49,2.82h0ZM74.81,13.52c3.38,2.7,6.37,5.94,8.87,9.6l3.49-2.82c-2.74-4.03-6.03-7.59-9.75-10.56l-2.6,3.78h0ZM83.68,76.87c-2.49,3.66-5.48,6.89-8.87,9.59l2.57,3.78c3.73-2.97,7.02-6.53,9.78-10.56l-3.49-2.81h0ZM56.27,94.8c-4.15.68-8.38.68-12.53,0l-.65,4.64c4.58.75,9.25.75,13.83,0l-.65-4.64h0ZM94.81,62l-4.2-1.12c-1.01,4.43-2.62,8.66-4.79,12.56l3.7,2.42c2.4-4.3,4.18-8.98,5.29-13.86h0ZM91.84,50c0,2.27-.16,4.54-.47,6.79l4.28.71c.69-4.96.69-10.02,0-14.98l-4.28.7c.32,2.25.47,4.52.47,6.79h0ZM73.87,92.81l-2.22-4.01c-3.6,2.35-7.51,4.1-11.59,5.19l1.04,4.55c4.5-1.2,8.81-3.13,12.77-5.73h0ZM8.17,50c0-2.27.16-4.54.47-6.79l-4.28-.7c-.69,4.96-.69,10.02,0,14.98l4.28-.71c-.31-2.25-.47-4.52-.47-6.79h0ZM10.48,24.15c-2.4,4.3-4.18,8.97-5.29,13.84l4.2,1.12c1.01-4.43,2.62-8.66,4.79-12.56l-3.7-2.4h0ZM61.08,1.46l-1.04,4.55c4.09,1.09,7.99,2.84,11.6,5.19l2.23-4.01c-3.97-2.6-8.29-4.52-12.8-5.73h0ZM38.92,1.46l1.04,4.55c-4.09,1.09-7.99,2.84-11.6,5.19l-2.22-4.01c3.98-2.6,8.29-4.52,12.79-5.73h0Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2 KiB |
85
.config_app/layout.css
Normal file
85
.config_app/layout.css
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*General style*/
|
||||||
|
* {
|
||||||
|
color: rgb(29, 29, 29);
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
QMainWindow{
|
||||||
|
background: rgb(235, 235, 235);
|
||||||
|
|
||||||
|
}
|
||||||
|
#progress_bar {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
#progress_bar::chunk {
|
||||||
|
background-color: #808080;
|
||||||
|
border-radius: 20%;
|
||||||
|
border: 2px solid rgb(134, 134, 134);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Label*/
|
||||||
|
QLabel{
|
||||||
|
color: rgb(29, 29, 29);
|
||||||
|
}
|
||||||
|
/*Label*/
|
||||||
|
QPushButton{
|
||||||
|
background-color: rgb(231, 231, 231);
|
||||||
|
color: rgb(22, 22, 22);
|
||||||
|
border: 1px solid rgb(4, 122, 10);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
QPushButton:hover{
|
||||||
|
background-color: rgb(82, 82, 82);
|
||||||
|
color: rgb(233, 233, 233);
|
||||||
|
border: 1px solid rgb(90, 207, 96);
|
||||||
|
|
||||||
|
}
|
||||||
|
QPlainTextEdit{
|
||||||
|
background-color: rgb(231, 231, 231);
|
||||||
|
color: rgb(22, 22, 22);
|
||||||
|
border: 1px solid rgb(4, 122, 10);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
}
|
||||||
|
QLineEdit{
|
||||||
|
background-color: rgb(231, 231, 231);
|
||||||
|
color: rgb(22, 22, 22);
|
||||||
|
border: 1px solid rgb(4, 122, 10);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
}
|
||||||
|
/*BasePage························································*/
|
||||||
|
#widget_up,#widget_center,#widget_down{
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
border: 1px solid rgba(136, 135, 135, 0.1); /* Borde sutil para dar profundidad */
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
#label_title{
|
||||||
|
color: rgb(19, 143, 2);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
#label_balance{
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*PageCreateWebsite························································*/
|
||||||
|
#line_project,
|
||||||
|
#line_website_name,
|
||||||
|
#plain_website_description,
|
||||||
|
#plain_website_seo {
|
||||||
|
background-color: rgb(231, 231, 231);
|
||||||
|
color: rgb(22, 22, 22);
|
||||||
|
border: 1px solid rgb(4, 122, 10);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
#footer_nostr,
|
||||||
|
#footer_session,
|
||||||
|
#footer_bastyon,
|
||||||
|
#footer_signal {
|
||||||
|
background-color: rgb(231, 231, 231);
|
||||||
|
color: rgb(22, 22, 22);
|
||||||
|
border: 1px solid rgb(4, 122, 10);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*PageEditWebsite························································*/
|
BIN
.config_app/urbanist.ttf
Normal file
BIN
.config_app/urbanist.ttf
Normal file
Binary file not shown.
BIN
appimagetool-x86_64.AppImage
Executable file
BIN
appimagetool-x86_64.AppImage
Executable file
Binary file not shown.
BIN
arweb.png
Normal file
BIN
arweb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
181
readme-español.md
Normal file
181
readme-español.md
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
---
|
||||||
|
title: "ArWeb "
|
||||||
|
description: "Lgado eterno de sitios webs. Una web sin dominio, universal y resistente a la censura, bloqueos y ataques"
|
||||||
|
image: ""
|
||||||
|
categories:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
tags:
|
||||||
|
- ""
|
||||||
|
date: 2025-03-03
|
||||||
|
authors:
|
||||||
|
- "Zenaku"
|
||||||
|
---
|
||||||
|
**ArWeb** es un creador de páginas web estáticas para la red **Arweave**, también conocido como **Eternal Legacy (arWeb-EternalLegacy)**.
|
||||||
|
|
||||||
|
El código es libre, siéntase a gusto de editarlo. Desarrollado en Ubuntu.
|
||||||
|
|
||||||
|
**Solo se ha probado en Linux**
|
||||||
|
**Teóricamente funciona en todos los sistemas operativos**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Índice
|
||||||
|
|
||||||
|
- [Instalación manual](#instalación-manual)
|
||||||
|
- [Instalación automática (recomendada)](#instalación-automática-recomendada)
|
||||||
|
- [Formas de uso](#formas-de-uso)
|
||||||
|
- [Descargo de responsabilidades](#descargo-de-responsabilidades)
|
||||||
|
- [Preguntas frecuentes](#preguntas-frecuentes)
|
||||||
|
- [Contacto y créditos](#contacto-y-créditos)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalación manual
|
||||||
|
|
||||||
|
### Requisitos
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Hugo
|
||||||
|
- ArDrive
|
||||||
|
|
||||||
|
### Pasos
|
||||||
|
|
||||||
|
1. Descargue y extraiga todos los archivos del repositorio:
|
||||||
|
`.config_app`, `main.py`, `worker.py`, `requirements.txt`, `*.AppImage`
|
||||||
|
|
||||||
|
2. Cree un entorno virtual dentro de la carpeta extraída:
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
|
3. Active el entorno virtual
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
4. Instale los requerimientos
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
5. Ejecute
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalación automática (recomendada)
|
||||||
|
|
||||||
|
### Requisitos
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Hugo (opcional)
|
||||||
|
- ArDrive
|
||||||
|
|
||||||
|
### Pasos
|
||||||
|
|
||||||
|
Esta instalación es la más sencilla y práctica. Hace lo mismo que la instalación manual, pero con un solo clic:
|
||||||
|
|
||||||
|
1. Descargue el archivo .AppImage
|
||||||
|
|
||||||
|
2. Ejecútelo. Se instalará una carpeta llamada ArWeb en su directorio Documentos y se lanzará la aplicación automáticamente.
|
||||||
|
|
||||||
|
**Para futuras ejecuciones, no volverá a crear carpetas, solo ejecutará la app directamente.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Formas de uso
|
||||||
|
|
||||||
|
### Requisitos iniciales
|
||||||
|
|
||||||
|
- Una wallet Arweave (puede crear una nueva o importar una existente).
|
||||||
|
|
||||||
|
### Flujo de trabajo
|
||||||
|
|
||||||
|
1. Seleccione si desea:
|
||||||
|
- Editar una web ya creada
|
||||||
|
- Crear una desde cero
|
||||||
|
|
||||||
|
2. Seleccione o importe una plantilla
|
||||||
|
*(Las plantillas están en formato `.zip` y pueden descargarse desde la web oficial)*
|
||||||
|
|
||||||
|
3. Complete los campos del formulario:
|
||||||
|
- **Project name**: nombre del proyecto (carpeta raíz)
|
||||||
|
- **Website name**: nombre del sitio web
|
||||||
|
- **Web description**: descripción visible en motores de búsqueda
|
||||||
|
- **SEO keywords**: palabras clave separadas por comas (para posicionamiento SEO)
|
||||||
|
- **Favicon**: imagen que aparece en las pestañas del navegador
|
||||||
|
|
||||||
|
4. La columna derecha incluye campos personalizados según la plantilla. Rellene todos los campos requeridos.
|
||||||
|
|
||||||
|
5. Después de completar el formulario podrá:
|
||||||
|
- Ver el sitio en su navegador
|
||||||
|
- Abrir la carpeta del proyecto
|
||||||
|
- Publicar en Arweave
|
||||||
|
|
||||||
|
|
||||||
|
### Para editar su sitio web:
|
||||||
|
|
||||||
|
- Presione **Edit website**
|
||||||
|
- Seleccione el nombre del proyecto en la esquina superior derecha
|
||||||
|
|
||||||
|
Podrá:
|
||||||
|
- Abrir la ubicación local del sitio
|
||||||
|
- Ver su manifiesto
|
||||||
|
- Agregar contenido nuevo
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Descargo de responsabilidades
|
||||||
|
|
||||||
|
- **ArWeb no recolecta ningún dato personal.**
|
||||||
|
El código está disponible públicamente para su inspección.
|
||||||
|
|
||||||
|
- **Las plantillas `.zip` están firmadas digitalmente.**
|
||||||
|
Si detecta una firma inválida, desconfíe del archivo o del autor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preguntas frecuentes
|
||||||
|
|
||||||
|
### ¿Dónde se guarda mi sitio web localmente?
|
||||||
|
Todos los proyectos se almacenan en:
|
||||||
|
|
||||||
|
```
|
||||||
|
.config_app/profiles/default/
|
||||||
|
```
|
||||||
|
|
||||||
|
`.config_app` es una carpeta oculta.
|
||||||
|
|
||||||
|
|
||||||
|
### ¿Qué necesito para publicar en Arweave?
|
||||||
|
Necesitarás tokens **AR**.
|
||||||
|
Con aproximadamente **$1–$2 USD** es suficiente para publicar.
|
||||||
|
|
||||||
|
|
||||||
|
### ¿Puedo actualizar un sitio después de subirlo?
|
||||||
|
Sí.
|
||||||
|
ArWeb utiliza un sistema de **criptografía local** para:
|
||||||
|
|
||||||
|
- Detectar qué archivos ya han sido subidos.
|
||||||
|
- Subir solo los nuevos.
|
||||||
|
- Actualizar el manifiesto, preservando metadatos anteriores.
|
||||||
|
- Evitar gastos innecesarios.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contacto y créditos
|
||||||
|
|
||||||
|
- **Autor principal**: Zenaku
|
||||||
|
Direccion monero
|
||||||
|
```bash
|
||||||
|
41kbbxc2VYtHmc9jNAnRoSTqxCAfXz1XdR1WfVWQNKsMGFL8MWcsxTQSoNUmiDNPnNNh1FkKKSjZn2uAXHTP8Jhv1GeGfwr
|
||||||
|
|
||||||
|
|
||||||
|
- **Colaborador**: SimplifiedPrivacy
|
||||||
|
Direccion monero
|
||||||
|
```bash
|
||||||
|
xx
|
||||||
|
|
||||||
|
Apoya este proyecto y la libertad digital con una donación
|
||||||
|
|
||||||
|
|
||||||
|
¡Gracias por usar **ArWeb**!
|
190
readme.md
Normal file
190
readme.md
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
---
|
||||||
|
title: "ArWeb"
|
||||||
|
description: "Eternal legacy of websites. A domain-less, universal web resistant to censorship, blocks, and attacks."
|
||||||
|
image: ""
|
||||||
|
categories:
|
||||||
|
- ""
|
||||||
|
- ""
|
||||||
|
tags:
|
||||||
|
- ""
|
||||||
|
date: 2025-03-03
|
||||||
|
authors:
|
||||||
|
- "Zenaku"
|
||||||
|
---
|
||||||
|
**ArWeb** is a static website builder for the **Arweave** network, also known as **Eternal Legacy (arWeb-EternalLegacy)**.
|
||||||
|
|
||||||
|
The code is open source — feel free to edit it. Developed on Ubuntu.
|
||||||
|
|
||||||
|
**Only tested on Linux**
|
||||||
|
**Theoretically works on all operating systems**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
- [Manual Installation](#manual-installation)
|
||||||
|
- [Automatic Installation (Recommended)](#automatic-installation-recommended)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Disclaimer](#disclaimer)
|
||||||
|
- [Frequently Asked Questions](#frequently-asked-questions)
|
||||||
|
- [Contact and Credits](#contact-and-credits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Installation
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Hugo
|
||||||
|
- ArDrive
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
1. Download and extract all repository files:
|
||||||
|
`.config_app`, `main.py`, `worker.py`, `requirements.txt`, `*.AppImage`
|
||||||
|
|
||||||
|
2. Create a virtual environment inside the extracted folder:
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
|
3. Activate the virtual environment:
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
4. Install the requirements:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
5. Run:
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automatic Installation (Recommended)
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Hugo (opcional)
|
||||||
|
- ArDrive
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
|
||||||
|
This is the easiest and most practical method. It does the same as the manual installation but with a single click:
|
||||||
|
|
||||||
|
1. Download the .AppImage file.
|
||||||
|
|
||||||
|
2. Run it. A folder called ArWeb will be created in your Documents directory and the app will launch automatically.
|
||||||
|
|
||||||
|
**For future executions, it will not create new folders — it will just launch the app directly.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Initial Requirements
|
||||||
|
|
||||||
|
- An Arweave wallet (you can create a new one or import an existing one).
|
||||||
|
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. Choose whether to:
|
||||||
|
- Edit an existing website
|
||||||
|
- Create a new one from scratch
|
||||||
|
|
||||||
|
2. Select or import a template
|
||||||
|
*(Templates are in .zip format and can be downloaded from the official website)*
|
||||||
|
|
||||||
|
|
||||||
|
3. Fill in the form fields:
|
||||||
|
- **Project name**: name of the project (root folder)
|
||||||
|
- **Website name**: name of the site
|
||||||
|
- **Web description**: visible in search engines
|
||||||
|
- **SEO keywords**: comma-separated keywords for SEO
|
||||||
|
- **Favicon**: image shown in browser tabs
|
||||||
|
|
||||||
|
4. The right-hand column includes custom fields based on the selected template. Fill in all required fields.
|
||||||
|
5. After completing the form, you can:
|
||||||
|
- View the site in your browser
|
||||||
|
|
||||||
|
- View the site in your browser
|
||||||
|
|
||||||
|
- Publish to Arweave
|
||||||
|
|
||||||
|
### To edit your website:
|
||||||
|
|
||||||
|
- Click **Edit website**
|
||||||
|
- Select the project name in the top right corner
|
||||||
|
|
||||||
|
|
||||||
|
You can then:
|
||||||
|
- Open the local site folder
|
||||||
|
- View the manifest
|
||||||
|
- Add new content
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
- **ArWeb does not collect any personal data.**
|
||||||
|
The code is available publicly for review.
|
||||||
|
|
||||||
|
|
||||||
|
- **The layouts`.zip` are digitally signed.**
|
||||||
|
If you detect an invalid signature, be cautious of the file or the author.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Frequently Asked Questions
|
||||||
|
|
||||||
|
### Where is my website stored locally?
|
||||||
|
All projects are saved in:
|
||||||
|
|
||||||
|
```
|
||||||
|
.config_app/profiles/default/
|
||||||
|
```
|
||||||
|
|
||||||
|
`.config_app` is a hidden folder.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
What do I need to publish to Arweave?
|
||||||
|
You’ll need AR tokens.
|
||||||
|
About $1–$2 USD is enough to publish.
|
||||||
|
|
||||||
|
|
||||||
|
### Can I update a site after uploading?
|
||||||
|
Yes.
|
||||||
|
ArWeb uses a local **cryptography system** to:
|
||||||
|
|
||||||
|
- Detect already uploaded files
|
||||||
|
- Upload only new files
|
||||||
|
- Update the manifest while preserving previous metadata
|
||||||
|
- Avoid unnecessary expenses
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contact and Credits
|
||||||
|
|
||||||
|
- **Author main**: Zenaku
|
||||||
|
Monero address:
|
||||||
|
```bash
|
||||||
|
41kbbxc2VYtHmc9jNAnRoSTqxCAfXz1XdR1WfVWQNKsMGFL8MWcsxTQSoNUmiDNPnNNh1FkKKSjZn2uAXHTP8Jhv1GeGfwr
|
||||||
|
|
||||||
|
|
||||||
|
- **Contributor:**: SimplifiedPrivacy
|
||||||
|
Monero address:
|
||||||
|
```bash
|
||||||
|
XXX
|
||||||
|
|
||||||
|
Support this project and digital freedom with a donation.
|
||||||
|
|
||||||
|
Thank you for using ArWeb!
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
cffi==1.17.1
|
||||||
|
cryptography==44.0.3
|
||||||
|
pycparser==2.22
|
||||||
|
PyQt6==6.9.0
|
||||||
|
PyQt6-Qt6==6.9.0
|
||||||
|
PyQt6_sip==13.10.0
|
||||||
|
tqdm==4.67.1
|
734
worker.py
Normal file
734
worker.py
Normal file
|
@ -0,0 +1,734 @@
|
||||||
|
# Módulos estándar
|
||||||
|
import glob
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Módulos de terceros
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
# Módulos de PyQt6
|
||||||
|
from PyQt6.QtCore import QThread, pyqtSignal
|
||||||
|
|
||||||
|
# Otros
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
class WorkerThread(QThread):
|
||||||
|
result = pyqtSignal(bool)
|
||||||
|
|
||||||
|
def __init__(self, key, profile_selected, hugo_memory=None, template_selected=None, project_selected=None, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.key = key
|
||||||
|
self.profile_selected = profile_selected
|
||||||
|
self.hugo_memory = hugo_memory or {}
|
||||||
|
self.template_selected = template_selected
|
||||||
|
self.project_selected = project_selected
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
success = False
|
||||||
|
if self.key == 'verify_wallet':
|
||||||
|
success = self.verify_wallet()
|
||||||
|
elif self.key == 'create_wallet':
|
||||||
|
success = self.create_wallet()
|
||||||
|
elif self.key == 'create_website':
|
||||||
|
success = self.create_website()
|
||||||
|
elif self.key == 'edit_website':
|
||||||
|
success = self.edit_website()
|
||||||
|
elif self.key == 'public_arweave':
|
||||||
|
success = self.public_arweave()
|
||||||
|
elif self.key == 'refresh_arweave':
|
||||||
|
success = self.refresh_arweave()
|
||||||
|
else:
|
||||||
|
print("Invalid key provided.")
|
||||||
|
self.result.emit(success)
|
||||||
|
|
||||||
|
def create_wallet(self):
|
||||||
|
# Ruta del perfil
|
||||||
|
profile_path = os.path.join(".config_app", "profiles", self.profile_selected)
|
||||||
|
wallet_path = os.path.join(profile_path, f"wallet_{self.profile_selected}.json")
|
||||||
|
|
||||||
|
# Verificar si el directorio del perfil existe. Si no, crearlo
|
||||||
|
if not os.path.exists(profile_path):
|
||||||
|
try:
|
||||||
|
os.makedirs(profile_path) # Crear el directorio para el perfil si no existe
|
||||||
|
print(f"El directorio del perfil {self.profile_selected} ha sido creado.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al crear el directorio del perfil {self.profile_selected}: {e}")
|
||||||
|
|
||||||
|
# Generación de la seedphrase
|
||||||
|
seed_proc = subprocess.run(["ardrive", "generate-seedphrase"], text=True, stdout=subprocess.PIPE)
|
||||||
|
seed = seed_proc.stdout.strip().replace('"', '')
|
||||||
|
|
||||||
|
# Generar la wallet
|
||||||
|
result = subprocess.run(
|
||||||
|
["ardrive", "generate-wallet", "-s", seed],
|
||||||
|
cwd=profile_path,
|
||||||
|
text=True,
|
||||||
|
stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
wallet_data = result.stdout.strip()
|
||||||
|
|
||||||
|
# Guardar la wallet en un archivo JSON
|
||||||
|
try:
|
||||||
|
with open(wallet_path, 'w') as f:
|
||||||
|
json.dump(json.loads(wallet_data), f, indent=4)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al guardar la wallet en {wallet_path}: {e}")
|
||||||
|
|
||||||
|
# Obtener el balance de la wallet
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["ardrive", "get-balance", "-w", wallet_path],
|
||||||
|
capture_output=True, text=True, check=True
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return False
|
||||||
|
def verify_wallet(self):
|
||||||
|
wallet_path = os.path.join(
|
||||||
|
".config_app", "profiles", self.profile_selected, f"wallet_{self.profile_selected}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ardrive", "get-balance", "-w", wallet_path],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print("Resultado del balance:")
|
||||||
|
print(result.stdout)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("Error al obtener el balance:")
|
||||||
|
print(e.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_website(self):
|
||||||
|
print(self.hugo_memory)
|
||||||
|
def create_hugo_structure():
|
||||||
|
#creaar estructura hugo
|
||||||
|
time.sleep(2)
|
||||||
|
print(f"BasePage Variables:\nprofile_selected: {self.profile_selected}\nproject_selected: {self.project_selected}\ntemplate_selected: {self.template_selected}")
|
||||||
|
|
||||||
|
general_path = os.path.join(".config_app", "profiles", self.profile_selected)
|
||||||
|
create_hugo = subprocess.run(
|
||||||
|
["hugo", "new", "site", self.project_selected],
|
||||||
|
cwd=general_path, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
if create_hugo.returncode != 0:
|
||||||
|
print(f"Error: {create_hugo.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Website '{self.project_selected}' created successfully!")
|
||||||
|
|
||||||
|
project_path = os.path.join(general_path, self.project_selected)
|
||||||
|
os.makedirs(os.path.join(project_path, "config", "_default"), exist_ok=True)
|
||||||
|
os.makedirs(os.path.join(project_path, "kontent"), exist_ok=True)
|
||||||
|
for fname in ["config.yaml", "taxonomies.yaml", "outputs.yaml"]:
|
||||||
|
open(os.path.join(project_path, "config", "_default", fname), "a").close()
|
||||||
|
|
||||||
|
for unwanted in ["content", "layouts", "static"]:
|
||||||
|
path = os.path.join(project_path, unwanted)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
shutil.rmtree(path, ignore_errors=True)
|
||||||
|
hugo_toml = os.path.join(project_path, "hugo.toml")
|
||||||
|
if os.path.isfile(hugo_toml):
|
||||||
|
os.remove(hugo_toml)
|
||||||
|
|
||||||
|
# Copiar la carpeta del tema (ya implementado)
|
||||||
|
src_theme_path = os.path.join(
|
||||||
|
".config_app", "templates", self.template_selected, self.template_selected
|
||||||
|
)
|
||||||
|
dst_theme_dir = os.path.join(project_path, "themes")
|
||||||
|
dst_theme_path = os.path.join(dst_theme_dir, self.template_selected)
|
||||||
|
|
||||||
|
if os.path.isdir(src_theme_path):
|
||||||
|
os.makedirs(dst_theme_dir, exist_ok=True)
|
||||||
|
shutil.copytree(src_theme_path, dst_theme_path, dirs_exist_ok=True)
|
||||||
|
print(f"Template '{self.template_selected}' copiado a 'themes/'.")
|
||||||
|
else:
|
||||||
|
print(f"Error: template no encontrado en {src_theme_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Copiar la carpeta 'kontent' del template a profile/
|
||||||
|
copy_kontent_path = os.path.join(
|
||||||
|
".config_app", "templates", self.template_selected, "kontent"
|
||||||
|
)
|
||||||
|
paste_kontent_path = os.path.join(general_path, self.project_selected, "kontent")
|
||||||
|
|
||||||
|
if os.path.isdir(copy_kontent_path):
|
||||||
|
shutil.copytree(copy_kontent_path, paste_kontent_path, dirs_exist_ok=True)
|
||||||
|
print(f"Karpeta 'kontent' copiada a {paste_kontent_path}.")
|
||||||
|
else:
|
||||||
|
print(f"Error: carpeta 'kontent' no encontrada en {copy_kontent_path}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
def call_create_markdown():
|
||||||
|
template_dir = os.path.join(".config_app","templates",self.template_selected)
|
||||||
|
for file_path in glob.glob(f"{template_dir}/*.py"):
|
||||||
|
try:
|
||||||
|
name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
|
spec = importlib.util.spec_from_file_location(name, file_path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
#Ir al main.py de layout (plantilla) y esperar envio de sesultado, se le envia
|
||||||
|
#hugo memory
|
||||||
|
|
||||||
|
if hasattr(module, 'create_markdown'):
|
||||||
|
print(f"Execute create_markdown from: {file_path}")
|
||||||
|
result = module.create_markdown(
|
||||||
|
self.profile_selected,
|
||||||
|
self.hugo_memory,
|
||||||
|
self.template_selected,
|
||||||
|
self.project_selected
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is True:
|
||||||
|
print("Markdown creado correctamente. Añadiendo contenido y compilando...")
|
||||||
|
|
||||||
|
base_path = os.path.join(".config_app","profiles",self.profile_selected, self.project_selected)
|
||||||
|
config_dir = os.path.join(base_path, "config", "_default")
|
||||||
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
|
|
||||||
|
config_path = os.path.join(config_dir, "config.yaml")
|
||||||
|
config_text = (
|
||||||
|
f'title: "{self.hugo_memory.get("line_website_name", "Generic name")}"\n'
|
||||||
|
f'contentDir: "kontent"\n'
|
||||||
|
f'theme: "{self.template_selected}"\n'
|
||||||
|
f'relativeURLs: true\n'
|
||||||
|
f'params:\n'
|
||||||
|
f' author: "{self.profile_selected}"\n'
|
||||||
|
f' description: "{self.hugo_memory.get("plain_website_description", "No description")}"\n'
|
||||||
|
f' seoKeys: "{self.hugo_memory.get("plain_website_seo", "No keywords")}"\n'
|
||||||
|
f'markup:\n'
|
||||||
|
f' goldmark:\n'
|
||||||
|
f' renderer:\n'
|
||||||
|
f' unsafe: true\n'
|
||||||
|
)
|
||||||
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(config_text)
|
||||||
|
print(f"Config created in {config_path}")
|
||||||
|
|
||||||
|
outputs_path = os.path.join(config_dir, "outputs.yaml")
|
||||||
|
outputs_text = (
|
||||||
|
f'home:\n'
|
||||||
|
f' - HTML\n'
|
||||||
|
f' - RSS\n'
|
||||||
|
f'section:\n'
|
||||||
|
f' - HTML\n'
|
||||||
|
f'page:\n'
|
||||||
|
f' - HTML\n'
|
||||||
|
f'taxonomy:\n'
|
||||||
|
f' - HTML\n'
|
||||||
|
f'term:\n'
|
||||||
|
f' - HTML\n'
|
||||||
|
)
|
||||||
|
with open(outputs_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(outputs_text)
|
||||||
|
print(f"Config created in {outputs_path}")
|
||||||
|
|
||||||
|
taxonomies_path = os.path.join(config_dir, "taxonomies.yaml")
|
||||||
|
|
||||||
|
# Obtener la lista desde hugo_memory, o usar lista vacía si no existe
|
||||||
|
taxonomies_list = self.hugo_memory.get("taxonomies", [])
|
||||||
|
|
||||||
|
# Generar el texto YAML dinámicamente
|
||||||
|
taxonomies_text = ""
|
||||||
|
for taxonomy in taxonomies_list:
|
||||||
|
taxonomies_text += f'{taxonomy}: "{taxonomy}"\n'
|
||||||
|
|
||||||
|
# Guardar el archivo YAML
|
||||||
|
with open(taxonomies_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(taxonomies_text)
|
||||||
|
|
||||||
|
print(f"Config created in {taxonomies_path}")
|
||||||
|
print(f"Ejecutando Hugo en: {base_path}")
|
||||||
|
print("Comando: hugo")
|
||||||
|
|
||||||
|
build = subprocess.run(
|
||||||
|
["hugo"],
|
||||||
|
cwd=base_path, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Salida estándar:")
|
||||||
|
print(build.stdout)
|
||||||
|
print("Salida de error:")
|
||||||
|
print(build.stderr)
|
||||||
|
|
||||||
|
if build.returncode != 0:
|
||||||
|
print(f"Error hugo:\n{build.stderr}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# 🔐 Crear y guardar estructura con hashes
|
||||||
|
print("New Structure!!")
|
||||||
|
structure = {}
|
||||||
|
public_path = os.path.join(base_path, "public")
|
||||||
|
for root, dirs, files in os.walk(public_path, topdown=False):
|
||||||
|
rel_path = os.path.relpath(root, public_path)
|
||||||
|
pointer = structure
|
||||||
|
if rel_path != ".":
|
||||||
|
for part in rel_path.split(os.sep):
|
||||||
|
pointer = pointer.setdefault(part, {})
|
||||||
|
file_data = {}
|
||||||
|
for file in sorted(files):
|
||||||
|
full_path = os.path.join(root, file)
|
||||||
|
with open(full_path, "rb") as f:
|
||||||
|
file_hash = hashlib.md5(f.read()).hexdigest()
|
||||||
|
pointer[file] = file_hash
|
||||||
|
file_data[file] = file_hash
|
||||||
|
for d in sorted(dirs):
|
||||||
|
sub_pointer = structure
|
||||||
|
for part in os.path.join(rel_path, d).split(os.sep):
|
||||||
|
sub_pointer = sub_pointer.get(part, {})
|
||||||
|
if "__hash__" in sub_pointer:
|
||||||
|
file_data[d] = {"__hash__": sub_pointer["__hash__"]}
|
||||||
|
pointer["__hash__"] = hashlib.md5(json.dumps(file_data, sort_keys=True).encode()).hexdigest()
|
||||||
|
|
||||||
|
structure_path = os.path.join(base_path, "structure.json")
|
||||||
|
with open(structure_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(structure, f, indent=4, sort_keys=True)
|
||||||
|
print(f"Save structure in {structure_path}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error{file_path} → {e}")
|
||||||
|
return False
|
||||||
|
print("No found create_markdown")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Flujo principal
|
||||||
|
if create_hugo_structure():
|
||||||
|
return call_create_markdown()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def edit_website(self):
|
||||||
|
print(self.template_selected)
|
||||||
|
#obtener self.template_selected
|
||||||
|
template_dir = os.path.join(".config_app","templates",self.template_selected)
|
||||||
|
base_path = os.path.join(".config_app", "profiles", self.profile_selected, self.project_selected)
|
||||||
|
|
||||||
|
for file_path in glob.glob(f"{template_dir}/*.py"):
|
||||||
|
try:
|
||||||
|
name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
|
spec = importlib.util.spec_from_file_location(name, file_path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
if hasattr(module, 'create_markdown'):
|
||||||
|
result = module.create_markdown(
|
||||||
|
self.profile_selected,
|
||||||
|
self.hugo_memory,
|
||||||
|
self.template_selected,
|
||||||
|
self.project_selected
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is True:
|
||||||
|
build = subprocess.run(
|
||||||
|
["hugo"],
|
||||||
|
cwd=base_path, text=True,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
print(build.stdout)
|
||||||
|
print(build.stderr)
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error {file_path} → {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def public_arweave(self):
|
||||||
|
print("DEBUG2, public first:", self.profile_selected, self.project_selected)
|
||||||
|
base_path = os.path.join(".config_app","profiles",self.profile_selected)
|
||||||
|
wallet_path = os.path.join(base_path, f"wallet_{self.profile_selected}.json")
|
||||||
|
wallet_path2 = os.path.abspath(wallet_path)
|
||||||
|
project_path = os.path.join(base_path, self.project_selected)
|
||||||
|
public_path = os.path.join(project_path, "public")
|
||||||
|
manifest_path = os.path.join(project_path, "manifest.json")
|
||||||
|
drive_file_path = os.path.join(project_path, f"drive_{self.project_selected}.json")
|
||||||
|
time.sleep(5)
|
||||||
|
print("Wait ensambling")
|
||||||
|
|
||||||
|
#si no existe ejecutar este codigo normalmente
|
||||||
|
create_drive = [
|
||||||
|
"ardrive", "create-drive",
|
||||||
|
"--wallet-file", wallet_path,
|
||||||
|
"--drive-name", self.project_selected
|
||||||
|
]
|
||||||
|
result = subprocess.run(create_drive, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error creating drive:", result.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
project_data = json.loads(result.stdout)
|
||||||
|
with open(drive_file_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(project_data, f, indent=4)
|
||||||
|
|
||||||
|
drive_id = next(
|
||||||
|
(d["entityId"] for d in project_data.get("created", []) if d.get("type") == "folder"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
if not drive_id:
|
||||||
|
print("No drive_id found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("Drive ID:", drive_id)
|
||||||
|
for _ in tqdm(range(5), desc="Stop", ncols=80):
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
upload_website = (
|
||||||
|
f"for i in *; do ardrive upload-file --local-path \"$i\" "
|
||||||
|
f"--parent-folder-id {drive_id} -w '{wallet_path2}'; done"
|
||||||
|
)
|
||||||
|
print(upload_website)
|
||||||
|
|
||||||
|
subprocess.run(upload_website, shell=True, cwd=public_path, text=True)
|
||||||
|
|
||||||
|
for _ in tqdm(range(5), desc="Make Manifest", ncols=80):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
upload_manifest = (
|
||||||
|
f"ardrive create-manifest -f '{drive_id}' -w '{wallet_path2}' > ../manifest.json"
|
||||||
|
)
|
||||||
|
subprocess.run(upload_manifest, shell=True, cwd=public_path, text=True)
|
||||||
|
if os.path.isfile(manifest_path):
|
||||||
|
print("Manifest created successfully")
|
||||||
|
for _ in tqdm(range(10), desc="Wait, distrubution on arweave system", ncols=80):
|
||||||
|
time.sleep(1)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("Manifest file not created")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def refresh_arweave(self):
|
||||||
|
print("DEBUG-Refresh-Arweave, public first:", self.profile_selected, self.project_selected)
|
||||||
|
|
||||||
|
print(self.template_selected)
|
||||||
|
profile_path = os.path.join(".config_app","profiles",self.profile_selected)
|
||||||
|
def change_manifest(profile_path):#firts step
|
||||||
|
print(profile_path)
|
||||||
|
try:
|
||||||
|
manifest_path = os.path.join(profile_path, self.project_selected, "manifest.json")
|
||||||
|
with open(manifest_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
paths_data = data["manifest"]["paths"]
|
||||||
|
|
||||||
|
new_path = os.path.join(os.path.dirname(manifest_path), "ma.json")
|
||||||
|
with open(new_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump({"paths": paths_data}, f, separators=(',', ':'))
|
||||||
|
os.remove(manifest_path)
|
||||||
|
print("manifest.json ---> ma.json.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing manifest: {e}")
|
||||||
|
|
||||||
|
def generate_structure(profile_path):
|
||||||
|
public_path = os.path.join(profile_path)
|
||||||
|
print(f"Buscando en: {public_path}")
|
||||||
|
print(self.project_selected)
|
||||||
|
|
||||||
|
if not os.path.exists(public_path):
|
||||||
|
print("El directorio 'public' no existe.")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
structure = {}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(public_path, topdown=False):
|
||||||
|
print(f"Explorando: {root}")
|
||||||
|
rel_path = os.path.relpath(root, public_path)
|
||||||
|
pointer = structure
|
||||||
|
if rel_path != ".":
|
||||||
|
for part in rel_path.split(os.sep):
|
||||||
|
pointer = pointer.setdefault(part, {})
|
||||||
|
|
||||||
|
file_data = {}
|
||||||
|
for file in sorted(files):
|
||||||
|
print(f" - Archivo encontrado: {file}")
|
||||||
|
full_path = os.path.join(root, file)
|
||||||
|
with open(full_path, "rb") as f:
|
||||||
|
data = f.read()
|
||||||
|
file_hash = hashlib.md5(data).hexdigest()
|
||||||
|
pointer[file] = file_hash
|
||||||
|
file_data[file] = file_hash
|
||||||
|
|
||||||
|
for d in sorted(dirs):
|
||||||
|
sub_path = os.path.join(rel_path, d)
|
||||||
|
sub_pointer = structure
|
||||||
|
for part in sub_path.split(os.sep):
|
||||||
|
sub_pointer = sub_pointer.get(part, {})
|
||||||
|
if "__hash__" in sub_pointer:
|
||||||
|
file_data[d] = {"__hash__": sub_pointer["__hash__"]}
|
||||||
|
|
||||||
|
folder_hash = hashlib.md5(json.dumps(file_data, sort_keys=True).encode()).hexdigest()
|
||||||
|
pointer["__hash__"] = folder_hash
|
||||||
|
|
||||||
|
print("Nueva radiografía generada:")
|
||||||
|
print(json.dumps(structure, indent=2, ensure_ascii=False))
|
||||||
|
return structure
|
||||||
|
|
||||||
|
def compare_hash(profile_path):
|
||||||
|
print("Verify structure")
|
||||||
|
public_path = os.path.join(profile_path,self.project_selected, "public")
|
||||||
|
swarp_path = os.path.join(profile_path,self.project_selected, "swarp")
|
||||||
|
radiografia_path = os.path.join(profile_path,self.project_selected, "structure.json")
|
||||||
|
|
||||||
|
if os.path.exists(radiografia_path):
|
||||||
|
with open(radiografia_path, "r") as f:
|
||||||
|
old_structure = json.load(f)
|
||||||
|
else:
|
||||||
|
print("Empty,fail old structure")
|
||||||
|
return
|
||||||
|
|
||||||
|
new_structure = generate_structure(public_path)
|
||||||
|
|
||||||
|
os.makedirs(swarp_path, exist_ok=True)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(public_path, topdown=False):
|
||||||
|
rel_path = os.path.relpath(root, public_path)
|
||||||
|
old_pointer = old_structure
|
||||||
|
new_pointer = new_structure
|
||||||
|
|
||||||
|
if rel_path != ".":
|
||||||
|
for part in rel_path.split(os.sep):
|
||||||
|
old_pointer = old_pointer.get(part, {})
|
||||||
|
new_pointer = new_pointer.get(part, {})
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file == "__hash__":
|
||||||
|
continue
|
||||||
|
new_file_hash = new_pointer.get(file)
|
||||||
|
old_file_hash = old_pointer.get(file)
|
||||||
|
|
||||||
|
if new_file_hash and old_file_hash == new_file_hash:
|
||||||
|
src = os.path.join(root, file)
|
||||||
|
dest_dir = os.path.join(swarp_path, rel_path)
|
||||||
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
|
shutil.move(src, os.path.join(dest_dir, file))
|
||||||
|
print(f"No changes, No upload: {rel_path}/{file}")
|
||||||
|
|
||||||
|
if not os.listdir(root):
|
||||||
|
os.rmdir(root)
|
||||||
|
|
||||||
|
os.remove(radiografia_path)
|
||||||
|
def new_radiography(profile_path):
|
||||||
|
print("Generate new structure.json")
|
||||||
|
public_path = os.path.join(profile_path, self.project_selected, "public")
|
||||||
|
radiografia_path = os.path.join(profile_path, self.project_selected, "structure.json")
|
||||||
|
|
||||||
|
structure = generate_structure(public_path)
|
||||||
|
|
||||||
|
with open(radiografia_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(structure, f, indent=4, sort_keys=True)
|
||||||
|
#bien, listo para lo que sigue
|
||||||
|
def upload_public(profile_path):
|
||||||
|
print("Upload new files")
|
||||||
|
|
||||||
|
project_file = os.path.join(profile_path,self.project_selected, f"drive_{self.project_selected}.json")
|
||||||
|
wallet_path = os.path.join(profile_path, f"wallet_{self.profile_selected}.json")
|
||||||
|
wallet_path2 = os.path.abspath(wallet_path)
|
||||||
|
|
||||||
|
public_path = os.path.join(profile_path, self.project_selected, "public")
|
||||||
|
project_path = os.path.join(profile_path, self.project_selected)
|
||||||
|
|
||||||
|
|
||||||
|
print('en esta direccion buscarmeos el json drive')
|
||||||
|
print(project_file)
|
||||||
|
time.sleep(30)
|
||||||
|
with open(project_file, "r") as f:
|
||||||
|
project_data = json.load(f)
|
||||||
|
|
||||||
|
drive_id = next(
|
||||||
|
(d["entityId"] for d in project_data.get("created", []) if d.get("type") == "folder"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not drive_id:
|
||||||
|
print("No valid drive_id")
|
||||||
|
return
|
||||||
|
|
||||||
|
for _ in tqdm(range(10), desc="Wait...", ncols=80):
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
upload_cmd = (
|
||||||
|
f"for i in *; do ardrive upload-file --local-path \"$i\" "
|
||||||
|
f"--parent-folder-id '{drive_id}' -w '{wallet_path2}'; done > ../ni.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.run(upload_cmd, shell=True, cwd=public_path, text=True)
|
||||||
|
print("New files upload ---> ni.json")
|
||||||
|
|
||||||
|
for _ in tqdm(range(10), desc="Formating ni.json", ncols=80):
|
||||||
|
time.sleep(1)
|
||||||
|
ni_path = os.path.join(project_path, "ni.json")
|
||||||
|
|
||||||
|
with open(ni_path, 'r', encoding='utf-8') as f:
|
||||||
|
contenido = f.read()
|
||||||
|
|
||||||
|
patron = r'"created"\s*:\s*(\[[^\]]*\](?:\s*,\s*\[[^\]]*\])*)'
|
||||||
|
coincidencias = re.findall(patron, contenido, re.DOTALL)
|
||||||
|
|
||||||
|
resultado_paths = {}
|
||||||
|
|
||||||
|
for coincidencia in coincidencias:
|
||||||
|
try:
|
||||||
|
lista = json.loads(coincidencia)
|
||||||
|
for item in lista:
|
||||||
|
if item.get("type") == "file" and "sourceUri" in item and "dataTxId" in item:
|
||||||
|
# Limpiar el sourceUri: quitar todo antes de "public/"
|
||||||
|
uri = item["sourceUri"]
|
||||||
|
uri = re.sub(r'^.*?public/', '', uri) # Elimina todo antes de "public/"
|
||||||
|
resultado_paths[uri] = {"id": item["dataTxId"]} # Reemplazar dataTxId por id
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(ni_path, 'w', encoding='utf-8') as out:
|
||||||
|
json.dump({"paths": resultado_paths}, out, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"ni.json correcty formatted")
|
||||||
|
##voy aca
|
||||||
|
def combine_folders(profile_path):
|
||||||
|
public_path = os.path.join(profile_path,self.project_selected, "public")
|
||||||
|
swarp_path = os.path.join(profile_path,self.project_selected, "swarp")
|
||||||
|
|
||||||
|
if not os.path.exists(swarp_path):
|
||||||
|
print("No swarp")
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in os.listdir(swarp_path):
|
||||||
|
s_item = os.path.join(swarp_path, item)
|
||||||
|
p_item = os.path.join(public_path, item)
|
||||||
|
|
||||||
|
if os.path.isdir(s_item):
|
||||||
|
shutil.copytree(s_item, p_item, dirs_exist_ok=True)
|
||||||
|
else:
|
||||||
|
shutil.copy2(s_item, p_item)
|
||||||
|
|
||||||
|
shutil.rmtree(swarp_path)
|
||||||
|
def new_manifest(profile_path):
|
||||||
|
print("ma.json + ni.json = manifest.json...")
|
||||||
|
|
||||||
|
ma_path = os.path.join(profile_path,self.project_selected, "ma.json")
|
||||||
|
ni_path = os.path.join(profile_path,self.project_selected, "ni.json")
|
||||||
|
mani_path = os.path.join(profile_path,self.project_selected, "manifest.json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Leer los archivos 'ma.json' (old) y 'ni.json' (new)
|
||||||
|
with open(ma_path, 'r', encoding='utf-8') as f:
|
||||||
|
ma_data = json.load(f)
|
||||||
|
|
||||||
|
with open(ni_path, 'r', encoding='utf-8') as f:
|
||||||
|
ni_data = json.load(f)
|
||||||
|
|
||||||
|
ma_paths = ma_data.get("paths", {})
|
||||||
|
ni_paths = ni_data.get("paths", {})
|
||||||
|
|
||||||
|
for key in ni_paths:
|
||||||
|
if key in ma_paths:
|
||||||
|
del ma_paths[key]
|
||||||
|
|
||||||
|
ma_paths.update(ni_paths)
|
||||||
|
|
||||||
|
new_manifest_data = {
|
||||||
|
"manifest": "arweave/paths",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"index": {
|
||||||
|
"path": "index.html"
|
||||||
|
},
|
||||||
|
"paths": ma_paths
|
||||||
|
}
|
||||||
|
|
||||||
|
# Guardar el nuevo manifiesto combinado en 'mani.json'
|
||||||
|
with open(mani_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(new_manifest_data, f, indent=4)
|
||||||
|
|
||||||
|
print("Manifest ready for upload.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error 404 {e}")
|
||||||
|
|
||||||
|
def upload_manifest(profile_path):
|
||||||
|
print("Upload manifest")
|
||||||
|
|
||||||
|
project_file = os.path.join(profile_path, self.project_selected, f"drive_{self.project_selected}.json")
|
||||||
|
wallet_path = os.path.join(profile_path, f"wallet_{self.profile_selected}.json")
|
||||||
|
wallet_path2 = os.path.abspath(wallet_path)
|
||||||
|
project_path = os.path.join(profile_path, self.project_selected)
|
||||||
|
|
||||||
|
with open(project_file, "r") as f:
|
||||||
|
project_data = json.load(f)
|
||||||
|
|
||||||
|
drive_id = next(
|
||||||
|
(d["entityId"] for d in project_data.get("created", []) if d.get("type") == "folder"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not drive_id:
|
||||||
|
print("No found drive_id")
|
||||||
|
return
|
||||||
|
|
||||||
|
upload_cmd = (
|
||||||
|
f"ardrive upload-file --content-type 'application/x.arweave-manifest+json' "
|
||||||
|
f"--local-path manifest.json --parent-folder-id '{drive_id}' -w '{wallet_path2}' > fest.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.run(upload_cmd, shell=True, cwd=project_path, text=True)
|
||||||
|
print("Manifest correctly uploaded")
|
||||||
|
|
||||||
|
mani_path = os.path.join(profile_path,self.project_selected, "manifest.json")
|
||||||
|
fest_path = os.path.join(profile_path,self.project_selected, "fest.json")
|
||||||
|
final_manifest_path = os.path.join(profile_path,self.project_selected, "manifest.json")
|
||||||
|
|
||||||
|
with open(mani_path, "r", encoding="utf-8") as f:
|
||||||
|
mani_data = json.load(f)
|
||||||
|
|
||||||
|
with open(fest_path, "r", encoding="utf-8") as f:
|
||||||
|
fest_data = json.load(f)
|
||||||
|
|
||||||
|
combined_data = {
|
||||||
|
"manifest": mani_data,
|
||||||
|
**fest_data # Esto "expande" created, tips, fees directamente
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(final_manifest_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(combined_data, f, indent=4)
|
||||||
|
|
||||||
|
def delete(profile_path):
|
||||||
|
project_path = os.path.join(profile_path, self.project_selected)
|
||||||
|
|
||||||
|
archivos = ["ma.json", "ni.json", "fest.json"]
|
||||||
|
for archivo in archivos:
|
||||||
|
ruta = os.path.join(project_path, archivo)
|
||||||
|
if os.path.exists(ruta):
|
||||||
|
os.remove(ruta)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
|
try:
|
||||||
|
# Call the change_manifest function
|
||||||
|
change_manifest(profile_path)
|
||||||
|
compare_hash(profile_path)
|
||||||
|
upload_public(profile_path)
|
||||||
|
combine_folders(profile_path)
|
||||||
|
new_manifest(profile_path)
|
||||||
|
new_radiography(profile_path)
|
||||||
|
upload_manifest(profile_path)
|
||||||
|
delete(profile_path)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
return False
|
Loading…
Reference in a new issue