You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
12 KiB
Python
257 lines
12 KiB
Python
import os, time, zipfile
|
|
from pymxs import runtime as rt
|
|
try: from PySide6 import QtWidgets
|
|
except ImportError: from PySide2 import QtWidgets
|
|
|
|
import urllib.request
|
|
import urllib.parse
|
|
import json
|
|
|
|
def mock_connect_api(ui):
|
|
hash_txt = ui.e_hash.text().strip()
|
|
if len(hash_txt) < 5:
|
|
QtWidgets.QMessageBox.warning(ui, "Acesso Negado", "Hash inválida.")
|
|
return
|
|
|
|
ui.b_conn.setText("Conectando..."); QtWidgets.QApplication.processEvents()
|
|
|
|
# Faz chamada real para a API
|
|
url = "https://api.vr4life.com/public/listaCanais"
|
|
req_data = urllib.parse.urlencode({"tx_hash_login_externo": hash_txt}).encode('utf-8')
|
|
req = urllib.request.Request(url, data=req_data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=10) as response:
|
|
res_body = response.read().decode('utf-8')
|
|
canais = json.loads(res_body)
|
|
|
|
ui.c_chn.clear()
|
|
# Espera receber lista: [{"nm_canal": "...", "tx_hash_canal": "..."}, ...]
|
|
if isinstance(canais, list) and len(canais) > 0:
|
|
for c in canais:
|
|
n_c = str(c.get("nm_canal", "Canal Desconhecido"))
|
|
h_c = str(c.get("tx_hash_canal", ""))
|
|
ui.c_chn.addItem(n_c, h_c)
|
|
else:
|
|
raise Exception("Nenhum canal encontrado para este Hash.")
|
|
|
|
except Exception as e:
|
|
ui.b_conn.setText("🔄 Tentar Novamente")
|
|
QtWidgets.QMessageBox.critical(ui, "Erro de API", f"Falha ao conectar: {str(e)}")
|
|
return
|
|
|
|
# Ativa campos após sucesso
|
|
for w in [ui.c_chn, ui.rdo_add, ui.rdo_edt, ui.e_tit, ui.e_mp3, ui.b_mp3]:
|
|
w.setEnabled(True)
|
|
w.setStyleSheet("background: white; color: black; font-weight: bold;")
|
|
|
|
ui.b_up.setEnabled(True)
|
|
ui.b_conn.setText("✅ Conectado!"); ui.b_conn.setStyleSheet("background: #000; color: #0F0; font-weight: bold; border: 1px solid #0F0;")
|
|
|
|
# Auto-carrega postagens do primeiro canal
|
|
fetch_posts_for_channel(ui)
|
|
|
|
def fetch_posts_for_channel(ui):
|
|
ui.c_pst.blockSignals(True)
|
|
ui.c_pst.clear()
|
|
|
|
hash_txt = ui.e_hash.text().strip()
|
|
idx = ui.c_chn.currentIndex()
|
|
has_posts = False
|
|
|
|
if idx >= 0:
|
|
ch_hash = ui.c_chn.itemData(idx)
|
|
print(f"[DEBUG UI] Canal Index {idx}. Valor Extraído do ItemData: '{ch_hash}'")
|
|
if ch_hash and str(ch_hash).strip():
|
|
ch_hash = str(ch_hash).strip()
|
|
url = "https://api.vr4life.com/public/listaPostagemCanal"
|
|
dict_data = {"tx_hash_login_externo": hash_txt, "tx_hash_canal": ch_hash}
|
|
req_data = urllib.parse.urlencode(dict_data).encode('utf-8')
|
|
|
|
print(f"\n[DEBUG API] Chamando: {url}")
|
|
print(f"[DEBUG API] Payload (FormData): {dict_data}")
|
|
|
|
req = urllib.request.Request(url, data=req_data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=10) as response:
|
|
raw_res = response.read().decode('utf-8')
|
|
posts = json.loads(raw_res)
|
|
if isinstance(posts, list) and len(posts) > 0:
|
|
has_posts = True
|
|
for p in posts:
|
|
t_p = p.get('tx_titulo', 'Sem Título')
|
|
h_p = p.get('hash_postagem', '')
|
|
print(f"[DEBUG API] Encontrou Postagem: '{t_p}' | Hash: {h_p}")
|
|
ui.c_pst.addItem(t_p, h_p)
|
|
except urllib.error.HTTPError as e:
|
|
err_body = e.read().decode('utf-8')
|
|
QtWidgets.QMessageBox.critical(ui, f"Erro HTTP {e.code}", f"O Servidor RECUSOU a requisição.\n\nResposta:\n{err_body}")
|
|
except Exception as e:
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso de Rede", f"Falha na comunicação ou falha lendo o JSON de postagens:\n\n{e}\n\n(A API pode ter retornado HTML ao invés de JSON puro?)")
|
|
else:
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso", "O Hash deste canal não foi preenchido.")
|
|
|
|
ui.c_pst.blockSignals(False)
|
|
|
|
# Se não retornou posts, forçar modo "Novo" e bloquear edição
|
|
if not has_posts:
|
|
ui.rdo_add.setChecked(True)
|
|
ui.rdo_edt.setEnabled(False)
|
|
else:
|
|
ui.rdo_edt.setEnabled(True)
|
|
|
|
on_post_changed(ui)
|
|
|
|
def on_post_changed(ui):
|
|
if ui.rdo_add.isChecked():
|
|
ui.c_pst.setEnabled(False)
|
|
ui.c_pst.setStyleSheet("background: #CCC; color: black; font-weight: normal;")
|
|
ui.e_tit.setText(""); ui.e_tit.setPlaceholderText("Título do NOVO projeto...")
|
|
ui.b_tmb.setEnabled(False)
|
|
else:
|
|
ui.c_pst.setEnabled(True)
|
|
ui.c_pst.setStyleSheet("background: white; color: black; font-weight: bold;")
|
|
if ui.c_pst.count() > 0:
|
|
ui.e_tit.setText(ui.c_pst.currentText())
|
|
ui.b_tmb.setEnabled(True)
|
|
|
|
def finalize_export(ui, p_glb, f_name):
|
|
# Transição de abas após o término do Exportador V19
|
|
ui.tabs.setCurrentIndex(3)
|
|
|
|
def generate_thumbnail(ui):
|
|
p_glb = ui.edt_p_glb.text().replace("\\", "/")
|
|
if not p_glb.endswith("/"): p_glb += "/"
|
|
tgs = ui.get_processable_items()
|
|
if len(tgs) == 1: sn = f"{tgs[0]['name']}_Export"
|
|
else: sn = "VR4Life_Scene_Export"
|
|
tp = os.path.join(p_glb, f"{sn}_Thumb.jpg").replace("\\", "/")
|
|
|
|
ui.pb.setFormat("📸 Tirando Snapshot da Cena..."); QtWidgets.QApplication.processEvents()
|
|
rt.execute(f"""( local c = getActiveCamera(); if c == undefined do ( local ac = for cam in cameras where (classof cam != TargetObject) collect cam; if ac.count > 0 do c = ac[1] ); try ( if c != undefined then ( render camera:c outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) else ( render outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) ) catch() )""")
|
|
ui.pb.setFormat("✅ Thumbnail Gerado e Salvo!"); ui.pb.setValue(100)
|
|
|
|
try: os.startfile(p_glb)
|
|
except: pass
|
|
|
|
def upload_to_cloud(ui):
|
|
p_glb = ui.edt_p_glb.text().replace("\\", "/")
|
|
if not p_glb.endswith("/"): p_glb += "/"
|
|
|
|
tgs = ui.get_processable_items()
|
|
if len(tgs) == 1: sn = f"{tgs[0]['name']}_Export"
|
|
else: sn = "VR4Life_Scene_Export"
|
|
|
|
oz = os.path.join(p_glb, f"{sn}.zip").replace("\\", "/")
|
|
tp = os.path.join(p_glb, f"{sn}_Thumb.jpg").replace("\\", "/")
|
|
|
|
if not os.path.exists(oz):
|
|
try:
|
|
zip_files = [os.path.join(p_glb, f) for f in os.listdir(p_glb) if f.lower().endswith('.zip')]
|
|
if zip_files:
|
|
oz = max(zip_files, key=os.path.getmtime)
|
|
base_zip_name = os.path.basename(oz).replace(".zip", "")
|
|
alt_tp = os.path.join(p_glb, f"{base_zip_name}_Thumb.jpg")
|
|
if os.path.exists(alt_tp): tp = alt_tp
|
|
except Exception as e: print(f"Erro ao buscar zip recente: {e}")
|
|
|
|
if not os.path.exists(oz):
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso", f"Nenhum arquivo ZIP encontrado na pasta:\n{p_glb}\n\nFaça o Bake primeiro!")
|
|
return
|
|
|
|
ui.pb.setFormat(f"📡 API: Sincronizando com a Nuvem..."); QtWidgets.QApplication.processEvents()
|
|
|
|
hx_login = ui.e_hash.text().strip()
|
|
ch_idx = ui.c_chn.currentIndex()
|
|
hx_canal = ui.c_chn.itemData(ch_idx) if ch_idx >= 0 else ""
|
|
titulo = ui.e_tit.text().strip()
|
|
hx_postagem = ""
|
|
if ui.rdo_edt.isChecked():
|
|
pst_idx = ui.c_pst.currentIndex()
|
|
if pst_idx >= 0: hx_postagem = ui.c_pst.itemData(pst_idx)
|
|
|
|
if not hx_login or not hx_canal or not titulo:
|
|
QtWidgets.QMessageBox.warning(ui, "Campos Inválidos", "Por favor conecte-se à API, selecione um Canal e digite um Título antes de enviar!")
|
|
ui.pb.setFormat("Pronto"); ui.pb.setValue(0)
|
|
return
|
|
|
|
import mimetypes
|
|
boundary = '----WebKitFormBoundaryVR4PluginX1A'
|
|
data_parts = []
|
|
|
|
def add_text_field(name, value):
|
|
data_parts.append(f'--{boundary}\r\n'.encode('utf-8'))
|
|
data_parts.append(f'Content-Disposition: form-data; name="{name}"\r\n\r\n'.encode('utf-8'))
|
|
data_parts.append(f'{value}\r\n'.encode('utf-8'))
|
|
|
|
def add_file_field(name, filename, filepath):
|
|
try:
|
|
mime_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
|
|
with open(filepath, 'rb') as f:
|
|
file_data = f.read()
|
|
data_parts.append(f'--{boundary}\r\n'.encode('utf-8'))
|
|
data_parts.append(f'Content-Disposition: form-data; name="{name}"; filename="{filename}"\r\n'.encode('utf-8'))
|
|
data_parts.append(f'Content-Type: {mime_type}\r\n\r\n'.encode('utf-8'))
|
|
data_parts.append(file_data)
|
|
data_parts.append(b'\r\n')
|
|
except Exception as e:
|
|
print(f"[DEBUG UPLOAD] Erro ao ler arquivo {filepath}: {e}")
|
|
|
|
add_text_field('tx_hash_login_externo', hx_login)
|
|
add_text_field('tx_hash_canal', hx_canal)
|
|
add_text_field('tx_titulo', titulo)
|
|
if ui.rdo_edt.isChecked() and hx_postagem:
|
|
add_text_field('tx_hash_postagem', hx_postagem)
|
|
|
|
if not os.path.exists(tp):
|
|
ui.pb.setFormat("📸 Tirando Snapshot Automático (Thumbnail)..."); QtWidgets.QApplication.processEvents()
|
|
rt.execute(f"""( local c = getActiveCamera(); if c == undefined do ( local ac = for cam in cameras where (classof cam != TargetObject) collect cam; if ac.count > 0 do c = ac[1] ); try ( if c != undefined then ( render camera:c outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) else ( render outputwidth:1280 outputheight:720 outputfile:@"{tp}" vfb:false quiet:true ) ) catch() )""")
|
|
|
|
if os.path.exists(tp):
|
|
add_file_field('thumbnail', os.path.basename(tp), tp)
|
|
|
|
if os.path.exists(oz):
|
|
add_file_field('arquivo', os.path.basename(oz), oz)
|
|
|
|
data_parts.append(f'--{boundary}--\r\n'.encode('utf-8'))
|
|
body = b''.join(data_parts)
|
|
|
|
url = "https://api.vr4life.com/public/savePost"
|
|
req = urllib.request.Request(url, data=body)
|
|
req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}')
|
|
req.add_header('Content-Length', str(len(body)))
|
|
|
|
print("\n" + "="*50)
|
|
print("📡 [DEBUG API UPLOAD] DADOS ENVIADOS NO FORM-DATA:")
|
|
print(f" -> tx_hash_login_externo: {hx_login}")
|
|
print(f" -> tx_hash_canal : {hx_canal}")
|
|
print(f" -> tx_titulo : {titulo}")
|
|
|
|
# Se campo não existe, printa vazio pra entender o contexto
|
|
if ui.rdo_edt.isChecked() and hx_postagem:
|
|
print(f" -> tx_hash_postagem : {hx_postagem}")
|
|
|
|
print(f" -> thumbnail (ARQUIVO) : {tp if os.path.exists(tp) else 'NAO ENVIADO'}")
|
|
print(f" -> arquivo (ARQUIVO) : {oz if os.path.exists(oz) else 'NAO ENVIADO'}")
|
|
print(f"--------------------------------------------------")
|
|
print(f"Disparando pacote de {len(body)} bytes para {url} ...")
|
|
print("="*50 + "\n")
|
|
|
|
ui.b_up.setEnabled(False); ui.b_up.setText("Upload em andamento...")
|
|
QtWidgets.QApplication.processEvents()
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=120) as response:
|
|
raw_res = response.read().decode('utf-8')
|
|
print(f"[DEBUG API UPLOAD] Sucesso HTTP 200:\n{raw_res}")
|
|
QtWidgets.QMessageBox.information(ui, "Sincronização Cloud Concluída", f"O projeto foi enviado com sucesso para a plataforma!\n\nDados Retornados da API:\n{raw_res}")
|
|
except urllib.error.HTTPError as e:
|
|
err_body = e.read().decode('utf-8')
|
|
print(f"[DEBUG API UPLOAD] Erro HTTP {e.code}:\n{err_body}")
|
|
QtWidgets.QMessageBox.critical(ui, "Sincronização Falhou", f"O PHP Recusou o Arquivo (HTTP {e.code}).\n\nMotivo:\n{err_body}")
|
|
except Exception as e:
|
|
print(f"[DEBUG API UPLOAD] Exceção: {e}")
|
|
QtWidgets.QMessageBox.warning(ui, "Aviso de Rede", f"Não foi possível completar o envio do arquivo Zip para o servidor.\n\nDetalhes:\n{e}")
|
|
finally:
|
|
ui.b_up.setEnabled(True); ui.b_up.setText("☁️ Enviar para Nuvem")
|
|
ui.pb.setFormat("Pronto"); ui.pb.setValue(0) |