Update dashboard, kb, memory +4 more (+28 ~18 -1)
This commit is contained in:
113
tools/generate_pdf.py
Normal file
113
tools/generate_pdf.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate PDF from markdown content.
|
||||
Outputs PDF to stdout as binary.
|
||||
Simple, robust approach focusing on text content.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Read JSON from stdin
|
||||
input_data = json.load(sys.stdin)
|
||||
markdown_content = input_data.get('markdown', '')
|
||||
filename = input_data.get('filename', 'document.pdf')
|
||||
|
||||
try:
|
||||
from fpdf import FPDF
|
||||
import re
|
||||
|
||||
# Create PDF
|
||||
pdf = FPDF(format='A4')
|
||||
pdf.add_page()
|
||||
pdf.set_margins(12, 12, 12)
|
||||
|
||||
# Try to use DejaVu font for Romanian support
|
||||
try:
|
||||
dejavu_path = Path("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
||||
if dejavu_path.exists():
|
||||
pdf.add_font("DejaVu", "", str(dejavu_path))
|
||||
pdf.add_font("DejaVu", "B", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
|
||||
pdf.set_font("DejaVu", "", 10)
|
||||
use_dejavu = True
|
||||
else:
|
||||
raise Exception("DejaVu font not found")
|
||||
except:
|
||||
pdf.set_font("Helvetica", "", 10)
|
||||
use_dejavu = False
|
||||
|
||||
# Parse markdown line by line
|
||||
lines = markdown_content.split('\n')
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Skip empty lines but add spacing
|
||||
if not line.strip():
|
||||
pdf.ln(2)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# H1 - Main heading
|
||||
if line.startswith('# '):
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 16)
|
||||
text = line.replace('# ', '', 1).strip()
|
||||
pdf.multi_cell(0, 7, text, ln=True)
|
||||
pdf.ln(1)
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
|
||||
|
||||
# H2 - Section heading
|
||||
elif line.startswith('## '):
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 12)
|
||||
text = line.replace('## ', '', 1).strip()
|
||||
pdf.multi_cell(0, 6, text, ln=True)
|
||||
pdf.ln(0.5)
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
|
||||
|
||||
# H3 - Subsection
|
||||
elif line.startswith('### '):
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "B", 11)
|
||||
text = line.replace('### ', '', 1).strip()
|
||||
pdf.multi_cell(0, 5, text, ln=True)
|
||||
pdf.ln(0.3)
|
||||
pdf.set_font("DejaVu" if use_dejavu else "Helvetica", "", 10)
|
||||
|
||||
# Bullet point
|
||||
elif line.strip().startswith('- ') or line.strip().startswith('* '):
|
||||
text = line.strip().lstrip('-*').strip()
|
||||
# Use simple dash for bullet
|
||||
pdf.multi_cell(0, 5, '- ' + text, ln=True)
|
||||
|
||||
# Numbered list
|
||||
elif re.match(r'^\s*\d+\.\s', line):
|
||||
text = re.sub(r'^\s*\d+\.\s', '', line)
|
||||
pdf.multi_cell(0, 5, text, ln=True)
|
||||
|
||||
# Regular text with formatting
|
||||
else:
|
||||
# Clean up markdown markers but keep structure
|
||||
text = line.strip()
|
||||
|
||||
# Remove inline markdown
|
||||
text = re.sub(r'\*\*(.*?)\*\*', r'\1', text) # Bold
|
||||
text = re.sub(r'__(.*?)__', r'\1', text) # Bold
|
||||
text = re.sub(r'\*(.*?)\*', r'\1', text) # Italic
|
||||
text = re.sub(r'_(.*?)_', r'\1', text) # Italic
|
||||
text = re.sub(r'\[(.*?)\]\(.*?\)', r'\1', text) # Links
|
||||
|
||||
if text:
|
||||
pdf.multi_cell(0, 5, text, ln=True)
|
||||
|
||||
i += 1
|
||||
|
||||
# Output PDF
|
||||
pdf_bytes = pdf.output()
|
||||
sys.stdout.buffer.write(pdf_bytes)
|
||||
sys.exit(0)
|
||||
|
||||
except Exception as e:
|
||||
error_json = json.dumps({'error': str(e)})
|
||||
sys.stderr.write(error_json)
|
||||
sys.exit(1)
|
||||
26
tools/lead-gen/clienti-romfast.csv
Normal file
26
tools/lead-gen/clienti-romfast.csv
Normal file
@@ -0,0 +1,26 @@
|
||||
NUME,RULAJ_ANUAL,COD_FISCAL,RAMURA,NOTE
|
||||
XENOTI S.R.L.,83099.1,RO6743250,HOTEL,"VALENTINA ESTE EXPERT CONTABIL SI DIRECTOR LA XENOTI. SUNTEM CUNOSTINTE VECHI, LA FEL CA SI CU ALEX STEFAN. IN VIRTUTEA RELATIEI VECHI DE PE ALTE FIRME, A VRUT PROGRAMUL ROA SI LA XENOTI. VALENTINA ARE SI ALTE FIRME. FACTUREZ TOTUL PE XENOTI, INCLUSIV PROIECTE DE MIGRARE ALE UNOR FIRME ALE VALENTINEI PE ROA SI DE ACEEA ARE CEA MAI MARE VALOARE. INTRE TIMP AM 3 FACTURI NEACHITATE DE LA XENOTI. NU A AVUT VANZARI BUNE IN ULTIMUL SEZON ESTIVAL"
|
||||
ROMPETROL ENERGY SA,57928.23,RO29923675,ENERGIE TERMICA,"FOSTA UTMIDIA NAVODARI, CLIENT VECHI, CLIENT IDEAL, FACTURI PUTINE, VALOARE MARE, CLIENTI PUTINI DAR MARI, LE-AM FACUT MIGRARE ROA DE LA UTMIDIA NAVODARI. AU SPUS CA TREC LA PROGRAMELE INTERNATIONALE FOLOSITE DE ROMPETROL SA, DAR INCA NU AU TRECUT DE CATIVA ANI"
|
||||
ADMINISTRATIA CANALELOR NAVIGABILE,39907.84,RO11087755,COMPANIE NATIONALA,"ACUM 15 ANI AU AVUT PROGRAME CONTABILE VECHI SI DE FACTURARE SI AU AVUT NEVOIE DE ALTE PROGRAME. CLIENTI VECHI, STABILI. ACUM NECESITA MENTENANTA PUTINA. A FOST MAI GREU SA LE CRESC PRETURILE. SUNT RETICENTI. CLIENT IDEAL"
|
||||
VENDING MASTER SRL,39270,RO33137200,"DISTRIBUTIE CAFEA, AUTOMATE CAFEA","AVIS DATABASE ACCOUNTING ESTE CONTABILUL LUI, ALEX STEFAN, EXPERTUL CONTABIL DE LA AVIS DATABASE ESTE UN FOST COLEG SI PRIN EL AM LUAT FIRMA VENDING CLIENT, DE CAND ERA MAI MICA CA DIMENSIUNE. I-AM CRESCUT TARIFUL IN DECURS DE MAI MULTI ANI PENTRU CA I-A CRESCUT ACTIVITATEA SI SUPORTUL TEHNIC"
|
||||
VADECO SRL,34012.67,RO14707452,SERVICII TRANSPORT,"CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 4 PERSOANE LA FACTURARE, AGENTI, CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ"
|
||||
EUROPEAN METAL SERVICES SA,32584.67,RO12629765,SERVICII FIER VECHI,"CLIENT IDEAL, SERVICII, ARE CLIENTI FOARTE PUTINI, DAR DE VALOARE MARE, 1 CONTABIL SI DIRECTOR ECONOMIC CU CARE LUCREZ. ARE SI ALTI ANGAJATI BINEINTELES"
|
||||
SOUTH EAST TRUCK SERVICES S.R.L.,31136.42,RO43525632,SERVICE AUTO,"CLIENT IDEAL. CLIENTI PUTINI (POLARIS M HOLDING) DAR MARI. UN RECEPTIONER/GESTIONAR, MECANICI SI PATRONUL CARE ESTE INGINER AUTO"
|
||||
MIDIA GREEN ENERGY SA,19269.2,RO14325363,ENERGIE FOTOVOLTAICA,CLIENT FOARTE VECHI. S-A DESPRINS DIN UTMIDIA NAVODARI CARE A FOST CONVERTITA IN ROMPETROL ENERGY SA. CLIENT IDEAL. LUCREZ DOAR CU DIRECTORUL ECONOMIC CAND ARE NEVOIE DE ASISTENTA LA DECLARATII SAU OPERATII IMOBILIZARI
|
||||
INTREPRINDEREA METALURGICA PENTRU AERONAUTICA META,18102.62,RO16036329,PRODUCTIE,"CLIENT FOARTE VECHI. IDEAL. CA SI EDUARD PUBLISHING A AVUT NEVOI CONTABILE SPORITE PENTRU URMARIREA PRODUCTIEI, COSTURILOR SI ANALIZA CHELTUIELILOR, BUGETELOR"
|
||||
ARGENTA SRL,14482.07,RO3959705,CONSTRUCTII,"CLIENT IDEAL. CLIENTI PUTINI, VALOARE MARE. ARE UN PROGRAM SPECIAL DE URMARIRE PROIECTE/DEVIZE/OFERTE COMPARATIE COSTURI FATA DE OFERTA, PENTRU ANALIZA RANDAMENT LUCRARI CONSTRUCTIE"
|
||||
ETALON DISTRIBUTION S R L,14380,RO42158724,DISTRIBUTIE PRESA,CLIENT VECHI PROVENIT DIN ALT CLIENT - PRIN TRANSFER ACTIVE. VALENTINA ESTE DIRECTOR ECONOMIC SI LA ETALON. AM CONTINUAT ACTIVITATEA CU ETALON PRIN VALENTINA
|
||||
A.B.C. VAL,13487.42,RO3853010,CONSTRUCTII,"A INTRAT IN FALIMENT. NU MAI ESTE CLIENT. CA SI ARGENTA SA, AVEA NEVOI DE URMARIRE PROIECTE, LUCRARI, DEVIZ, OFERTA - AM PROGRAMUL RESPECTIV"
|
||||
EDUARD PUBLISHING,12084.4,RO 25629015,"EDITURA CARTE, PRODUCTIE CARTE","ACUM O DUCE MAI GREU. LA INCEPUTUL RELATIEI, ACUM 15 ANI AVEA ACTIVITATE MAI MARE SI AVEA NEVOI DE URMARIRE PRODUCTIE DE CARTE"
|
||||
AUTOMOTIVE SERVICE SRL,10165,RO18448482,SERVICE AUTO,"CLIENT IDEAL. UN PATRON, UN INGINER, PLUS MECANICI. PLATESTE PROMPT. AM PUTINA ACTIVITATE CU EL CA SUPORT TEHNIC. DE VAZUT CE CIFRA DE AFACERI ARE"
|
||||
CLEVER MOTORS SRL,10165,RO44234984,SERVICE AUTO,"NU PLATESTE LA TIMP, RESTANTE 3-4 LUNI. ESTE UN SERVICE AUTO CARE CRED CA NU ISI PRIMESTE BANII LA TIMP DE LA ASIGURATORI, SAU ARE COMENZI PUTINE. DE VAZUT CE CIFRA DE AFACERI ARE"
|
||||
ALMMA CONTRACTORS GROUP S.R.L.,9223.46,RO37165512,CONSTRUCTII,A DAT FALIMENT. NU MAI ESTE CLIENT. ERA IN GRUPUL A.B.C VAL
|
||||
ROMCONSTRUCT GLASS S.R.L.,8615,RO24498302,PRODUCTIE,"PRODUCTIE GEAMURI, MONTAJ. CLIENT IDEAL DAR IN ULTIMII ANI SI-A REDUS ACTIVITATEA. AVEA NEVOIE DE PROGRAM DE PRODUCTIE, CONSUM, FACTURARE. AM FACUT PROGRAMUL DAR S-A DOVEDIT FOARTE COMPLEX FATA DE CALCULELE LOR DIN EXCEL. FOLOSESC DOAR FACTURAREA DE SERVICII, IN LOC DE PRODUCTIE, CONSUM SI FACTURARE PRODUCTIE. PROBLEMA CEA MAI MARE ERA SI ESTE GESTIUNEA STOCURILOR, DEVIZELE CARE SE FAC IN EXCEL, DAR NU SE VAD IN SOFT. LE LUA PREA MULT TIMP SA FOLOSEASCA PROGRAMELE CONSTRUITE SPECIAL PENTRU PRODUCTIE, DESCARCARE PRODUCTIE SI NU LE-AU FOLOSIT."
|
||||
SIGMA L.C. SERVICE,7274.66,RO21773785,SERVICE AUTO,ARE RESTANTE 3-6 LUNI. E OK CA NIVEL DE CERERI
|
||||
WERT SRL,6000,RO7435479,PRODUCTIE,ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
|
||||
DIRECTIA DE PAZA A JUDETULUI CONSTANTA,4314,5639774,SERVICII,A RAMAS DOAR CU PROGRAMUL DE GESTIUNE OBIECTE DE INVENTAR. NU MAI ARE ACTIVITATE
|
||||
DRAFT CONSTRUCT SRL,3600,RO16322932,CONSTRUCTII,A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
|
||||
DRAFT DINAMIC CONSTRUCT S.R.L.,3600,RO45269115,CONSTRUCTII,A INTRAT IN INSOLVENTA. CLIENT CU PROBLEME FINANCIARE. ESTE CLIENTUL CARAPETRU CONTAB SI FOLOSESTE PROGRAMELE ROA PENTRU CONTABILITATE PRIMARA. NU EMITE FACTURI DIN ROA.
|
||||
BIG TRADE S.A.,2958.38,RO7436261,SERVICII,"NU MAI ESTE CLIENTUL MEU. PLATEA PENTRU UN GRUP DE FIRME MICI, CARE AU TRECUT PE SAGA PENTRU CA NU AVEAU ACTIVITATE"
|
||||
AVIS DATABASE ACCOUNTING S.R.L.,1776.82,40023585,CONTABILITATE,ALEX STEFAN DE LA AVIS DATABASE ACCOUNTING A FOST COLEG CU MINE. SI-A FACUT FIRMA DE CONTABILITATE. VENDING MASTER ESTE CLIENTUL LUI. PRIN ALEX STEFAN L-AM OBTINUT ACUM MULTI ANI.
|
||||
CARAPETRU CONTAB SRL,300,35295575,CONTABILITATE,"SILVIA DE LA CARAPETRU CONTAB ESTE CONTABILA MEA SI FOLOSESTE PROGRAMELE ROA PENTRU FIRMELE EI. WERT, DRAFT DINAMIC, DRAFT CONSTRUCT, SUNT CLIENTII EI"
|
||||
|
@@ -448,11 +448,13 @@ def save_to_oracle(ocr_result: dict, do_commit: bool = False) -> dict:
|
||||
INSERT INTO ACT_TEMP (
|
||||
LUNA, AN, COD, DATAIREG, DATAACT, NRACT,
|
||||
EXPLICATIA, SCD, SCC, SUMA, PROC_TVA,
|
||||
ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA
|
||||
ID_PARTC, ID_PARTD, ID_FDOC, ID_JTVA_COLOANA, TAXCODE, ID_UTIL, DATAORA,
|
||||
STERS, ID_SET, ID_SUCURSALA
|
||||
) VALUES (
|
||||
:luna, :an, :cod, TRUNC(SYSDATE), :dataact, :nract,
|
||||
:expl, :scd, :scc, :suma, :proc_tva,
|
||||
:id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE
|
||||
:id_partc, :id_partd, :id_fdoc, :id_jtva, :taxcode, 0, SYSDATE,
|
||||
0, 0, 0
|
||||
)
|
||||
""",
|
||||
luna=luna, an=an, cod=cod, dataact=receipt_date, nract=nract,
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
YouTube subtitle downloader with rate limit protection and progressive retry.
|
||||
Usage: python3 yt_download.py URL [URL2] [URL3] ...
|
||||
|
||||
Features:
|
||||
- Cookies support for higher limits
|
||||
- Sleep between downloads to avoid rate limiting
|
||||
- Progressive retry: 2h → 4h → 24h on 429
|
||||
- Tracks rate limit state in JSON file
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Add deno to PATH
|
||||
os.environ["PATH"] = f"{Path.home()}/.deno/bin:" + os.environ.get("PATH", "")
|
||||
|
||||
COOKIES_FILE = Path(__file__).parent.parent / "credentials" / "youtube-cookies.txt"
|
||||
RATE_LIMIT_FILE = Path(__file__).parent.parent / "memory" / "youtube-rate-limit.json"
|
||||
SLEEP_BETWEEN = 20 # seconds between downloads
|
||||
MAX_PER_SESSION = 30
|
||||
|
||||
# Progressive retry delays (in hours)
|
||||
RETRY_DELAYS = [2, 4, 24]
|
||||
|
||||
def load_rate_limit_state() -> dict:
|
||||
"""Load rate limit state from JSON file."""
|
||||
if RATE_LIMIT_FILE.exists():
|
||||
try:
|
||||
with open(RATE_LIMIT_FILE) as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
pass
|
||||
return {"last_429": None, "retry_count": 0, "blocked_until": None}
|
||||
|
||||
def save_rate_limit_state(state: dict):
|
||||
"""Save rate limit state to JSON file."""
|
||||
RATE_LIMIT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(RATE_LIMIT_FILE, "w") as f:
|
||||
json.dump(state, f, indent=2, default=str)
|
||||
|
||||
def check_rate_limit() -> tuple[bool, str]:
|
||||
"""
|
||||
Check if we're still in a rate limit cooldown period.
|
||||
Returns: (can_proceed, message)
|
||||
"""
|
||||
state = load_rate_limit_state()
|
||||
|
||||
if state.get("blocked_until"):
|
||||
blocked_until = datetime.fromisoformat(state["blocked_until"])
|
||||
now = datetime.now()
|
||||
|
||||
if now < blocked_until:
|
||||
remaining = blocked_until - now
|
||||
hours = remaining.total_seconds() / 3600
|
||||
return False, f"⏳ Rate limited. Retry în {hours:.1f}h ({blocked_until.strftime('%H:%M')})"
|
||||
else:
|
||||
# Cooldown period expired, reset retry count
|
||||
state["retry_count"] = 0
|
||||
state["blocked_until"] = None
|
||||
save_rate_limit_state(state)
|
||||
|
||||
return True, "OK"
|
||||
|
||||
def record_rate_limit():
|
||||
"""Record a rate limit hit and calculate next retry time."""
|
||||
state = load_rate_limit_state()
|
||||
|
||||
retry_count = state.get("retry_count", 0)
|
||||
delay_hours = RETRY_DELAYS[min(retry_count, len(RETRY_DELAYS) - 1)]
|
||||
|
||||
blocked_until = datetime.now() + timedelta(hours=delay_hours)
|
||||
|
||||
state["last_429"] = datetime.now().isoformat()
|
||||
state["retry_count"] = retry_count + 1
|
||||
state["blocked_until"] = blocked_until.isoformat()
|
||||
|
||||
save_rate_limit_state(state)
|
||||
|
||||
return delay_hours, blocked_until
|
||||
|
||||
def clear_rate_limit():
|
||||
"""Clear rate limit state after successful downloads."""
|
||||
state = load_rate_limit_state()
|
||||
if state.get("retry_count", 0) > 0:
|
||||
state["retry_count"] = 0
|
||||
state["blocked_until"] = None
|
||||
save_rate_limit_state(state)
|
||||
|
||||
def download_subtitles(url: str, use_cookies: bool = True) -> tuple[bool, bool]:
|
||||
"""
|
||||
Download subtitles for a single video.
|
||||
Returns: (success, rate_limited)
|
||||
"""
|
||||
cmd = [
|
||||
"yt-dlp",
|
||||
"--remote-components", "ejs:github",
|
||||
"--write-auto-sub",
|
||||
"--sub-lang", "en,ro",
|
||||
"--skip-download",
|
||||
"-o", "temp_%(id)s",
|
||||
]
|
||||
|
||||
if use_cookies and COOKIES_FILE.exists():
|
||||
cmd.extend(["--cookies", str(COOKIES_FILE)])
|
||||
|
||||
cmd.append(url)
|
||||
|
||||
print(f"📥 Downloading: {url}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
combined_output = result.stdout + result.stderr
|
||||
|
||||
if "429" in combined_output:
|
||||
print(f" ⚠️ Rate limited (429)")
|
||||
return False, True
|
||||
elif "Writing video subtitles" in combined_output:
|
||||
print(f" ✅ Success")
|
||||
return True, False
|
||||
elif result.returncode == 0:
|
||||
print(f" ✅ Success")
|
||||
return True, False
|
||||
else:
|
||||
print(f" ❌ Error: {combined_output[:200]}")
|
||||
return False, False
|
||||
|
||||
def main():
|
||||
urls = sys.argv[1:]
|
||||
if not urls:
|
||||
print("Usage: python3 yt_download.py URL [URL2] ...")
|
||||
print("\nRate limit state:")
|
||||
state = load_rate_limit_state()
|
||||
print(f" Retry count: {state.get('retry_count', 0)}")
|
||||
print(f" Blocked until: {state.get('blocked_until', 'Not blocked')}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if we're in cooldown
|
||||
can_proceed, message = check_rate_limit()
|
||||
if not can_proceed:
|
||||
print(message)
|
||||
sys.exit(3) # Special exit code for cooldown
|
||||
|
||||
if len(urls) > MAX_PER_SESSION:
|
||||
print(f"⚠️ Max {MAX_PER_SESSION} per session. Processing first {MAX_PER_SESSION}.")
|
||||
urls = urls[:MAX_PER_SESSION]
|
||||
|
||||
has_cookies = COOKIES_FILE.exists()
|
||||
state = load_rate_limit_state()
|
||||
|
||||
print(f"🍪 Cookies: {'YES' if has_cookies else 'NO'}")
|
||||
print(f"⏱️ Sleep: {SLEEP_BETWEEN}s between videos")
|
||||
print(f"📊 Videos: {len(urls)}")
|
||||
print(f"🔄 Retry count: {state.get('retry_count', 0)}")
|
||||
print("-" * 40)
|
||||
|
||||
success = 0
|
||||
rate_limited = False
|
||||
|
||||
for i, url in enumerate(urls):
|
||||
ok, limited = download_subtitles(url, has_cookies)
|
||||
if ok:
|
||||
success += 1
|
||||
if limited:
|
||||
rate_limited = True
|
||||
delay_hours, blocked_until = record_rate_limit()
|
||||
print(f"🛑 Rate limit hit! Retry în {delay_hours}h ({blocked_until.strftime('%H:%M')})")
|
||||
print(f" {len(urls) - i - 1} videos rămase pentru retry.")
|
||||
break
|
||||
|
||||
if i < len(urls) - 1:
|
||||
print(f" 💤 Sleeping {SLEEP_BETWEEN}s...")
|
||||
time.sleep(SLEEP_BETWEEN)
|
||||
|
||||
print("-" * 40)
|
||||
print(f"✅ Done: {success}/{len(urls)} videos")
|
||||
|
||||
# Clear rate limit state if we had successful downloads without hitting limit
|
||||
if success > 0 and not rate_limited:
|
||||
clear_rate_limit()
|
||||
|
||||
if rate_limited:
|
||||
sys.exit(2) # Rate limit hit
|
||||
|
||||
sys.exit(0 if success == len(urls) else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user