Files
pgz-sport/scripts/hns_avatar_harvester.py
T
Damir Radulić 9b0ed43b92 RUSH 4-sub: filteri Klubovi/Sportaši + manifestacije card view + CRM v2 redesign
RUSH-1 Klubovi: list_klubovi() LEFT JOIN v_klubovi_financiranje (prima_pgz/rss/grad_rijeka, u_godisnjaku, ukupno_potpora). financiran=true sad OR od 3 davatelja (drop legacy klubovi.pgz_sufinanciran s 1312 false-positive). Sort sort=potpora&order=desc. UI: gold ukupno_potpora + tooltip + sortable kolona. Defaults priority view (financirani+godišnjak ON, hns_roster OFF). Test: priority=604, +hns=36, all=1641, financiran=15 sorted ZAMET 80208€.

RUSH-2 Sportaši: SELECT widened (slika_url, reprezentativac, kategoriziran, broj_dresa). avatarUrl() helper s 3 forme (apsolutni / lokalni /sport/uploads/avatars / initials fallback) + 32px circular avatar lijevo od imena. Test: priority=3712, no-priority=6086, +hns=1439, 1990-2000=645.

RUSH-3 Manifestacije: bugfix razina filter HTTP 500 (ambiguous column nakon LEFT JOIN savezi → m.razina/mjesto/organizator). 3 dropdowna iz meta (26 mjesta / 8 razina / 50 organizatora), view toggle 🃏 Kartice / 📋 Tablica (localStorage), 🔗 link ikona u card+table, source_url → Google fallback. Test: default=3, mjesto=Lošinj=2, razina=Tradicionalna=3, organizator=AK Kvarner=1.

RUSH-4 CRM v2: tab strip rewrite (10 taba u spec redu Članarine|Liječnički|Obrasci|E-mail|Accounts|Contacts|Leads|Opps|Activities|Cases, sticky+scrollable+gold underline). Pipeline → Opps tab. Novi e-mail templates tab (5 endpointa, 3 seed templates, +Novi modal). Card layout (.cgrid/.ccard) za Accounts/Contacts/Leads/Opps. Export dropdown 📥 ▾ CSV/XLSX(SheetJS CDN)/PDF na svaki tab. Test: /crm_v2 200, 10/10 tab labela, 10 Export dropdowna + 31 exportTab() handlera.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:33:20 +02:00

65 lines
2.5 KiB
Python

#!/usr/bin/env python3
# Fajl: hns_avatar_harvester.py | v1.0 | 05.05.2026
# Author: Damir Radulić
# Lokacija: /opt/pgz-sport/scripts/hns_avatar_harvester.py
# Svrha: Dohvati avatar URL za svakog igrača sa HNS profila
import os, time, re, json, sys
import psycopg2
import requests
from bs4 import BeautifulSoup
DSN = os.environ.get("RINET_DSN", "host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password=R1net2026!SecureDB#v7")
HEADERS = {"User-Agent": "Mozilla/5.0 (Ri.NET PGŽ Sport Bot)"}
conn = psycopg2.connect(DSN); conn.autocommit = True
def fetch_avatar(hns_id, slug=""):
url = f"https://semafor.hns.family/igraci/{hns_id}/"
if slug: url += f"{slug}/"
try:
r = requests.get(url, headers=HEADERS, timeout=15)
if r.status_code != 200: return None
soup = BeautifulSoup(r.text, "html.parser")
# Player photo selectors
for sel in [".playerPhoto img", ".player-photo img", ".playerHeader img", "img.player_photo"]:
img = soup.select_one(sel)
if img and img.get("src"):
src = img["src"]
if src.startswith("/"): src = "https://hns.family" + src
return src
# Generic: first img inside header
hdr = soup.select_one(".playerHeader, .player-header, .basic_info")
if hdr:
img = hdr.find("img")
if img and img.get("src"):
src = img["src"]
if src.startswith("/"): src = "https://hns.family" + src
return src
return None
except Exception as e:
return None
with conn.cursor() as cur:
cur.execute("""
SELECT id, hns_igrac_id, ime, prezime
FROM pgz_sport.clanovi
WHERE hns_igrac_id IS NOT NULL AND foto_url IS NULL
LIMIT 200
""")
rows = cur.fetchall()
print(f"Total: {len(rows)} igrača za avatar fetch")
hits = 0
for i, (cid, hns_id, ime, prezime) in enumerate(rows):
slug = f"{ime}-{prezime}".lower().replace("ć","c").replace("č","c").replace("š","s").replace("ž","z").replace("đ","d").replace(" ","-")
slug = re.sub(r"[^a-z0-9-]", "", slug)
avatar = fetch_avatar(hns_id, slug)
if avatar:
with conn.cursor() as cur:
cur.execute("UPDATE pgz_sport.clanovi SET foto_url=%s WHERE id=%s", (avatar, cid))
hits += 1
if i % 10 == 0: print(f" [{i+1}/{len(rows)}] {ime} {prezime}{avatar[:80]}")
time.sleep(0.5)
print(f"\nDONE: {hits}/{len(rows)} avatar URL-ova spremljen")