name: Genera API JSON on: push: paths: - 'index.html' jobs: genera: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Genera JSON e RSS run: | python3 << 'PYEOF' import re, json, os from datetime import datetime, timezone from collections import defaultdict with open('index.html', 'r', encoding='utf-8') as f: html = f.read() def extract_dataset(html, year): m = re.search(rf'const DATA_{year} = ([\s\S]*?);$', html, re.MULTILINE) if not m: return [] raw = m.group(1) raw = re.sub(r'([{,]\s*)([A-Za-z_][A-Za-z0-9_]*)(\s*:)', r'\1"\2"\3', raw) raw = raw.replace("\\'", "'") try: return json.loads(raw) except Exception as e: print(f"Errore anno {year}: {e}") return [] all_records = [] for year in [2020, 2021, 2022, 2023, 2024, 2025, 2026]: recs = extract_dataset(html, year) for r in recs: r['_YEAR'] = year all_records.extend(recs) print(f" {year}: {len(recs)} record") m = re.search(r'const OFFICIAL_GROUP_CATALOG = \[(.*?)\];', html, re.DOTALL) groups = sorted(set(g.lower().strip() for g in re.findall(r'"([^"]+)"', m.group(1)))) generated = datetime.now(timezone.utc).isoformat() os.makedirs('api/v1/anno', exist_ok=True) def parse_gb(val): try: return float(val) except: return 0.0 # rivendicazioni.json records_out = [{"id":r.get("ID",""),"data":r.get("DATA",""),"anno":r["_YEAR"], "vittima":r.get("VITTIMA",""),"gruppo":r.get("GRUPPO",""),"link":r.get("LINK",""), "provincia":r.get("PROVINCIA",""),"regione":r.get("REGIONE",""), "dati_gb":parse_gb(r.get("DATI",0)),"note":r.get("NOTE","")} for r in all_records] with open('api/v1/rivendicazioni.json','w',encoding='utf-8') as f: json.dump({"source":"ransomnews.online","generated":generated, "total":len(records_out),"records":records_out}, f, ensure_ascii=False, indent=2) print(f"rivendicazioni.json -> {len(records_out)} record") # gruppi.json gcounts = defaultdict(int); ggb = defaultdict(float); gyears = defaultdict(set) for r in all_records: g = r.get("GRUPPO","").lower().strip() gcounts[g] += 1; ggb[g] += parse_gb(r.get("DATI",0)); gyears[g].add(r["_YEAR"]) groups_out = [{"nome":g,"rivendicazioni":gcounts.get(g,0), "dati_gb":round(ggb.get(g,0.0),2),"anni_attivi":sorted(gyears.get(g,[]))} for g in groups] with open('api/v1/gruppi.json','w',encoding='utf-8') as f: json.dump({"source":"ransomnews.online","generated":generated, "total":len(groups_out),"gruppi":groups_out}, f, ensure_ascii=False, indent=2) print(f"gruppi.json -> {len(groups_out)} gruppi") # stats.json by_year = defaultdict(list) for r in all_records: by_year[r["_YEAR"]].append(r) anni_stats = {str(y):{"rivendicazioni":len(recs), "dati_gb":round(sum(parse_gb(r.get("DATI",0)) for r in recs),2), "gruppi_attivi":len(set(r.get("GRUPPO","").lower() for r in recs))} for y,recs in sorted(by_year.items())} by_reg = defaultdict(int) for r in all_records: by_reg[r.get("REGIONE","Sconosciuta")] += 1 gcounts_items = sorted(gcounts.items(), key=lambda x: -x[1]) ggb_items = sorted(ggb.items(), key=lambda x: -x[1]) stats = {"source":"ransomnews.online","generated":generated, "totali":{"rivendicazioni":len(all_records),"gruppi_a_catalogo":len(groups), "dati_gb":round(sum(parse_gb(r.get("DATI",0)) for r in all_records),2)}, "per_anno":anni_stats, "top5_gruppi_per_rivendicazioni":[{"gruppo":g,"rivendicazioni":c} for g,c in gcounts_items[:5]], "top5_gruppi_per_gb":[{"gruppo":g,"dati_gb":round(v,2)} for g,v in ggb_items[:5]], "top5_regioni":[{"regione":r,"rivendicazioni":c} for r,c in sorted(by_reg.items(),key=lambda x:-x[1])[:5]]} with open('api/v1/stats.json','w',encoding='utf-8') as f: json.dump(stats, f, ensure_ascii=False, indent=2) print("stats.json -> ok") # anno/YYYY.json for y, recs in sorted(by_year.items()): recs_out = [{"id":r.get("ID",""),"data":r.get("DATA",""),"vittima":r.get("VITTIMA",""), "gruppo":r.get("GRUPPO",""),"link":r.get("LINK",""),"provincia":r.get("PROVINCIA",""), "regione":r.get("REGIONE",""),"dati_gb":parse_gb(r.get("DATI",0)),"note":r.get("NOTE","")} for r in recs] with open(f'api/v1/anno/{y}.json','w',encoding='utf-8') as f: json.dump({"source":"ransomnews.online","generated":generated, "anno":y,"total":len(recs_out),"records":recs_out}, f, ensure_ascii=False, indent=2) print(f"anno/{y}.json -> {len(recs_out)} record") # rss.xml def parse_date_rfc822(d): try: dt = datetime.strptime(d.strip(), "%d/%m/%Y") return dt.strftime("%a, %d %b %Y 00:00:00 +0000") except: return datetime.now(timezone.utc).strftime("%a, %d %b %Y 00:00:00 +0000") def escape_xml(s): return str(s).replace('&','&').replace('<','<').replace('>','>').replace('"','"') def sort_key(r): try: return datetime.strptime(r.get("DATA","01/01/2000").strip(), "%d/%m/%Y") except: return datetime.min sorted_recs = sorted(all_records, key=sort_key, reverse=True)[:20] now_rfc822 = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000") base_url = "https://ransomnews.github.io/RedACT" items = [] for r in sorted_recs: vittima = escape_xml(r.get("VITTIMA","")) gruppo = escape_xml(r.get("GRUPPO","").lower()) prov = escape_xml(r.get("PROVINCIA","--")) reg = escape_xml(r.get("REGIONE","--")) gb = parse_gb(r.get("DATI",0)) gb_str = (f"{gb:.2f}".replace(".",",")) if gb > 0 else "0" link = r.get("LINK","").strip() rec_id = r.get("ID", r.get("P","")) date_rfc = parse_date_rfc822(r.get("DATA","")) note = escape_xml(r.get("NOTE","")) desc = "Gruppo: " + gruppo + " | Provincia: " + prov + " | Regione: " + reg if gb > 0: desc += " | Dati: GB " + gb_str if note: desc += " | Nota: " + note item_link = ("https://" + link) if link else base_url guid = base_url + "/#" + str(rec_id) item = " \n" item += " " + vittima + "\n" item += " " + escape_xml(item_link) + "\n" item += " " + escape_xml(desc) + "\n" item += " " + date_rfc + "\n" item += " " + escape_xml(guid) + "\n" item += " " + gruppo + "\n" item += " " items.append(item) rss_lines = [ '', '', ' ', ' ransomNews - Rivendicazioni Italia', ' ' + base_url + '/', ' Monitoraggio e verifica degli attacchi ransomware contro organizzazioni italiane.', ' it', ' ransomNews - ransomnews.online', ' ' + now_rfc822 + '', ' ', ] rss_lines.extend(items) rss_lines.append(' ') rss_lines.append('') with open('rss.xml', 'w', encoding='utf-8') as f: f.write('\n'.join(rss_lines) + '\n') print(f"rss.xml -> {len(sorted_recs)} items") print("Done") PYEOF - name: Commit JSON e RSS aggiornati run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add api/v1/ rss.xml git diff --cached --quiet || git commit -m "Auto: aggiorna API JSON e RSS [skip ci]" git push