import sys import os import shutil import subprocess import ctypes import psutil import traceback from pathlib import Path from datetime import datetime from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QMessageBox, QCheckBox, QGroupBox, QSizePolicy, QScrollArea ) from PySide6.QtCore import Qt from PySide6.QtGui import QClipboard def get_resource_path(relative_path): """PyInstaller로 만든 실행파일에서 리소스 경로를 올바르게 반환합니다.""" try: # PyInstaller가 생성한 임시 폴더 base_path = sys._MEIPASS except Exception: # 개발 환경에서는 현재 스크립트의 디렉토리 base_path = os.path.dirname(os.path.abspath(sys.argv[0])) return os.path.join(base_path, relative_path) def write_log(msg, exc_info=None): try: log_path = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "log.txt") with open(log_path, "a", encoding="utf-8") as f: now = datetime.now().strftime("[%Y-%m-%d %H:%M:%S] ") f.write(now + msg + "\n") if exc_info: f.write(traceback.format_exc() + "\n") except Exception as e: print("로그 기록 실패:", e) def is_admin(): try: admin = ctypes.windll.shell32.IsUserAnAdmin() write_log(f"관리자 권한 체크: {admin}") return admin except Exception: write_log("관리자 권한 체크 중 오류", exc_info=True) return False BROWSER_INFOS = [ {"name": "Chrome", "display": "Chrome", "path": r"C:\Program Files\Google\Chrome\Application\chrome.exe", "url": "chrome://extensions/"}, {"name": "Whale", "display": "Whale (네이버 웨일)", "path": r"C:\Program Files\Naver\Naver Whale\Application\whale.exe", "url": "whale://extensions/"}, {"name": "Edge", "display": "Edge (마이크로소프트)", "path": r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", "url": "edge://extensions/"}, {"name": "Edge2", "display": "Edge (마이크로소프트)", "path": r"C:\Program Files\Microsoft\Edge\Application\msedge.exe", "url": "edge://extensions/"} ] def get_install_base_dir(): path = os.path.join(os.environ["LOCALAPPDATA"], "my_extension_installer") write_log(f"설치 베이스 디렉토리: {path}") return path def find_browsers(): browser_list = [] used = set() for info in BROWSER_INFOS: if os.path.exists(info["path"]) and info["name"] not in used: browser_list.append({ "name": info["name"] if info["name"] != "Edge2" else "Edge", "display": info["display"], "path": info["path"], "url": info["url"] }) used.add(info["name"]) write_log(f"탐지된 브라우저: {browser_list}") return browser_list def open_browser_extensions(browser): try: write_log(f"{browser['name']} 확장 프로그램 설정페이지 오픈 시도") if browser["name"] == "Chrome": is_running = any('chrome.exe' in (p.name().lower() if hasattr(p, "name") else "") for p in psutil.process_iter()) write_log(f"크롬 실행중 여부: {is_running}") if not is_running: subprocess.Popen([browser["path"], "--new-window", "chrome://extensions/"]) write_log("크롬 확장페이지 새창으로 오픈") else: msg = ("크롬이 이미 실행중입니다!\n\n" "주소창에 chrome://extensions/ 를 직접 입력하세요.") QMessageBox.information(None, "수동 안내", msg) write_log("크롬 실행중: 수동 안내") elif browser["name"] == "Whale": subprocess.Popen([browser["path"], "--new-window", "whale://extensions"]) write_log("웨일 확장페이지 새창으로 오픈") elif browser["name"] == "Edge": subprocess.Popen([browser["path"], "--new-window", "edge://extensions"]) write_log("엣지 확장페이지 새창으로 오픈") else: os.startfile(browser["path"]) write_log(f"{browser['name']} 실행") except Exception: write_log(f"{browser['name']} 확장 프로그램 페이지 오픈 중 오류", exc_info=True) class ExtensionInstaller(QWidget): def __init__(self): super().__init__() write_log("프로그램 시작") self.setWindowTitle("🧩 크롬/웨일/엣지 확장 자동설치 마법사") self.setMinimumSize(1100, 850) # 가로 900, 세로 700 self.setStyleSheet(""" QWidget { background: #20222B; color: #eef; font-size: 12px;} QGroupBox { border: 2px solid #67c1f5; border-radius: 12px; margin-top: 14px; font-weight: bold; padding:13px; } QPushButton { border-radius: 8px; background: #67c1f5; color: #181818; min-width: 130px; min-height: 44px; font-weight: bold; font-size: 12px; padding: 6px 16px;} QPushButton:hover { background: #38b6ff; } QLabel { font-size: 12px;} QCheckBox { font-size: 12px;} """) try: self.browsers = find_browsers() except Exception: self.browsers = [] write_log("브라우저 탐색 실패", exc_info=True) self.install_paths = {} self.init_ui() def init_ui(self): layout = QVBoxLayout() layout.setSpacing(20) title = QLabel("🧩 크롬/웨일/엣지 확장 자동설치 마법사") title.setStyleSheet("font-size: 22px; font-weight: bold; color: #58b7ff; padding:20px 0 0 0") layout.addWidget(title, alignment=Qt.AlignHCenter) guide = QLabel( "설치 안내:
" # "1. main.py와 같은 경로의 wrmc_ext 폴더가 자동 복사됩니다.
" "1. 설치할 브라우저를 모두 체크 후 설치 시작을 누르세요.
" "2. 설치 후 브라우저별 단계별 안내를 참고하세요.
") guide.setWordWrap(True) guide.setStyleSheet("margin-bottom:12px;") layout.addWidget(guide) browser_group = QGroupBox("설치할 브라우저 선택") browser_layout = QHBoxLayout() self.checkboxs = [] for b in self.browsers: cb = QCheckBox(f"{b['display']}") cb.setChecked(False) cb.setStyleSheet("margin-right:40px;") cb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.checkboxs.append(cb) browser_layout.addWidget(cb) browser_group.setLayout(browser_layout) layout.addWidget(browser_group) self.install_btn = QPushButton("🚀 설치 시작") self.install_btn.setMinimumHeight(54) self.install_btn.setStyleSheet("background:#38b6ff; color:#222; font-size:17px;") self.install_btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.install_btn.clicked.connect(self.do_install) layout.addWidget(self.install_btn) # 결과 부분 스크롤 지원(많이 길어질 경우) self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) self.result_group = QGroupBox("설치 완료 결과 (브라우저별 단계별 안내)") self.result_group.setStyleSheet("QGroupBox { border: 2px solid #38b6ff; margin-top:18px; padding:24px; }") self.result_layout = QVBoxLayout() self.result_group.setLayout(self.result_layout) self.result_group.hide() self.scroll_area.setWidget(self.result_group) layout.addWidget(self.scroll_area, stretch=1) help_label = QLabel("※ 복사 버튼 클릭시 클립보드에 저장됩니다. 폴더 경로는 꼭 붙여넣기 해주세요.\n" "※ 크롬이 이미 실행중이면 주소창에 직접 chrome://extensions/ 입력 필요.") help_label.setStyleSheet("color:#bbffaa; font-size:12px; margin:10px 0 0 0;") help_label.setWordWrap(True) layout.addWidget(help_label) self.setLayout(layout) def do_install(self): write_log("설치 버튼 클릭") DEVELOPER_MODE_GUIDE = { "Chrome": "② 오른쪽 상단의 [개발자 모드]를 ON(체크)로 변경하세요.", "Edge": "② 오른쪽 상단의 [개발자 모드]를 ON(체크)로 변경하세요.", "Whale": "② 제일 하단의 [개발자 모드]를 ON(체크)로 변경하세요." } try: checked = [cb.isChecked() for cb in self.checkboxs] selected = [b for b, ch in zip(self.browsers, checked) if ch] write_log(f"선택된 브라우저: {selected}") if not selected: QMessageBox.warning(self, "경고", "적어도 하나의 브라우저를 선택하세요.") write_log("브라우저 미선택 - 경고") return ext_folder = get_resource_path("wrmc_ext") write_log(f"wrmc_ext 폴더 경로: {ext_folder}") if not os.path.exists(ext_folder): QMessageBox.critical(self, "오류", f"확장 프로그램 폴더가 없습니다:\n{ext_folder}") write_log("wrmc_ext 폴더 없음 - 오류") return self.install_paths = {} for b in selected: base_dir = get_install_base_dir() os.makedirs(base_dir, exist_ok=True) folder_name = f"{b['name'].lower()}_extension" target_dir = os.path.join(base_dir, folder_name) try: if os.path.exists(target_dir): shutil.rmtree(target_dir) write_log(f"{b['name']} 기존 폴더 삭제: {target_dir}") shutil.copytree(ext_folder, target_dir) self.install_paths[b['name']] = target_dir write_log(f"{b['name']} 확장 폴더 복사 완료: {target_dir}") except Exception as e: QMessageBox.critical(self, "오류", f"{b['name']} 설치 폴더 복사 실패: {e}") write_log(f"{b['name']} 설치 폴더 복사 실패", exc_info=True) return while self.result_layout.count(): item = self.result_layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() # 단계별 안내(글자 안잘리도록, Expanding 적용, WordWrap, padding/여백 강화) for b in selected: browser_name = b['name'] browser_disp = b['display'] path = self.install_paths.get(browser_name, '') url = b['url'] box = QGroupBox(f"{browser_disp} 단계별 안내") box.setStyleSheet("QGroupBox {margin-bottom:24px;}") box_layout = QVBoxLayout() box_layout.setSpacing(18) # 1번 확장주소 복사 row1 = QHBoxLayout() lbl1 = QLabel("① 확장설정 페이지로 이동 :") lbl1.setWordWrap(True) lbl1.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) url_btn = QPushButton("확장주소 복사") url_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) url_btn.setMinimumWidth(160) url_lbl = QLabel(url) url_lbl.setStyleSheet("color:#5be0ff; font-weight:bold;") url_lbl.setTextInteractionFlags(Qt.TextSelectableByMouse) url_lbl.setWordWrap(True) url_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) row1.addWidget(lbl1) row1.addWidget(url_btn) row1.addWidget(url_lbl) row1.addStretch() # 2번 개발자모드 체크 row2 = QLabel(DEVELOPER_MODE_GUIDE.get(browser_name, "② [개발자 모드] 위치를 찾아 ON(체크)로 변경하세요.")) row2.setTextFormat(Qt.RichText) row2.setWordWrap(True) # 3번 압축해제된 확장프로그램 로드 row3 = QLabel("③ [압축해제된 확장 프로그램 로드] 또는 [압축해제된 확장앱 설치] 버튼 클릭") row3.setTextFormat(Qt.RichText) row3.setWordWrap(True) # 4번 폴더 선택 및 복사 row4 = QHBoxLayout() lbl4 = QLabel("④ 아래 폴더 경로 복사→ [폴더 선택]에 붙여넣기 -> 아무것도 안나오지만 선택버튼 클릭") lbl4.setWordWrap(True) lbl4.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) path_btn = QPushButton("폴더 경로 복사") path_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) path_btn.setMinimumWidth(160) path_lbl = QLabel(path) path_lbl.setStyleSheet("color:#eaff86; font-weight:bold;") path_lbl.setTextInteractionFlags(Qt.TextSelectableByMouse) path_lbl.setWordWrap(True) path_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # 5번 확장프로그램 아이콘을 클릭하여 로그인 row5 = QLabel("⑤ 주소창 옆 확장프로그램 아이콘을 클릭하여 로그인") row5.setTextFormat(Qt.RichText) row5.setWordWrap(True) row4.addWidget(lbl4) row4.addWidget(path_btn) row4.addWidget(path_lbl) row4.addStretch() url_btn.clicked.connect(lambda checked, u=url: self.copy_to_clipboard(u, "확장주소")) path_btn.clicked.connect(lambda checked, p=path: self.copy_to_clipboard(p, "폴더 경로")) box_layout.addLayout(row1) box_layout.addWidget(row2) box_layout.addWidget(row3) box_layout.addLayout(row4) box_layout.addWidget(row5) box.setLayout(box_layout) self.result_layout.addWidget(box) self.result_group.show() write_log("결과 UI 표시 완료") for b in selected: open_browser_extensions(b) msg = ( "설치가 완료되었습니다!\n\n" "브라우저별로 ①~④ 단계대로 진행하세요.\n" "※ 크롬이 이미 실행중이면 주소창에 직접 chrome://extensions/ 입력 필요!" ) QMessageBox.information(self, "설치 안내", msg) write_log("설치 안내 메시지 표시") except Exception: write_log("설치 프로세스 전체 예외 발생", exc_info=True) QMessageBox.critical(self, "오류", "예상치 못한 오류가 발생했습니다. log.txt를 확인해 주세요.") def copy_to_clipboard(self, value, kind): try: clipboard = QApplication.clipboard() clipboard.setText(value, QClipboard.Clipboard) write_log(f"{kind} 복사: {value}") QMessageBox.information(self, "복사 완료", f"{kind}가 클립보드에 복사되었습니다:\n{value}") except Exception: write_log(f"{kind} 복사 함수 예외", exc_info=True) QMessageBox.critical(self, "오류", f"{kind} 복사 중 오류가 발생했습니다.") if __name__ == "__main__": try: if not is_admin(): QMessageBox.critical(None, "권한 부족", "이 프로그램은 관리자 권한으로 실행해야 할 수 있습니다.\n(브라우저 접근/복사 등)") write_log("관리자 권한이 아님 - 재실행") if getattr(sys, 'frozen', False): script_path = sys.argv[0] else: script_path = os.path.abspath(__file__) ctypes.windll.shell32.ShellExecuteW( None, "runas", sys.executable, f'"{script_path}"', None, 1) sys.exit(0) app = QApplication(sys.argv) window = ExtensionInstaller() window.show() sys.exit(app.exec()) except Exception: write_log("메인 루프 전체 예외", exc_info=True) raise