# ============================================================
# 출자공고 통합 모니터링 봇 v3
# ① 한국성장금융   - 【선정공고】【선정결과】 키워드 필터
# ② 한국벤처캐피탈협회 (KVCA) - 새 공고 전부 알림
# ③ 한국벤처투자 (KVIC)       - 새 공고 전부 알림
# ============================================================
# 실행: python combined_monitor.py
# 중단: Ctrl + C
# ============================================================

import re, schedule, time, requests, json, os
from datetime import datetime
from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright

# ── 대시보드 연동 설정 ──────────────────────────────────────
ANNOUNCEMENT_FILE = "announcements.json"   # announcement_server.py 와 공유
MAX_ANNOUNCEMENTS = 200                    # 최대 보관 개수

def 공고_파일저장(사이트명, 제목, 공고URL, 첨부파일_목록):
    """새 공고를 announcements.json 에 저장 → 대시보드 게시판에 표시됨"""
    try:
        if os.path.exists(ANNOUNCEMENT_FILE):
            with open(ANNOUNCEMENT_FILE, "r", encoding="utf-8") as f:
                data = json.load(f)
        else:
            data = []
        data.insert(0, {
            "id": f"{사이트명}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
            "site": 사이트명,
            "title": 제목,
            "url": 공고URL,
            "attachments": 첨부파일_목록 if 첨부파일_목록 else [],
            "detected_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        })
        if len(data) > MAX_ANNOUNCEMENTS:
            data = data[:MAX_ANNOUNCEMENTS]
        with open(ANNOUNCEMENT_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print(f"     📁 대시보드 저장 완료")
    except Exception as e:
        print(f"     ⚠️ 대시보드 저장 실패: {e}")

체크_주기_분 = 30
저장_파일    = "combined_seen.json"
성장금융_키워드 = ["【선정공고】", "【선정결과】"]

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

# ── 저장/불러오기 ──────────────────────────────────────────

def 본공고_불러오기():
    if os.path.exists(저장_파일):
        with open(저장_파일, "r", encoding="utf-8") as f:
            return set(json.load(f))
    return set()

def 본공고_저장(seen):
    with open(저장_파일, "w", encoding="utf-8") as f:
        json.dump(list(seen), f, ensure_ascii=False)

# ── 카카오 ────────────────────────────────────────────────

def 토큰_불러오기():
    if not os.path.exists("kakao_token.json"):
        print("❌ kakao_token.json 없음"); exit()
    with open("kakao_token.json", "r") as f:
        return json.load(f)

def 토큰_갱신(info):
    resp = requests.post("https://kauth.kakao.com/oauth/token", data={
        "grant_type": "refresh_token",
        "client_id": info["REST_API_KEY"],
        "refresh_token": info["refresh_token"],
    })
    if resp.status_code == 200:
        data = resp.json()
        info["access_token"] = data["access_token"]
        if "refresh_token" in data:
            info["refresh_token"] = data["refresh_token"]
        with open("kakao_token.json", "w") as f:
            json.dump(info, f, ensure_ascii=False, indent=2)
    return info

def 카카오_전송(사이트명, 제목, 공고URL, 첨부파일_목록):
    info = 토큰_불러오기()
    첨부_텍스트 = ""
    if 첨부파일_목록:
        첨부_텍스트 = "\n\n📎 첨부파일:\n" + "\n".join(
            f"• {f}" for f in 첨부파일_목록
        )
    메시지 = (
        f"🔔 [{사이트명}] 새 공고\n\n"
        f"📋 {제목}"
        f"{첨부_텍스트}\n\n"
        f"🔗 바로가기:\n{공고URL}"
    )
    template = {
        "object_type": "text",
        "text": 메시지[:1000],
        "link": {"web_url": 공고URL, "mobile_web_url": 공고URL}
    }

    def _send(token):
        return requests.post(
            "https://kapi.kakao.com/v2/api/talk/memo/default/send",
            headers={"Authorization": f"Bearer {token}"},
            data={"template_object": json.dumps(template, ensure_ascii=False)},
            timeout=10
        )

    resp = _send(info["access_token"])
    if resp.status_code == 401:
        info = 토큰_갱신(info)
        resp = _send(info["access_token"])
    return resp.status_code == 200

# ══════════════════════════════════════════════════════════
# ① 한국성장금융 (requests 방식)
# ══════════════════════════════════════════════════════════

def 성장금융_체크(본공고):
    BASE = "https://www.kgrowth.or.kr"
    URL  = "https://www.kgrowth.or.kr/notice.asp?str_type=1&tab=1"
    이름  = "한국성장금융"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        resp = requests.get(URL, headers=HEADERS, timeout=15)
        resp.encoding = "euc-kr"
        soup = BeautifulSoup(resp.text, "html.parser")

        공고_목록 = []
        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            if "notice_view.asp" not in href:
                continue
            제목 = a.get_text(strip=True)
            if len(제목) < 5:
                continue
            full = href if href.startswith("http") else BASE + "/" + href.lstrip("/")
            공고_목록.append({"제목": 제목, "URL": full, "ID": "kgrowth|" + href})

        print(f"  총 {len(공고_목록)}개 공고 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            # 키워드 필터 적용
            매칭 = next((k for k in 성장금융_키워드 if k in 항목["제목"]), None)
            if not 매칭:
                continue
            새공고 += 1
            print(f"  ✅ [{매칭}] {항목['제목'][:45]}")

            # 첨부파일 추출
            첨부이름 = []
            try:
                dr = requests.get(항목["URL"], headers=HEADERS, timeout=15)
                dr.encoding = "euc-kr"
                ds = BeautifulSoup(dr.text, "html.parser")
                for a in ds.find_all("a", href=True):
                    h = a.get("href", "")
                    if any(k in h.lower() for k in [".pdf",".hwp",".doc",".xls",".zip","download","file"]):
                        첨부이름.append(a.get_text(strip=True) or h.split("/")[-1])
            except:
                pass

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], 첨부이름)
            공고_파일저장(이름, 항목["제목"], 항목["URL"], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print("  — 해당 키워드 공고 없음")
    return 새공고

# ══════════════════════════════════════════════════════════
# ② 한국벤처캐피탈협회 KVCA (Playwright 방식)
# ══════════════════════════════════════════════════════════

def KVCA_체크(본공고, browser):
    URL  = "https://www.kvca.or.kr/Program/invest/list.html?a_gb=board&a_cd=8&a_item=0&sm=2_2_2"
    이름  = "한국벤처캐피탈협회"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)

        rows = page.query_selector_all("tbody tr")
        공고_목록 = []

        for row in rows:
            try:
                cells = row.query_selector_all("td")
                if len(cells) < 3:
                    continue
                번호 = cells[0].inner_text().strip()
                제목 = cells[2].inner_text().strip()
                if not 번호.isdigit() or len(제목) < 5:
                    continue
                공고_목록.append({"번호": 번호, "제목": 제목, "row": row, "ID": f"kvca|{번호}"})
            except:
                continue

        print(f"  총 {len(공고_목록)}개 공고 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            새공고 += 1
            print(f"  ✅ 새 공고: {항목['제목'][:45]}")

            # 행 클릭 → 실제 상세 URL 캡처 (listbody.html?po_no=XXX)
            detail_url = URL
            첨부이름 = []
            try:
                with page.expect_navigation(wait_until="networkidle", timeout=15000):
                    항목["row"].click()
                page.wait_for_timeout(1500)
                detail_url = page.url  # 실제 URL 캡처!
                print(f"     🔗 {detail_url[:70]}")

                for a in page.query_selector_all("a[href]"):
                    href = a.get_attribute("href") or ""
                    text = a.inner_text().strip()
                    if any(k in href.lower() for k in [".pdf",".hwp",".doc",".xls",".zip","download","file"]):
                        첨부이름.append(text or href.split("/")[-1])

                page.go_back(wait_until="networkidle", timeout=10000)
                page.wait_for_timeout(1500)
            except Exception as e:
                print(f"  ⚠️ 상세페이지 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], detail_url, 첨부이름)
            공고_파일저장(이름, 항목["제목"], detail_url, 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

        page.close()

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print("  — 새 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ③ 한국벤처투자 KVIC (requests 방식)
# ══════════════════════════════════════════════════════════

def KVIC_체크(본공고):
    BASE     = "https://kvic.or.kr"
    LIST_URL = "https://kvic.or.kr/notice/kvic-notice/investment-business-notice"
    이름      = "한국벤처투자"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        resp = requests.get(LIST_URL, headers=HEADERS, timeout=15)
        resp.encoding = "utf-8"
        soup = BeautifulSoup(resp.text, "html.parser")

        공고_목록 = []
        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            # board_view(숫자) 패턴 추출
            match = re.search(r"board_view\((\d+)\)", href)
            if not match:
                continue
            글ID = match.group(1)
            제목 = a.get_text(strip=True)
            if len(제목) < 5:
                continue
            detail_url = (
                f"{BASE}/notice/kvic-notice/investment-business-notice"
                f"?pageNo=1&searchCategory=&searchType=all&searchWord=&id={글ID}"
            )
            공고_목록.append({"글ID": 글ID, "제목": 제목, "URL": detail_url, "ID": f"kvic|{글ID}"})

        print(f"  총 {len(공고_목록)}개 공고 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            새공고 += 1
            print(f"  ✅ 새 공고: {항목['제목'][:45]}")

            # 상세 페이지에서 첨부파일 이름 추출
            첨부이름 = []
            try:
                dr = requests.get(항목["URL"], headers=HEADERS, timeout=15)
                dr.encoding = "utf-8"
                ds = BeautifulSoup(dr.text, "html.parser")
                file_area = ds.find(class_="board-view-file")
                if file_area:
                    # 파일명만 뽑기 (숫자. 파일명.확장자 패턴)
                    text = file_area.get_text(separator="\n")
                    for line in text.split("\n"):
                        line = line.strip()
                        if any(ext in line.lower() for ext in [".pdf",".hwp",".doc",".xls",".zip",".xlsx",".pptx"]):
                            # "바로보기", "내려받기" 제거
                            line = re.sub(r"(바로보기|내려받기)", "", line).strip()
                            if line:
                                첨부이름.append(line)
            except Exception as e:
                print(f"  ⚠️ 첨부파일 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], 첨부이름)
            공고_파일저장(이름, 항목["제목"], 항목["URL"], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print("  — 새 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ④ 한국산업은행 KDB (Playwright 방식)
# - 공지사항에서 "선정계획" 키워드 있는 새 공고만 알림
# ══════════════════════════════════════════════════════════

def KDB_체크(본공고, browser):
    BASE     = "https://kdb.co.kr"
    LIST_URL = "https://kdb.co.kr/CHBIPR23N00.act?_mnuId=IHIHIR0087&_PARAM_NAME=NEXT_DATA&_PARM_DATA=%7B%22SEARCH_CATE%22%3A%22ZZ%22%7D&_PARM_DATA_SIGN=1454450323"
    이름      = "한국산업은행"
    키워드    = "선정계획"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        page = browser.new_page()
        page.goto(LIST_URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)

        # 공지사항 목록에서 번호, 제목, 링크 추출
        rows = page.query_selector_all("tbody tr")
        공고_목록 = []

        for row in rows:
            try:
                cells = row.query_selector_all("td")
                if len(cells) < 3:
                    continue
                번호_텍스트 = cells[0].inner_text().strip().replace("공지", "").strip()
                제목_cell = cells[2]
                링크 = 제목_cell.query_selector("a")
                if not 링크:
                    continue
                제목 = 링크.inner_text().strip()
                href = 링크.get_attribute("href") or ""
                onclick = 링크.get_attribute("onclick") or ""

                if len(제목) < 5:
                    continue

            # 상세 URL은 클릭 시 이동하는 URL을 직접 사용
                full_url = href if href.startswith("http") else BASE + href if href.startswith("/") else LIST_URL

                공고_목록.append({
                    "번호": 번호_텍스트,
                    "제목": 제목,
                    "URL": full_url,
                    "링크엘": 링크,
                    "ID": f"kdb|{번호_텍스트}|{제목[:20]}"
                })
            except:
                continue

        print(f"  총 {len(공고_목록)}개 공지 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])

            # 키워드 필터
            if 키워드 not in 항목["제목"]:
                continue

            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")

            # 첨부파일 추출: 링크 클릭 후 상세 페이지로 이동
            첨부이름 = []
            detail_url = 항목["URL"]
            try:
                with page.expect_navigation(wait_until="networkidle", timeout=15000):
                    항목["링크엘"].click()
                detail_url = page.url
                print(f"     🔗 {detail_url[:80]}")

                # 첨부파일 목록 - 텍스트에서 파일명 추출
                file_text = page.inner_text("body")
                for line in file_text.split("\n"):
                    line = line.strip()
                    if any(ext in line.lower() for ext in [".pdf", ".hwp", ".doc", ".xls", ".zip", ".xlsx", ".pptx"]):
                        if len(line) > 3 and len(line) < 100:
                            첨부이름.append(line)

                page.go_back(wait_until="networkidle", timeout=10000)
                page.wait_for_timeout(1000)
            except Exception as e:
                print(f"  ⚠️ 상세페이지 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], detail_url, 첨부이름)
            공고_파일저장(이름, 항목["제목"], detail_url, 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

        page.close()

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — \'{키워드}\' 키워드 공지 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑤ 사학연금 (requests 방식)
# - 공지사항에서 "블라인드펀드 위탁운용사" 키워드 있는 새 공고만 알림
# ══════════════════════════════════════════════════════════

def TP_체크(본공고):
    BASE     = "https://www.tp.or.kr"
    LIST_URL = "https://www.tp.or.kr/tp-kr/bbs/i-151/list.do"
    이름      = "사학연금"
    키워드    = "블라인드펀드 위탁운용사"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        resp = requests.get(LIST_URL, headers=HEADERS, timeout=15)
        resp.encoding = "utf-8"
        soup = BeautifulSoup(resp.text, "html.parser")

        공고_목록 = []
        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            if "detail.do" not in href or "ntt_sn" not in href:
                continue
            제목 = a.get_text(strip=True)
            if len(제목) < 5:
                continue
            full_url = BASE + "/tp-kr/bbs/i-151/" + href.lstrip("./")
            # ntt_sn 추출 (고유 ID)
            ntt_sn = href.split("ntt_sn=")[-1].split("&")[0]
            공고_목록.append({
                "제목": 제목,
                "URL": full_url,
                "ID": f"tp|{ntt_sn}"
            })

        print(f"  총 {len(공고_목록)}개 공지 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])

            # 키워드 필터
            if 키워드 not in 항목["제목"]:
                continue

            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")

            # 첨부파일 추출
            첨부이름 = []
            try:
                dr = requests.get(항목["URL"], headers=HEADERS, timeout=15)
                dr.encoding = "utf-8"
                ds = BeautifulSoup(dr.text, "html.parser")
                for a in ds.find_all("a", href=True):
                    h = a.get("href", "")
                    t = a.get_text(strip=True)
                    if any(k in h.lower() for k in [".pdf", ".hwp", ".doc", ".xls", ".zip", ".xlsx", ".pptx", "download", "file", "atch"]):
                        if t and len(t) > 2:
                            첨부이름.append(t)
                        elif h:
                            첨부이름.append(h.split("/")[-1].split("?")[0])
            except Exception as e:
                print(f"  ⚠️ 첨부파일 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], 첨부이름)
            공고_파일저장(이름, 항목["제목"], 항목["URL"], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — \'{키워드}\' 키워드 공지 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑥ 신협중앙회 (requests 방식)
# - 공지사항에서 "블라인드펀드" 키워드 있는 새 공고만 알림
# ══════════════════════════════════════════════════════════

def NACUFOK_체크(본공고):
    BASE     = "https://cu.co.kr"
    LIST_URL = "https://cu.co.kr/cu/na/ntt/selectNttList.do?mi=100007&bbsId=1020"
    이름      = "신협중앙회"
    키워드    = "블라인드펀드"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        resp = requests.get(LIST_URL, headers=HEADERS, timeout=15)
        resp.encoding = "utf-8"
        soup = BeautifulSoup(resp.text, "html.parser")

        공고_목록 = []
        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            # selectNttInfo.do?nttSn=숫자 패턴
            if "selectNttInfo.do" not in href and "nttSn" not in href:
                continue
            제목 = a.get_text(strip=True)
            if len(제목) < 5:
                continue
            # nttSn 추출
            ntt_sn = ""
            if "nttSn=" in href:
                ntt_sn = href.split("nttSn=")[-1].split("&")[0]
            full_url = BASE + href if href.startswith("/") else href
            공고_목록.append({
                "제목": 제목,
                "URL": full_url,
                "ID": f"nacufok|{ntt_sn}"
            })

        print(f"  총 {len(공고_목록)}개 공지 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])

            if 키워드 not in 항목["제목"]:
                continue

            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")

            # 첨부파일 추출
            첨부이름 = []
            try:
                dr = requests.get(항목["URL"], headers=HEADERS, timeout=15)
                dr.encoding = "utf-8"
                ds = BeautifulSoup(dr.text, "html.parser")
                for fa in ds.find_all("a", href=True):
                    h = fa.get("href", "")
                    t = fa.get_text(strip=True)
                    if any(k in h.lower() for k in [".pdf",".hwp",".doc",".xls",".zip",".xlsx",".pptx","download","atch","file"]):
                        if t and len(t) > 2:
                            첨부이름.append(t)
            except Exception as e:
                print(f"  ⚠️ 첨부파일 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], 첨부이름)
            공고_파일저장(이름, 항목["제목"], 항목["URL"], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공지 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑦ 삼성자산운용 (requests 방식)
# - 공지사항에서 "대체투자상품" 키워드 있는 새 공고만 알림
# ══════════════════════════════════════════════════════════

def SAMSUNG_체크(본공고):
    BASE     = "https://www.samsungfund.com"
    LIST_URL = "https://www.samsungfund.com/fund/lounge/notice.do"
    이름      = "삼성자산운용"
    키워드    = "대체투자상품"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0

    try:
        resp = requests.get(LIST_URL, headers=HEADERS, timeout=15)
        resp.encoding = "utf-8"
        soup = BeautifulSoup(resp.text, "html.parser")

        공고_목록 = []
        for a in soup.find_all("a", href=True):
            href = a.get("href", "")
            # notice-view.do?no=숫자 패턴 (javascript: 방식)
            if "notice-view.do" not in href and "noticeView" not in href:
                continue
            제목 = a.get_text(strip=True)
            if len(제목) < 5:
                continue
            # no 값 추출
            no_val = ""
            if "no=" in href:
                no_val = href.split("no=")[-1].split("'")[0].split("&")[0].strip()
            # 상세 URL 구성
            detail_url = f"{BASE}/fund/lounge/notice-view.do?no={no_val}" if no_val else BASE + href
            공고_목록.append({
                "제목": 제목,
                "URL": detail_url,
                "ID": f"samsung|{no_val}"
            })

        print(f"  총 {len(공고_목록)}개 공지 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])

            if 키워드 not in 항목["제목"]:
                continue

            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")

            # 첨부파일 추출
            첨부이름 = []
            try:
                dr = requests.get(항목["URL"], headers=HEADERS, timeout=15)
                dr.encoding = "utf-8"
                ds = BeautifulSoup(dr.text, "html.parser")
                for fa in ds.find_all("a", href=True):
                    h = fa.get("href", "")
                    t = fa.get_text(strip=True)
                    if any(k in h.lower() for k in [".pdf",".hwp",".doc",".xls",".zip",".xlsx",".pptx","download","atch","file"]):
                        if t and len(t) > 2:
                            첨부이름.append(t)
            except Exception as e:
                print(f"  ⚠️ 첨부파일 오류: {e}")

            for f in 첨부이름:
                print(f"     📎 {f}")

            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], 첨부이름)
            공고_파일저장(이름, 항목["제목"], 항목["URL"], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공지 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑧ 우정사업본부 KPAMS (Playwright 방식)
# - 거래기관 선정공고에서 '위탁운용사' 키워드 있는 새 공고만 알림
# ══════════════════════════════════════════════════════════

def KPAMS_체크(본공고, browser):
    import re as _re
    MAIN_URL = 'https://www.kpasset.go.kr/'
    LIST_URL = 'https://www.kpasset.go.kr/board.es?mid=a10510000000'
    이름      = '우정사업본부'
    키워드    = '위탁운용사'
    BOARD_ID  = 'a10510000000'
    print(f'\n▶ {이름} 확인 중...')
    새공고 = 0

    try:
        page = browser.new_page()
        page.goto(MAIN_URL, wait_until='networkidle', timeout=30000)
        page.wait_for_timeout(2000)

        all_links = page.query_selector_all('a[href]')
        공고_목록 = []

        for link in all_links:
            href = link.get_attribute('href') or ''
            if BOARD_ID not in href or 'introBoardView' not in href:
                continue
            제목 = link.inner_text().strip()
            if len(제목) < 5:
                continue
            m = _re.search(r"introBoardView\([^,]+,\s*['\"]([0-9]+)['\"]", href)
            번호 = m.group(1) if m else href[-10:]
            공고_목록.append({
                '제목': 제목,
                '번호': 번호,
                'URL': LIST_URL,
                'ID': f'kpams|{번호}'
            })

        print(f'  총 {len(공고_목록)}개 공고 확인됨')

        for 항목 in 공고_목록:
            if 항목['ID'] in 본공고:
                continue
            본공고.add(항목['ID'])

            if 키워드 not in 항목['제목']:
                continue

            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")

            첨부이름 = []
            오늘_공고 = False
            오늘 = datetime.now().strftime('%Y-%m-%d')
            try:
                target_link = None
                for lnk in page.query_selector_all('a[href]'):
                    h = lnk.get_attribute('href') or ''
                    if BOARD_ID in h and 항목['번호'] in h:
                        target_link = lnk
                        break
                if target_link:
                    with page.expect_navigation(wait_until='networkidle', timeout=15000):
                        target_link.click()
                    page.wait_for_timeout(1500)
                    body_text = page.inner_text('body')

                    # 작성일시 확인 → 오늘 날짜인지 체크
                    for line in body_text.split('\n'):
                        if '작성일시' in line and 오늘 in line:
                            오늘_공고 = True
                            break
                        # 날짜만 있는 경우도 체크 (예: 2026-04-14)
                        if 오늘 in line:
                            오늘_공고 = True
                            break

                    if not 오늘_공고:
                        print(f'     — 오늘({오늘}) 공고 아님, 스킵')
                        page.go_back(wait_until='networkidle', timeout=10000)
                        page.wait_for_timeout(1000)
                        continue

                    # 첨부파일 추출
                    for line in body_text.split('\n'):
                        line = line.strip()
                        if any(ext in line.lower() for ext in ['.pdf','.hwp','.doc','.xls','.zip','.xlsx']):
                            if 3 < len(line) < 100:
                                첨부이름.append(line)

                    page.go_back(wait_until='networkidle', timeout=10000)
                    page.wait_for_timeout(1000)
            except Exception as e:
                print(f'  ⚠️ 상세페이지 오류: {e}')

            if not 오늘_공고:
                continue

            for f in 첨부이름:
                print(f'     📎 {f}')

            ok = 카카오_전송(이름, 항목['제목'], 항목['URL'], 첨부이름)
            공고_파일저장(이름, 항목['제목'], 항목['URL'], 첨부이름)
            print(f"     카카오: {'✅' if ok else '❌'}")

        page.close()

    except Exception as e:
        print(f'  ❌ 오류: {e}')

    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고




# ══════════════════════════════════════════════════════════
# ⑨ KIF (Playwright) - ktoa.or.kr 입찰공고
# ⚠️  현재 URL은 한국통신사업자연합회(KTOA) 사이트입니다.
#    "업무집행조합원" 키워드와 맞지 않을 수 있어요.
#    정확한 KIF URL 확인 후 LIST_URL을 변경해주세요.
# ══════════════════════════════════════════════════════════

def KIF_체크(본공고, browser):
    # KTOA(한국통신사업자연합회) 입찰공고 = KIF(Korea IT Fund) 출자공고
    LIST_URL = "https://www.ktoa.or.kr/ko/notice/bidding/list?pageIndex=1&pageRowSize=10"
    BASE     = "https://www.ktoa.or.kr"
    이름      = "KIF(KTOA)"
    키워드    = "업무집행조합원"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(LIST_URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)

        공고_목록 = []
        # a.unit.listView 하나에 번호+제목+날짜+조회수가 전부 담겨 있음
        listview_links = page.query_selector_all("a.unit.listView")
        for link in listview_links:
            raw = link.inner_text().strip()
            # 줄 분리 후 빈 줄 제거
            parts = [p.strip() for p in raw.split("\n") if p.strip()]
            if len(parts) < 2:
                continue
            번호 = parts[0]      # 첫 번째 = 번호
            제목 = parts[1]      # 두 번째 = 제목
            날짜 = parts[2] if len(parts) > 2 else ""
            if not 번호.isdigit() or len(제목) < 5:
                continue
            # 상세 URL: detailsKey=번호
            detail_url = (
                f"https://www.ktoa.or.kr/ko/notice/bidding/list"
                f"?pageIndex=1&pageRowSize=10&listRowSize=10"
                f"&detailsKey={번호}&q=&f=1"
            )
            공고_목록.append({
                "번호": 번호,
                "제목": 제목,
                "날짜": 날짜,
                "URL": detail_url,
                "ID": f"kif|{번호}"
            })

        print(f"  총 {len(공고_목록)}개 공고 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")

        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ⑩ IBK 기업은행 (Playwright)
# - "출자사업" 키워드
# ══════════════════════════════════════════════════════════

def IBK_체크(본공고, browser):
    URL   = "https://www.ibk.co.kr/cyber/noticeListCyber.ibk"
    이름   = "IBK기업은행"
    키워드 = "출자사업"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)
        공고_목록 = []
        rows = page.query_selector_all("tbody tr, .board-list tr, ul.list li")
        for row in rows:
            links = row.query_selector_all("a[href]")
            for a in links:
                href = a.get_attribute("href") or ""
                제목 = a.inner_text().strip()
                if len(제목) < 5:
                    continue
                full = ("https://www.ibk.co.kr" + href) if href.startswith("/") else href if href.startswith("http") else URL
                uid = href[-30:] if href else 제목[:20]
                공고_목록.append({"제목": 제목, "URL": full, "ID": f"ibk|{uid}"})
        # 중복 제거
        seen = set()
        공고_목록 = [x for x in 공고_목록 if not (x["ID"] in seen or seen.add(x["ID"]))]
        print(f"  총 {len(공고_목록)}개 공고 확인됨")
        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")
        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")
    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑪ SBA 서울산업진흥원 (Playwright)
# - "출자사업" 키워드
# ══════════════════════════════════════════════════════════

def SBA_체크(본공고, browser):
    URL   = "https://www.sba.seoul.kr/Pages/CustomerCenter/Notice.aspx"
    이름   = "SBA서울산업진흥원"
    키워드 = "출자사업"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)
        공고_목록 = []
        for a in page.query_selector_all("a[href]"):
            href = a.get_attribute("href") or ""
            제목 = a.inner_text().strip()
            if len(제목) < 5:
                continue
            if not any(c in href.lower() for c in ["detail", "view", "seq", "no=", "id=", "notice"]):
                continue
            full = ("https://www.sba.seoul.kr" + href) if href.startswith("/") else href if href.startswith("http") else URL
            uid = href[-40:] if href else 제목[:20]
            공고_목록.append({"제목": 제목, "URL": full, "ID": f"sba|{uid}"})
        seen = set()
        공고_목록 = [x for x in 공고_목록 if not (x["ID"] in seen or seen.add(x["ID"]))]
        print(f"  총 {len(공고_목록)}개 공고 확인됨")
        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")
        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")
    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑫ 하나벤처스 (Playwright)
# - "민간모펀드" 키워드
# ══════════════════════════════════════════════════════════

def 하나벤처스_체크(본공고, browser):
    URL   = "https://www.hanaventures.co.kr/main/community/notice"
    이름   = "하나벤처스"
    키워드 = "민간모펀드"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)
        공고_목록 = []
        for a in page.query_selector_all("a[href]"):
            href = a.get_attribute("href") or ""
            제목 = a.inner_text().strip()
            if len(제목) < 5:
                continue
            if not any(c in href.lower() for c in ["detail", "view", "notice", "seq", "no=", "id=", "/notice/"]):
                continue
            if href == URL or href.endswith("/notice"):
                continue
            full = ("https://www.hanaventures.co.kr" + href) if href.startswith("/") else href if href.startswith("http") else URL
            uid = href[-40:] if href else 제목[:20]
            공고_목록.append({"제목": 제목, "URL": full, "ID": f"hana|{uid}"})
        seen = set()
        공고_목록 = [x for x in 공고_목록 if not (x["ID"] in seen or seen.add(x["ID"]))]
        print(f"  총 {len(공고_목록)}개 공고 확인됨")
        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")
        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")
    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑬ 우리자산운용 (Playwright)
# - "위탁운용사" 키워드
# ══════════════════════════════════════════════════════════

def 우리자산_체크(본공고, browser):
    URL   = "https://www.wooriam.kr/customer/notice-list"
    이름   = "우리자산운용"
    키워드 = "위탁운용사"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)
        공고_목록 = []
        for a in page.query_selector_all("a[href]"):
            href = a.get_attribute("href") or ""
            제목 = a.inner_text().strip()
            if len(제목) < 5:
                continue
            if not any(c in href.lower() for c in ["detail", "view", "notice", "seq", "no=", "id=", "notice-"]):
                continue
            if href.endswith("/notice-list") or href == URL:
                continue
            full = ("https://www.wooriam.kr" + href) if href.startswith("/") else href if href.startswith("http") else URL
            uid = href[-40:] if href else 제목[:20]
            공고_목록.append({"제목": 제목, "URL": full, "ID": f"woori|{uid}"})
        seen = set()
        공고_목록 = [x for x in 공고_목록 if not (x["ID"] in seen or seen.add(x["ID"]))]
        print(f"  총 {len(공고_목록)}개 공고 확인됨")
        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")
        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")
    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑭ 신한자산운용 (Playwright)
# - "위탁운용사" 키워드
# ══════════════════════════════════════════════════════════

def 신한자산_체크(본공고, browser):
    URL   = "https://www.shinhanfund.com/ko/pc/board/notice"
    이름   = "신한자산운용"
    키워드 = "위탁운용사"
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)
        공고_목록 = []
        for a in page.query_selector_all("a[href]"):
            href = a.get_attribute("href") or ""
            제목 = a.inner_text().strip()
            if len(제목) < 5:
                continue
            if not any(c in href.lower() for c in ["detail", "view", "notice", "seq", "no=", "id=", "board/"]):
                continue
            if href.endswith("/notice") or href == URL:
                continue
            full = ("https://www.shinhanfund.com" + href) if href.startswith("/") else href if href.startswith("http") else URL
            uid = href[-40:] if href else 제목[:20]
            공고_목록.append({"제목": 제목, "URL": full, "ID": f"shinhan|{uid}"})
        seen = set()
        공고_목록 = [x for x in 공고_목록 if not (x["ID"] in seen or seen.add(x["ID"]))]
        print(f"  총 {len(공고_목록)}개 공고 확인됨")
        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            if 키워드 not in 항목["제목"]:
                continue
            새공고 += 1
            print(f"  ✅ [{키워드}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")
        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")
    if 새공고 == 0:
        print(f"  — '{키워드}' 키워드 공고 없음")
    return 새공고


# ══════════════════════════════════════════════════════════
# ⑮ 국민연금공단 NPS (Playwright)
# - "벤처펀드" 또는 "사모투자" 키워드
# ══════════════════════════════════════════════════════════

def NPS_체크(본공고, browser):
    LIST_URL = "https://fund.nps.or.kr/impa/dlnginstslctnpbanclist/getOHEF0017M0.do?menuId=MN24000492"
    이름      = "국민연금공단"
    키워드_목록 = ["벤처펀드", "사모투자"]
    print(f"\n▶ {이름} 확인 중...")
    새공고 = 0
    try:
        page = browser.new_page()
        page.goto(LIST_URL, wait_until="networkidle", timeout=30000)
        page.wait_for_timeout(2000)

        공고_목록 = []
        rows = page.query_selector_all("tbody tr")
        for row in rows:
            try:
                cells = row.query_selector_all("td")
                if len(cells) < 4:
                    continue
                번호 = cells[0].inner_text().strip()
                제목 = cells[1].inner_text().strip()
                등록일 = cells[3].inner_text().strip() if len(cells) > 3 else ""
                if len(제목) < 5:
                    continue
                # href에서 ID 추출: javascript:fnc_goBbsDetail('ZZ2026...', 'BS...')
                a = cells[1].query_selector("a")
                href = a.get_attribute("href") if a else ""
                import re as _re
                m = _re.search("fnc_goBbsDetail\\('([^']+)'", href or "")
                doc_id = m.group(1) if m else 번호
                공고_목록.append({
                    "번호": 번호,
                    "제목": 제목,
                    "등록일": 등록일,
                    "URL": LIST_URL,
                    "ID": f"nps|{doc_id}"
                })
            except:
                continue

        print(f"  총 {len(공고_목록)}개 공고 확인됨")

        for 항목 in 공고_목록:
            if 항목["ID"] in 본공고:
                continue
            본공고.add(항목["ID"])
            # 키워드 필터 (벤처펀드 OR 사모투자)
            매칭 = next((k for k in 키워드_목록 if k in 항목["제목"]), None)
            if not 매칭:
                continue
            새공고 += 1
            print(f"  ✅ [{매칭}] {항목['제목'][:45]}")
            ok = 카카오_전송(이름, 항목["제목"], 항목["URL"], [])
            공고_파일저장(이름, 항목["제목"], 항목["URL"], [])
            print(f"     카카오: {'✅' if ok else '❌'}")

        page.close()
    except Exception as e:
        print(f"  ❌ 오류: {e}")

    if 새공고 == 0:
        print(f"  — 키워드 공고 없음")
    return 새공고

# ══════════════════════════════════════════════════════════
# 메인 체크
# ══════════════════════════════════════════════════════════

def 전체_체크():
    지금 = datetime.now().strftime("%H:%M:%S")
    print(f"\n{'='*50}")
    print(f"[{지금}] 출자공고 통합 체크 시작")
    print(f"{'='*50}")

    본공고 = 본공고_불러오기()
    총합 = 0

    # ① 한국성장금융 (requests)
    총합 += 성장금융_체크(본공고)

    # ② KVCA + ④ KDB: Playwright 브라우저 1개 공유
    try:
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)

            # ② KVCA
            총합 += KVCA_체크(본공고, browser)

            # ④ KDB
            총합 += KDB_체크(본공고, browser)

            # ⑧ 우정사업본부 KPAMS
            총합 += KPAMS_체크(본공고, browser)

            # ⑨ KIF
            총합 += KIF_체크(본공고, browser)

            # ⑩ IBK 기업은행
            총합 += IBK_체크(본공고, browser)

            # ⑪ SBA 서울산업진흥원
            총합 += SBA_체크(본공고, browser)

            # ⑫ 하나벤처스
            총합 += 하나벤처스_체크(본공고, browser)

            # ⑬ 우리자산운용
            총합 += 우리자산_체크(본공고, browser)

            # ⑭ 신한자산운용
            총합 += 신한자산_체크(본공고, browser)

            # ⑮ 국민연금공단
            총합 += NPS_체크(본공고, browser)

            browser.close()
    except Exception as e:
        print(f"  ❌ Playwright 오류: {e}")

    # ③ KVIC (requests로 가능)
    총합 += KVIC_체크(본공고)

    # ⑤ 사학연금 (requests로 가능)
    총합 += TP_체크(본공고)

    # ⑥ 신협중앙회 (requests로 가능)
    총합 += NACUFOK_체크(본공고)

    # ⑦ 삼성자산운용 (requests로 가능)
    총합 += SAMSUNG_체크(본공고)

    본공고_저장(본공고)
    print(f"\n⏰ 완료. {체크_주기_분}분 후 재확인.")
    if 총합 == 0:
        print("  (새 공고 없음)")

# ══════════════════════════════════════════════════════════
# 실행
# ══════════════════════════════════════════════════════════

if __name__ == "__main__":
    print("=" * 50)
    print("  출자공고 통합 모니터링 봇 v9 (대시보드 연동)")
    print(f"  ① 한국성장금융   → 키워드 필터 (선정공고/선정결과)")
    print(f"  ② 한국벤처캐피탈협회 → 새 공고 전부")
    print(f"  ③ 한국벤처투자   → 새 공고 전부")
    print(f"  ④ 한국산업은행   → 키워드 필터 (선정계획)")
    print(f"  ⑤ 사학연금       → 키워드 필터 (블라인드펀드 위탁운용사)")
    print(f"  ⑥ 신협중앙회     → 키워드 필터 (블라인드펀드)")
    print(f"  ⑦ 삼성자산운용   → 키워드 필터 (대체투자상품)")
    print(f"  ⑧ 우정사업본부   → 키워드 필터 (위탁운용사)")
    print(f"  ⑨ KIF           → 키워드 필터 (업무집행조합원)")
    print(f"  ⑩ IBK기업은행    → 키워드 필터 (출자사업)")
    print(f"  ⑪ SBA서울산업진흥원→ 키워드 필터 (출자사업)")
    print(f"  ⑫ 하나벤처스     → 키워드 필터 (민간모펀드)")
    print(f"  ⑬ 우리자산운용   → 키워드 필터 (위탁운용사)")
    print(f"  ⑭ 신한자산운용   → 키워드 필터 (위탁운용사)")
    print(f"  ⑮ 국민연금공단   → 키워드 필터 (벤처펀드/사모투자)")
    print(f"  주기: {체크_주기_분}분마다")
    print("=" * 50)

    전체_체크()
    schedule.every(체크_주기_분).minutes.do(전체_체크)
    print(f"\n⏰ 스케줄 실행 중. 종료: Ctrl+C\n")
    while True:
        schedule.run_pending()
        time.sleep(60)
