first commit

This commit is contained in:
Zenaku 2025-05-21 10:32:03 -05:00
commit 76add2a45a
15 changed files with 2599 additions and 0 deletions

View 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

View 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
View 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

View 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

View 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

View 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
View 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

Binary file not shown.

BIN
appimagetool-x86_64.AppImage Executable file

Binary file not shown.

BIN
arweb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

1211
main.py Normal file

File diff suppressed because it is too large Load diff

181
readme-español.md Normal file
View 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
View 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?
Youll 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
View 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
View 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