344 lines
16 KiB
Python
344 lines
16 KiB
Python
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(
|
|
"<b>설치 안내:</b><br>"
|
|
# "<span style='color:#e1ffbb;'>1. main.py와 같은 경로의 <b>wrmc_ext</b> 폴더가 자동 복사됩니다.<br>"
|
|
"1. 설치할 브라우저를 모두 체크 후 <b>설치 시작</b>을 누르세요.<br>"
|
|
"2. 설치 후 브라우저별 단계별 안내를 참고하세요.</span>")
|
|
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": "② 오른쪽 상단의 <b>[개발자 모드]</b>를 ON(체크)로 변경하세요.",
|
|
"Edge": "② 오른쪽 상단의 <b>[개발자 모드]</b>를 ON(체크)로 변경하세요.",
|
|
"Whale": "② <span style='color:#ffc'><b>제일 하단의 [개발자 모드]</b>를 ON(체크)로 변경하세요.</span>"
|
|
}
|
|
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("③ <b>[압축해제된 확장 프로그램 로드] 또는 [압축해제된 확장앱 설치]</b> 버튼 클릭")
|
|
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("⑤ <b>주소창 옆 확장프로그램 아이콘을 클릭하여 로그인</b>")
|
|
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
|