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