@danhachuel/thunderbolt 0.7.11 → 0.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/main.py CHANGED
@@ -2626,7 +2626,16 @@ def _channel_average_video_time(channel: dict[str, Any], *, prompt_master: str =
2626
2626
 
2627
2627
  def _refresh_youtube_channel_metrics(channel: dict[str, Any], youtube: YouTubeAdapter) -> tuple[bool, str]:
2628
2628
  channel_ref = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("url") or channel.get("handle") or "")
2629
- result = youtube.fetch_channel(channel_ref) if youtube.api_key else youtube.fetch_channel_public(channel_ref)
2629
+ if not channel_ref:
2630
+ return False, "Este canal não tem URL, handle ou ID YouTube para actualizar as métricas."
2631
+ try:
2632
+ result = youtube.fetch_channel(channel_ref) if youtube.api_key else youtube.fetch_channel_public(channel_ref)
2633
+ if not result.ok and youtube.api_key:
2634
+ public_result = youtube.fetch_channel_public(channel_ref)
2635
+ if public_result.ok:
2636
+ result = public_result
2637
+ except Exception as exc:
2638
+ return False, f"Não foi possível actualizar as métricas YouTube ({type(exc).__name__}). Tente novamente."
2630
2639
  if not result.ok or not isinstance(result.data, dict):
2631
2640
  return False, result.message
2632
2641
  data = result.data
@@ -448,7 +448,7 @@ def ensure_storage() -> None:
448
448
 
449
449
  _LOCK_TIMEOUT_SECONDS = 30.0
450
450
  _LOCK_POLL_SECONDS = 0.05
451
- _LOCK_STALE_SECONDS = 15 * 60
451
+ _LOCK_STALE_SECONDS = 120
452
452
  _PROTECTED_STATE_FILES = {"channels.json", "tasks.json", "batches.json", "queues.json", "uploads.json"}
453
453
 
454
454
 
@@ -466,6 +466,12 @@ def _state_lock(path: Path, *, read_only: bool = False) -> Iterator[None]:
466
466
  """
467
467
  lock_path = path.with_name(f".{path.name}.lock")
468
468
  lock_path.parent.mkdir(parents=True, exist_ok=True)
469
+ if read_only:
470
+ # JSON writes use os.replace(), so a reader can safely read either the
471
+ # old complete file or the new complete file. Never make page loads
472
+ # wait for a writer/antivirus handle on Windows.
473
+ yield
474
+ return
469
475
  deadline = time.monotonic() + _LOCK_TIMEOUT_SECONDS
470
476
  descriptor: int | None = None
471
477
  while descriptor is None:
@@ -483,7 +489,22 @@ def _state_lock(path: Path, *, read_only: bool = False) -> Iterator[None]:
483
489
  return
484
490
  except FileExistsError:
485
491
  try:
486
- if time.time() - lock_path.stat().st_mtime > _LOCK_STALE_SECONDS:
492
+ lock_age = time.time() - lock_path.stat().st_mtime
493
+ owner_pid = ""
494
+ try:
495
+ owner_pid = lock_path.read_text(encoding="ascii").split("pid=", 1)[1].splitlines()[0].strip()
496
+ except (IndexError, OSError, UnicodeDecodeError):
497
+ pass
498
+ owner_alive = False
499
+ if owner_pid.isdigit():
500
+ try:
501
+ os.kill(int(owner_pid), 0)
502
+ owner_alive = True
503
+ except PermissionError:
504
+ owner_alive = True
505
+ except (ProcessLookupError, OSError):
506
+ owner_alive = False
507
+ if (owner_pid and not owner_alive) or (not owner_pid and lock_age > _LOCK_STALE_SECONDS):
487
508
  lock_path.unlink()
488
509
  continue
489
510
  except OSError:
@@ -235,7 +235,7 @@ def _public_feed_data(channel_id: str, headers: dict[str, str]) -> dict[str, Any
235
235
  response = requests.get(
236
236
  f"https://www.youtube.com/feeds/videos.xml?channel_id={channel_id}",
237
237
  headers=headers,
238
- timeout=12,
238
+ timeout=6,
239
239
  )
240
240
  response.raise_for_status()
241
241
  root = ET.fromstring(response.text)
@@ -375,7 +375,7 @@ class YouTubeAdapter:
375
375
  for page_url in _public_page_candidates(source):
376
376
  last_url = page_url
377
377
  try:
378
- response = requests.get(page_url, headers=headers, timeout=20, allow_redirects=True)
378
+ response = requests.get(page_url, headers=headers, timeout=8, allow_redirects=True)
379
379
  response.raise_for_status()
380
380
  except requests.RequestException as exc:
381
381
  last_error = f"Falha HTTP: {exc}"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.7.11",
3
+ "version": "0.7.13",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",