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