Compare commits

..

No commits in common. "master" and "V1.1" have entirely different histories.
master ... V1.1

20 changed files with 65 additions and 2268 deletions

64
cp.py
View File

@ -1,64 +0,0 @@
import psutil
import os
import subprocess
import pygetwindow as gw
import glob # glob 모듈 import
def is_program_running(process_name):
"""프로세스 이름을 기준으로 프로그램 실행 여부 확인"""
for proc in psutil.process_iter(attrs=["pid", "name"]):
if process_name.lower() in proc.info["name"].lower():
return proc.info["pid"]
return None
def focus_window_by_title(title_start):
"""창 제목이 특정 문자열로 시작하는 창을 찾아 활성화"""
for window in gw.getAllWindows():
if window.title and window.title.startswith(title_start): # 창 제목이 조건에 맞는 경우
try:
window.activate()
print(f"프로그램 창으로 전환: {window.title}")
return True
except Exception as e:
print(f"창 활성화 실패: {e}")
return False
return False
def find_shortcut_in_start_menu(shortcut_name):
"""시작 메뉴에서 바로가기 찾기"""
user_start_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
all_users_start_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
for start_menu_path in [user_start_menu, all_users_start_menu]:
shortcut_path = glob.glob(os.path.join(start_menu_path, f"**\\{shortcut_name}.lnk"), recursive=True)
if shortcut_path:
return shortcut_path[0]
return None
def run_program(shortcut_path):
"""프로그램 실행"""
try:
subprocess.Popen([shortcut_path], shell=True)
print(f"프로그램 실행: {shortcut_path}")
except Exception as e:
print(f"프로그램 실행 실패: {e}")
if __name__ == "__main__":
program_name = "@카피맨.exe"
shortcut_name = "@카피맨"
window_title_start = "카피맨" # 창 제목이 "카피맨"으로 시작하는지 확인
# 프로그램 실행 여부 확인
pid = is_program_running(program_name)
if pid:
print(f"{program_name} 실행 중 (PID: {pid})")
# 실행 중인 프로그램 창으로 전환
if not focus_window_by_title(window_title_start):
print(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
else:
print(f"{program_name} 실행 중이 아님. 실행 시도 중...")
# 바로가기를 찾아 실행
shortcut_path = find_shortcut_in_start_menu(shortcut_name)
if shortcut_path:
run_program(shortcut_path)
else:
print(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")

620
main.py
View File

@ -1,23 +1,15 @@
import sys
import os
import sqlite3
import subprocess
import json
from datetime import datetime, time
from src.version import __version__
import xlrd
from PySide6.QtGui import QIcon
import logging
import traceback
import os
import sys
import winreg
import psutil
import subprocess
import pygetwindow as gw
import glob
import pandas as pd
from PySide6.QtWidgets import (
QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QButtonGroup, QRadioButton,
QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit,
QComboBox, QSpinBox, QCheckBox, QProgressBar, QTextEdit, QFileDialog, QWidget, QMessageBox, QDialog, QTableWidget, QTableWidgetItem, QAbstractItemView, QDialogButtonBox
)
from PySide6.QtCore import Qt, QThread, Signal, QSettings
@ -27,15 +19,13 @@ class BookmarkWorker(QThread):
log = Signal(str)
completed = Signal() # 작업 완료 시 호출
def __init__(self, bookmarks, folder_name, bookmarks_path, browser_path, selected_browser, remove_existing):
def __init__(self, bookmarks, folder_name, bookmarks_path, chrome_path, remove_existing):
super().__init__()
self.bookmarks = bookmarks
self.folder_name = folder_name
self.bookmarks_path = bookmarks_path
self.browser_path = browser_path
self.selected_browser = selected_browser
self.chrome_path = chrome_path
self.remove_existing = remove_existing
self.chunk_size = 1000
def run(self):
try:
@ -79,7 +69,7 @@ class BookmarkWorker(QThread):
}
# 하위 폴더 생성
chunk_size = self.chunk_size # 하위 폴더에 넣을 북마크 수
chunk_size = 100 # 하위 폴더에 넣을 북마크 수
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
# 하위 폴더 이름 생성
folder_name = f"거상북마크-{self.folder_name}-{idx}" # 하위 폴더 이름 (예: 거상북마크-중국-1)
@ -114,11 +104,12 @@ class BookmarkWorker(QThread):
self.log.emit("즐겨찾기 추가 작업이 완료되었습니다!")
# 작업 완료 시 브라우저 실행
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, browser_name=self.selected_browser)
# 작업 완료 후 '카피맨' 실행 및 창 활성화
self.run_and_focus_copyman()
# 작업 완료 시 크롬 실행
if os.path.exists(self.chrome_path):
subprocess.Popen([self.chrome_path, "chrome://bookmarks/"])
self.log.emit("크롬 북마크 관리 페이지를 열었습니다.")
else:
self.log.emit(f"크롬 실행 파일을 찾을 수 없습니다: {self.chrome_path}")
self.completed.emit()
@ -126,112 +117,6 @@ class BookmarkWorker(QThread):
self.log.emit(f"오류 발생: {str(e)}", exc_info=True)
self.progress.emit(0)
# 작업 완료 시 브라우저 실행
def run_browser_with_profile(self, browser_path, bookmarks_path, browser_name="브라우저"):
if "웨일" in browser_name:
# 실행 중인 웨일 프로세스를 강제 종료
for proc in psutil.process_iter(attrs=["pid", "name"]):
if "whale" in proc.info["name"].lower():
try:
proc.terminate() # 프로세스 종료
proc.wait(timeout=5) # 종료 대기
self.log.emit("웨일 브라우저의 기존 프로세스를 종료했습니다.")
except Exception as e:
self.log.emit(f"웨일 브라우저 프로세스 종료 중 오류 발생: {str(e)}")
if os.path.exists(browser_path):
# 북마크 경로에서 프로필 이름 추출
profile_directory = os.path.basename(os.path.dirname(bookmarks_path))
# 접속 URL 설정
if "크롬" in browser_name.lower():
bookmark_page = "chrome://bookmarks/"
elif "웨일" in browser_name.lower():
bookmark_page = "whale://bookmarks/"
else:
bookmark_page = "about:blank" # 기본값 (알 수 없는 브라우저)
if profile_directory: # 유효한 프로필 디렉토리가 있는 경우
subprocess.Popen([
browser_path,
f"--profile-directory={profile_directory}",
bookmark_page
])
self.log.emit(f"{browser_name} 북마크 관리 페이지를 '{profile_directory}' 프로필로 열었습니다.")
else:
# 프로필 경로를 찾을 수 없는 경우 기본 실행
subprocess.Popen([browser_path, bookmark_page])
self.log.emit(f"{browser_name} 북마크 관리 페이지를 기본 프로필로 열었습니다.")
else:
self.log.emit(f"{browser_name} 실행 파일을 찾을 수 없습니다: {browser_path}")
def run_and_focus_copyman(self):
"""
'카피맨' 프로그램을 실행하고 해당 창으로 포커스를 전환하는 메서드
"""
program_name = "@카피맨.exe"
shortcut_name = "@카피맨"
window_title_start = "카피맨" # 창 제목이 "카피맨"으로 시작하는지 확인
try:
# 프로그램 실행 여부 확인
pid = self.is_program_running(program_name)
if pid:
self.log.emit(f"{program_name} 실행 중 (PID: {pid})")
# 실행 중인 프로그램 창으로 전환
if not self.focus_window_by_title(window_title_start):
self.log.emit(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
else:
self.log.emit(f"{program_name} 실행 중이 아님. 실행 시도 중...")
# 바로가기를 찾아 실행
shortcut_path = self.find_shortcut_in_start_menu(shortcut_name)
if shortcut_path:
self.run_program(shortcut_path)
else:
self.log.emit(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")
except Exception as e:
error_traceback = traceback.format_exc()
self.log.emit(f"카피맨 실행 중 오류 발생: {str(e)}\n{error_traceback}")
def is_program_running(self, process_name):
"""프로세스 이름을 기준으로 프로그램 실행 여부 확인"""
for proc in psutil.process_iter(attrs=["pid", "name"]):
if process_name.lower() in proc.info["name"].lower():
return proc.info["pid"]
return None
def focus_window_by_title(self, title_start):
"""창 제목이 특정 문자열로 시작하는 창을 찾아 활성화"""
for window in gw.getAllWindows():
if window.title and window.title.startswith(title_start): # 창 제목이 조건에 맞는 경우
try:
window.activate()
self.log.emit(f"프로그램 창으로 전환: {window.title}")
return True
except Exception as e:
self.log.emit(f"창 활성화 실패: {e}")
return False
return False
def find_shortcut_in_start_menu(self, shortcut_name):
"""시작 메뉴에서 바로가기 찾기"""
user_start_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
all_users_start_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
for start_menu_path in [user_start_menu, all_users_start_menu]:
shortcut_path = glob.glob(os.path.join(start_menu_path, f"**\\{shortcut_name}.lnk"), recursive=True)
if shortcut_path:
return shortcut_path[0]
return None
def run_program(self, shortcut_path):
"""프로그램 실행"""
try:
subprocess.Popen([shortcut_path], shell=True)
self.log.emit(f"프로그램 실행: {shortcut_path}")
except Exception as e:
self.log.emit(f"프로그램 실행 실패: {e}")
def remove_existing_bookmarks(self, node):
"""
북마크 데이터를 재귀적으로 탐색하여 '거상북마크' 시작하는 모든 폴더를 제거합니다.
@ -283,25 +168,19 @@ class MainWindow(QMainWindow):
QMessageBox.critical(self, "비밀번호 오류", "비밀번호가 일치하지 않습니다. 프로그램을 종료합니다.")
sys.exit()
self.setWindowTitle(f"크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님) - v{__version__}")
self.setWindowTitle("크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님)")
self.setGeometry(300, 300, 800, 500)
# UI 구성
self.layout = QVBoxLayout()
self.buttons_layout = QHBoxLayout()
self.filter_layout = QHBoxLayout()
self.browser_selection_layout = QHBoxLayout()
# DB 입력 버튼
self.db_input_button = QPushButton("DB 입력")
self.db_input_button.setToolTip("엑셀 파일을 선택하여 DB에 저장합니다. 기존 DB를 제거하거나 추가로 데이터를 입력할 수 있습니다.")
self.db_input_button.clicked.connect(self.load_excel)
# DB 양식보기 버튼
self.sample_excel_button = QPushButton("추가양식 보기")
self.sample_excel_button.setToolTip("추가 양식을 확인합니다.")
self.sample_excel_button.clicked.connect(self.load_sample_excel)
# 기존 DB 제거 체크박스
self.remove_db_checkbox = QCheckBox("기존 DB 제거")
self.remove_db_checkbox.setToolTip("체크하면 기존 DB를 삭제하고 새롭게 데이터를 입력합니다.")
@ -312,22 +191,6 @@ class MainWindow(QMainWindow):
self.view_data_button.setEnabled(False)
self.view_data_button.clicked.connect(self.view_data)
# 브라우저 경로 설정 버튼
self.chrome_path_button = QPushButton("크롬 경로 설정")
self.chrome_path_button.clicked.connect(self.set_chrome_path)
self.whale_path_button = QPushButton("웨일 경로 설정")
self.whale_path_button.clicked.connect(self.set_whale_path)
# 브라우저 선택 라디오버튼
self.chrome_radio = QRadioButton("크롬")
self.whale_radio = QRadioButton("웨일")
self.whale_radio.clicked.connect(self.whale_radio_clicked)
self.browser_group = QButtonGroup()
self.browser_group.addButton(self.chrome_radio)
self.browser_group.addButton(self.whale_radio)
self.chrome_radio.setChecked(True) # 기본값: 크롬
# 드롭다운: 국가
self.country_label = QLabel("국가")
self.country_dropdown = QComboBox()
@ -340,22 +203,13 @@ class MainWindow(QMainWindow):
self.grade_dropdown.addItems(["일반", "파워", "빅파워", "랜덤"])
self.grade_dropdown.setCurrentText("랜덤")
# 스핀박스: 추출 몰 갯수
self.count_label = QLabel("추출 몰 갯수")
# 스핀박스: 갯수
self.count_label = QLabel("갯수")
self.count_spinbox = QSpinBox()
self.count_spinbox.setMinimum(1000)
self.count_spinbox.setMaximum(50000)
self.count_spinbox.setSingleStep(1000)
self.count_spinbox.setValue(1000)
# 스핀박스: 폴더당 북마크 갯수
self.fcount_label = QLabel("폴더당 북마크 갯수")
self.fcount_spinbox = QSpinBox()
self.fcount_spinbox.setMinimum(1000)
self.fcount_spinbox.setMaximum(50000)
self.fcount_spinbox.setSingleStep(100)
self.fcount_spinbox.setValue(100)
self.fcount_spinbox.valueChanged.connect(self.update_chunk_size)
self.count_spinbox.setMinimum(100)
self.count_spinbox.setMaximum(1000)
self.count_spinbox.setSingleStep(100)
self.count_spinbox.setValue(100)
# 기존 북마크 제거 체크박스
self.remove_existing_checkbox = QCheckBox("기존 북마크 제거")
@ -366,24 +220,6 @@ class MainWindow(QMainWindow):
self.run_button.setToolTip("선택된 필터 조건에 따라 DB에서 데이터를 가져와 북마크를 추가합니다.")
self.run_button.clicked.connect(self.run_task)
# 추출 옵션 체크박스
self.extract_based_checkbox = QCheckBox("추출 횟수 기반 추출")
self.extract_based_checkbox.setChecked(False) # 기본값 OFF
self.extract_based_checkbox.setToolTip("체크 시 추출 횟수가 낮은 데이터부터 추출합니다.")
self.extract_based_checkbox.stateChanged.connect(self.toggle_extract_options)
# 최대 추출 횟수 스핀박스
self.max_extract_label = QLabel("최대 추출 횟수")
self.max_extract_spinbox = QSpinBox()
self.max_extract_spinbox.setMinimum(1)
self.max_extract_spinbox.setMaximum(5)
self.max_extract_spinbox.setValue(1) # 기본값
# 리셋 버튼
self.reset_button = QPushButton("추출 횟수 초기화")
self.reset_button.setToolTip("DB의 모든 추출 횟수를 0으로 초기화합니다.")
self.reset_button.clicked.connect(self.reset_extract_count)
# 로그 박스
self.log_box = QTextEdit()
self.log_box.setReadOnly(True)
@ -401,39 +237,17 @@ class MainWindow(QMainWindow):
self.filter_layout.addWidget(self.count_label)
self.filter_layout.addWidget(self.count_spinbox)
self.filter_layout.addWidget(QLabel(" "))
self.filter_layout.addWidget(self.fcount_label)
self.filter_layout.addWidget(self.fcount_spinbox)
self.filter_layout.addWidget(QLabel(" "))
self.filter_layout.addWidget(self.remove_existing_checkbox)
# # 버튼 레이아웃 구성
# self.buttons_layout.addWidget(self.chrome_path_button)
# self.buttons_layout.addWidget(self.whale_path_button)
# 브라우저 선택 레이아웃 구성
self.browser_selection_layout.addWidget(QLabel("브라우저 선택:"))
self.browser_selection_layout.addWidget(self.chrome_radio)
self.browser_selection_layout.addWidget(self.whale_radio)
self.browser_selection_layout.addWidget(self.chrome_path_button)
self.browser_selection_layout.addWidget(self.whale_path_button)
# 버튼 레이아웃 구성
self.buttons_layout.addWidget(self.db_input_button)
self.buttons_layout.addWidget(self.sample_excel_button) # 추가 양식 버튼 추가
self.buttons_layout.addWidget(self.remove_db_checkbox)
self.buttons_layout.addWidget(self.view_data_button)
self.buttons_layout.addWidget(self.run_button)
self.filter_layout.addWidget(self.extract_based_checkbox)
self.filter_layout.addWidget(self.max_extract_label)
self.filter_layout.addWidget(self.max_extract_spinbox)
self.filter_layout.addWidget(self.reset_button)
# 메인 레이아웃 구성
self.layout.addLayout(self.filter_layout)
self.layout.addLayout(self.buttons_layout)
self.layout.addLayout(self.browser_selection_layout)
self.layout.addWidget(self.log_box)
self.layout.addWidget(self.progress_bar)
@ -442,120 +256,12 @@ class MainWindow(QMainWindow):
self.main_widget.setLayout(self.layout)
self.setCentralWidget(self.main_widget)
# 모던한 디자인을 위한 스타일시트 적용
self.setStyleSheet("""
QMainWindow { background-color: #f9f9f9; }
QPushButton { background-color: #4CAF50; color: white; padding: 6px 12px; border-radius: 4px; }
QPushButton:hover { background-color: #45a049; }
QLineEdit, QComboBox, QSpinBox, QCheckBox, QTextEdit { font-size: 14px; }
QLabel { font-size: 14px; }
QProgressBar { height: 20px; }
""")
# 상태 변수
self.db_path = "markets.db"
# DB 파일 확인
self.check_db()
# 브라우저 선택 복원
self.restore_browser_selection()
# 브라우저 선택 상태 변경 시 QSettings에 저장
self.chrome_radio.toggled.connect(self.save_browser_selection)
self.whale_radio.toggled.connect(self.save_browser_selection)
# 크롬/웨일 경로 초기화
self.chrome_path = self.get_browser_path("chrome") or "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
self.chrome_bookmarks_path = os.path.join(
self.get_specific_profile_path("chrome"), "Bookmarks"
) if self.get_specific_profile_path("chrome") else ""
self.whale_path = self.get_browser_path("whale") or "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
self.whale_bookmarks_path = os.path.join(
self.get_specific_profile_path("whale"), "Bookmarks"
) if self.get_specific_profile_path("whale") else ""
self.log(f"크롬 실행 파일 경로가 설정되었습니다: {self.chrome_path}")
self.log(f"웨일 실행 파일 경로가 설정되었습니다: {self.whale_path}")
self.log(f"크롬 프로파일 경로가 설정되었습니다: {self.chrome_bookmarks_path}")
self.log(f"웨일 프로파일 경로가 설정되었습니다: {self.whale_bookmarks_path}")
# 아이콘 설정 (추가된 부분)
self.set_app_icon()
def update_chunk_size(self):
"""폴더당 북마크 갯수 변경 시 chunk_size 업데이트"""
# self.log(f"폴더당 북마크 갯수가 변경되었습니다: {self.worker.chunk_size}")
def set_app_icon(self):
"""애플리케이션 아이콘을 설정합니다."""
base_dir = self.get_base_dir()
icon_path = os.path.join(base_dir, "bookmaker.ico")
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
else:
pass
# 브라우저 실행 파일 경로 가져오기
def get_browser_path(self, browser_name):
try:
reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f"{reg_path}\\{browser_name}.exe") as key:
return winreg.QueryValue(key, None)
except FileNotFoundError:
return None
# 브라우저 프로파일 경로 가져오기
def get_browser_profile_path(self, browser_name):
if browser_name == "chrome":
return os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data")
elif browser_name == "whale":
return os.path.expandvars(r"%LOCALAPPDATA%\Naver\Naver Whale\User Data")
return None
# 기본 프로파일 경로 가져오기
def get_specific_profile_path(self, browser_name, profile_name="Default"):
"""
주어진 브라우저 이름에 따라 프로필 경로를 찾고 Bookmarks 파일이 존재하는 폴더를 반환합니다.
:param browser_name: 브라우저 이름 ("chrome" 또는 "whale")
:param profile_name: 기본 프로필 이름 (기본값은 "Default")
:return: Bookmarks 파일이 존재하는 프로필 경로 또는 None
"""
base_profile_path = self.get_browser_profile_path(browser_name)
if not base_profile_path:
return None
# 확인 순서: Default -> Profile 1 -> Profile 2 -> ...
profile_names = [profile_name] + [f"Profile {i}" for i in range(1, 10)] # 최대 10개의 프로필 확인
for profile in profile_names:
specific_profile_path = os.path.join(base_profile_path, profile)
bookmarks_file = os.path.join(specific_profile_path, "Bookmarks")
if os.path.exists(bookmarks_file):
self.log(f"'{profile}' 프로필에서 Bookmarks 파일을 찾았습니다: {bookmarks_file}")
return specific_profile_path
# Bookmarks 파일이 없는 경우 None 반환
self.log(f"'{browser_name}' 브라우저에서 유효한 Bookmarks 파일을 찾을 수 없습니다.")
return None
def set_chrome_path(self):
"""크롬 실행 파일 경로 설정"""
file_path, _ = QFileDialog.getOpenFileName(self, "크롬 실행 파일 선택", "", "Executable Files (*.exe)")
if file_path:
self.chrome_path = file_path
self.log(f"크롬 실행 파일 경로가 설정되었습니다: {file_path}")
def set_whale_path(self):
"""웨일 실행 파일 경로 설정"""
file_path, _ = QFileDialog.getOpenFileName(self, "웨일 실행 파일 선택", "", "Executable Files (*.exe)")
if file_path:
self.whale_path = file_path
self.log(f"웨일 실행 파일 경로가 설정되었습니다: {file_path}")
def show_password_dialog(self):
"""비밀번호 입력 창을 표시"""
dialog = QDialog(self)
@ -601,24 +307,6 @@ class MainWindow(QMainWindow):
else:
self.password = None
def save_browser_selection(self):
"""사용자가 선택한 브라우저를 QSettings에 저장"""
if self.chrome_radio.isChecked():
self.settings.setValue("selected_browser", "chrome")
self.log("사용자가 크롬 브라우저를 선택했습니다.")
elif self.whale_radio.isChecked():
self.settings.setValue("selected_browser", "whale")
self.log("사용자가 웨일 브라우저를 선택했습니다.")
def restore_browser_selection(self):
"""QSettings에서 저장된 브라우저 선택 상태를 복원"""
selected_browser = self.settings.value("selected_browser", "chrome") # 기본값은 크롬
if selected_browser == "chrome":
self.chrome_radio.setChecked(True)
elif selected_browser == "whale":
self.whale_radio.setChecked(True)
self.log(f"저장된 브라우저 선택을 복원했습니다: {selected_browser}")
def log(self, message, exc_info=False):
"""
메시지를 로그에 출력하고, GUI 로그 박스에도 표시
@ -641,38 +329,6 @@ class MainWindow(QMainWindow):
self.log("DB 파일이 로드되었습니다.")
self.view_data_button.setEnabled(True)
def get_base_dir(self):
"""
실행 환경에 따라 base_dir을 설정하는 메서드.
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
"""
if getattr(sys, 'frozen', False): # 패키징된 경우
base_dir = os.path.dirname(sys.executable)
internal_dir = os.path.join(base_dir, 'lib', 'src') # lib 디렉토리 포함
if os.path.exists(internal_dir): # lib 디렉토리가 존재하면 base_dir로 설정
return internal_dir
else: # 일반 Python 실행 환경
base_dir = os.path.dirname(os.path.abspath(__file__))
debug_dir = os.path.join(base_dir, 'src') # lib 디렉토리 포함
return debug_dir
def load_sample_excel(self):
"""'추가양식.xlsx' 파일을 엽니다."""
try:
base_dir = self.get_base_dir()
file_path = os.path.join(base_dir, "추가양식.xlsx")
if os.path.exists(file_path):
os.startfile(file_path)
self.log(f"추가 양식 파일 열기: {file_path}")
else:
self.log(f"추가 양식 파일을 찾을 수 없습니다: {file_path}")
QMessageBox.warning(self, "파일 없음", "추가 양식 파일을 찾을 수 없습니다.")
except Exception as e:
self.log(f"추가 양식 파일 열기 중 오류 발생: {str(e)}", exc_info=True)
QMessageBox.critical(self, "오류", f"추가 양식 파일을 열 수 없습니다: {str(e)}")
def load_excel(self):
file_path, _ = QFileDialog.getOpenFileName(self, "엑셀 파일 선택", "", "Excel Files (*.xlsx *.xls)")
if not file_path:
@ -684,16 +340,8 @@ class MainWindow(QMainWindow):
# 파일 확장자 확인
ext = os.path.splitext(file_path)[-1].lower()
# 열 이름 매핑 정의
column_name_mapping = {
'mall_url': ['mall_url(필수)', 'mall_url', '마켓링크', 'URL'],
'mall_name': ['mall_name', '셀러명', '판매자명'],
'mall_grade': ['mall_grade', '등급', '판매자등급'],
'country': ['country', '국가', '지역']
}
required_columns = ['mall_url']
all_expected_columns = list(column_name_mapping.keys())
# 필수 열 이름 정의
required_columns = ['country', 'mall_grade', 'mall_name', 'mall_url']
# 엑셀 파일 읽기
if ext == ".xls":
@ -716,34 +364,22 @@ class MainWindow(QMainWindow):
self.log("지원되지 않는 파일 형식입니다. .xls 또는 .xlsx 파일을 선택하세요.")
return
# 엑셀 열 이름과 정의된 이름 매핑
rename_dict = {}
excel_columns = df.columns.tolist()
for internal_name, possible_names in column_name_mapping.items():
for excel_col in excel_columns:
if excel_col in possible_names:
rename_dict[excel_col] = internal_name
break # 하나라도 매칭되면 다음 내부 이름으로 이동
# 데이터프레임 열 이름 변경
df.rename(columns=rename_dict, inplace=True)
# 필수 열이 있는지 확인
if 'mall_url' not in df.columns:
self.log(f"엑셀 파일에 필수 열({column_name_mapping['mall_url']}) 중 하나라도 있어야 합니다.")
# 엑셀에서 가져온 열과 필수 열 비교
available_columns = [col for col in required_columns if col in df.columns]
if not available_columns:
self.log("엑셀 파일에 필수 열이 없습니다. 필요한 열: " + ", ".join(required_columns))
return
# 필요한 열만 선택 (매핑된 내부 이름 사용)
available_columns = [col for col in all_expected_columns if col in df.columns]
df = df[available_columns].copy()
# 필요한 열만 가져오기
df = df[available_columns]
# 누락된 열은 빈 값으로 채우기
for col in all_expected_columns:
# 누락된 열은 빈 값으로 추가
for col in required_columns:
if col not in df.columns:
df[col] = ""
df[col] = "" # 누락된 열은 빈 값으로 채움
# 열 순서 설정 (데이터베이스 테이블 순서와 맞춤)
df = df[['country', 'mall_grade', 'mall_name', 'mall_url']]
# 열 이름을 정렬하여 설정
df = df[required_columns]
# datetime.time 타입 데이터를 문자열로 변환
def convert_time_to_string(x):
@ -761,7 +397,6 @@ class MainWindow(QMainWindow):
df['mall_url'] = df['mall_url'].apply(convert_time_to_string)
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
if self.remove_db_checkbox.isChecked():
# 기존 DB 제거 및 새 테이블 생성
@ -772,55 +407,27 @@ class MainWindow(QMainWindow):
country TEXT,
mall_grade TEXT,
mall_name TEXT,
mall_url TEXT,
extract_count INTEGER DEFAULT 0
mall_url TEXT
)
""")
self.log("기존 DB가 제거되었습니다. 새롭게 생성되었습니다.")
# 기존 DB에 `extract_count` 필드 추가
try:
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
except sqlite3.OperationalError:
# 컬럼이 이미 있는 경우 무시
pass
# 데이터 저장, 중복 방지
df.drop_duplicates(subset=['country', 'mall_grade', 'mall_name', 'mall_url'], inplace=True)
added_count = 0
skipped_count = 0
# 데이터 저장 및 중복 방지
# id 열을 자동 생성하지 않으므로, 기존 테이블 스키마를 유지하면서 데이터를 삽입
for _, row in df.iterrows():
mall_url = row['mall_url']
cursor.execute("SELECT mall_url FROM markets WHERE mall_url=?", (mall_url,))
existing_mall = cursor.fetchone()
if not existing_mall:
cursor.execute("""
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
VALUES (?, ?, ?, ?)
""", (row['country'], row['mall_grade'], row['mall_name'], mall_url))
self.log(f"새로운 몰 추가: {mall_url}")
added_count += 1
else:
self.log(f"중복된 몰 건너뛰기: {mall_url}")
skipped_count += 1
conn.execute("""
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
VALUES (?, ?, ?, ?)
""", (row['country'], row['mall_grade'], row['mall_name'], row['mall_url']))
conn.commit()
conn.close()
self.log("DB 저장 완료")
self.show_load_excel_result_message(added_count, skipped_count)
except Exception as e:
self.log(f"엑셀 파일을 불러오는 중 에러 발생: {str(e)}", exc_info=True)
def show_load_excel_result_message(self, added_count, skipped_count):
"""엑셀 로드 완료 후 결과를 메시지 박스로 보여줍니다."""
QMessageBox.information(
self,
"엑셀 처리 완료",
f"{added_count}개의 몰이 추가되었고, {skipped_count}개의 중복된 몰은 제외되었습니다."
)
def view_data(self):
try:
conn = sqlite3.connect(self.db_path)
@ -859,14 +466,6 @@ class MainWindow(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "오류", f"DB 데이터를 불러오는 중 오류 발생: {e}", exc_info=True)
# 웨일 라디오 버튼 클릭 이벤트 처리 메서드
def whale_radio_clicked(self):
# 메세지 창 띄우기
QMessageBox.warning(
self,
"주의!!",
"웨일의 경우 현재 실행된 웨일브라우저를 강제종료합니다."
)
def sort_table(self, table, column_index):
"""
@ -908,123 +507,43 @@ class MainWindow(QMainWindow):
else:
self.log("수정이 취소되었습니다.")
def ensure_extract_count_column(self, conn):
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(markets)")
columns = [row[1] for row in cursor.fetchall()]
if "extract_count" not in columns:
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
def toggle_extract_options(self):
if self.extract_based_checkbox.isChecked():
self.max_extract_spinbox.setEnabled(True)
else:
self.max_extract_spinbox.setEnabled(False)
def run_task(self):
country = self.country_dropdown.currentText()
grade = self.grade_dropdown.currentText()
count = self.count_spinbox.value()
remove_existing = self.remove_existing_checkbox.isChecked()
extract_based = self.extract_based_checkbox.isChecked()
max_extract = self.max_extract_spinbox.value()
conn = sqlite3.connect(self.db_path)
query = "SELECT mall_name AS name, mall_url AS url FROM markets WHERE 1=1"
# 국가 필터
if country != "랜덤":
query += f" AND country = '{country}'"
# 등급 필터
if grade != "랜덤":
query += f" AND mall_grade = '{grade}'"
# 랜덤 정렬 추가
query += " ORDER BY RANDOM()"
# 갯수 제한 추가
query += f" LIMIT {count}"
try:
# 추출 횟수 필드 확인 및 추가
self.ensure_extract_count_column(conn)
# 기본 쿼리
query = "SELECT id, mall_name AS name, mall_url AS url FROM markets WHERE 1=1"
# 국가 필터
if country != "랜덤":
query += f" AND country = '{country}'"
# 등급 필터
if grade != "랜덤":
query += f" AND mall_grade = '{grade}'"
# 추출 횟수 기반 옵션 처리
if extract_based:
query += " AND extract_count < ?"
query += " ORDER BY extract_count ASC, RANDOM()"
else:
query += " ORDER BY RANDOM()"
query += f" LIMIT {count}"
# 추출
if extract_based:
df = pd.read_sql_query(query, conn, params=(max_extract,))
else:
df = pd.read_sql_query(query, conn)
if df.empty:
# 추출 가능한 데이터가 없는 경우 리셋
if extract_based:
reply = QMessageBox.question(
self,
"데이터 초기화",
"추출 가능한 데이터가 없습니다. 모든 추출 횟수를 초기화하시겠습니까?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
self.reset_extract_count()
else:
return
else:
QMessageBox.warning(self, "추출 실패", "DB에 추출 가능한 데이터가 없습니다.")
return
df = pd.read_sql_query(query, conn)
conn.close()
self.bookmarks = df.to_dict("records")
# 추출 횟수 업데이트
for record in df.to_dict("records"):
conn.execute("UPDATE markets SET extract_count = extract_count + 1 WHERE id = ?", (record["id"],))
conn.commit()
self.log(f"{len(self.bookmarks)}개의 북마크를 추출했습니다.")
except Exception as e:
self.log(f"DB 쿼리 실행 중 오류 발생: {str(e)}", exc_info=True)
conn.close()
return
conn.close()
folder_name = f"거상북마크-{grade}"
self.bookmarks_path = os.path.expanduser(r"~\AppData\Local\Google\Chrome\User Data\Default\Bookmarks")
self.chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
# 사용자가 선택한 브라우저에 따라 경로 설정
if self.chrome_radio.isChecked():
selected_bookmarks_path = self.chrome_bookmarks_path
selected_browser_path = self.chrome_path
selected_browser = '크롬'
self.log("크롬 브라우저가 선택되었습니다.")
elif self.whale_radio.isChecked():
selected_bookmarks_path = self.whale_bookmarks_path
selected_browser_path = self.whale_path
selected_browser = '웨일'
self.log("웨일 브라우저가 선택되었습니다.")
else:
QMessageBox.warning(self, "브라우저 선택 오류", "브라우저를 선택해주세요.")
return
# 선택된 브라우저 경로 유효성 확인
if not os.path.exists(selected_browser_path):
QMessageBox.warning(self, "브라우저 경로 오류", "선택된 브라우저 실행 파일 경로가 유효하지 않습니다. 경로를 설정해주세요.")
return
if not os.path.exists(selected_bookmarks_path):
QMessageBox.warning(self, "북마크 경로 오류", "선택된 브라우저의 북마크 경로가 유효하지 않습니다. 경로를 설정해주세요.")
return
self.worker = BookmarkWorker(self.bookmarks, folder_name, selected_bookmarks_path, selected_browser_path, selected_browser, remove_existing)
self.worker.chunk_size = self.fcount_spinbox.value()
self.worker = BookmarkWorker(self.bookmarks, folder_name, self.bookmarks_path, self.chrome_path, remove_existing)
self.worker.progress.connect(self.progress_bar.setValue)
self.worker.log.connect(self.log)
self.worker.completed.connect(self.task_completed)
@ -1034,25 +553,6 @@ class MainWindow(QMainWindow):
self.log("작업이 완료되었습니다!")
QMessageBox.information(self, "완료", "즐겨찾기 추가 작업이 완료되었습니다.")
def reset_extract_count(self):
conn = sqlite3.connect(self.db_path)
try:
# `extract_count` 필드가 없으면 추가
try:
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
except sqlite3.OperationalError:
# 이미 필드가 존재하는 경우 무시
pass
# 모든 추출 횟수를 0으로 초기화
conn.execute("UPDATE markets SET extract_count = 0")
conn.commit()
self.log("모든 추출 횟수가 초기화되었습니다.")
QMessageBox.information(self, "초기화 완료", "모든 추출 횟수가 초기화되었습니다.")
except Exception as e:
self.log(f"추출 횟수 초기화 중 오류 발생: {str(e)}", exc_info=True)
finally:
conn.close()
def main():

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,10 @@
from cx_Freeze import setup, Executable
import os
import sys
from src.version import __version__
# 애플리케이션 이름과 버전
application_name = "크롬 즐겨찾기 추가 프로그램"
application_version = __version__
application_version = "1.0.0"
# 실행 파일 생성 설정
base = None
@ -17,16 +16,14 @@ main_file = "main.py"
# 필요한 추가 파일 설정 (예: 리소스 파일, 아이콘 등)
include_files = [
("markets.db", "markets.db"),
("추가양식.xlsx", "lib/src/추가양식.xlsx"),
("bookmaker.ico", "lib/src/bookmaker.ico"), # 아이콘 파일 포함 확인
("markets.db", "markets.db"), # 데이터베이스 파일 (필요 시)
]
# 빌드 옵션
build_options = {
"packages": ["os", "sys", "sqlite3", "subprocess", "psutil", "pygetwindow", "glob","json", "pandas", "datetime", "PySide6", "openpyxl", "xlrd"],
"packages": ["os", "sys", "sqlite3", "subprocess", "json", "pandas", "datetime", "PySide6"],
"include_files": include_files,
"excludes": ['PySide6.QtAsyncio.events'], # tkinter 미사용 시 제외
"excludes": [], # tkinter 미사용 시 제외
}
# 실행 파일 설정
@ -43,7 +40,7 @@ executables = [
setup(
name=application_name,
version=application_version,
description=f"크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+ - v{__version__})",
description="크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+)",
options={"build_exe": build_options},
executables=executables,
)

View File

View File

@ -1 +0,0 @@
__version__ = "1.3.4"

541
t2.py
View File

@ -1,541 +0,0 @@
import flet as ft
import sqlite3
import subprocess
import json
import threading
import logging
import traceback
import os
import psutil
import glob
import pandas as pd
from datetime import datetime, time
# 로깅 기본 설정
logging.basicConfig(level=logging.INFO)
# =====================================================================
# DB 관련 기능을 담당하는 모듈 (DBHandler)
# =====================================================================
class DBHandler:
def __init__(self, db_path="markets.db"):
self.db_path = db_path
self.ensure_db()
def ensure_db(self):
if not os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
conn.execute("""
CREATE TABLE markets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
country TEXT,
mall_grade TEXT,
mall_name TEXT,
mall_url TEXT,
extract_count INTEGER DEFAULT 0
)
""")
conn.commit()
conn.close()
def load_excel(self, file_path, remove_existing=False, log_callback=print):
try:
ext = os.path.splitext(file_path)[-1].lower()
if ext == ".xls":
df = pd.read_excel(file_path, sheet_name=0, engine="xlrd")
elif ext == ".xlsx":
df = pd.read_excel(file_path, sheet_name=0, engine="openpyxl")
else:
log_callback("지원되지 않는 파일 형식입니다. (.xls 또는 .xlsx)")
return
required_columns = ['country', 'mall_grade', 'mall_name', 'mall_url']
for col in required_columns:
if col not in df.columns:
df[col] = ""
df = df[required_columns]
df.drop_duplicates(subset=required_columns, inplace=True)
conn = sqlite3.connect(self.db_path)
if remove_existing:
conn.execute("DROP TABLE IF EXISTS markets")
conn.execute("""
CREATE TABLE markets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
country TEXT,
mall_grade TEXT,
mall_name TEXT,
mall_url TEXT,
extract_count INTEGER DEFAULT 0
)
""")
try:
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass
for _, row in df.iterrows():
conn.execute("""
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
VALUES (?, ?, ?, ?)
""", (row['country'], row['mall_grade'], row['mall_name'], row['mall_url']))
conn.commit()
conn.close()
log_callback("DB 저장 완료")
except Exception as e:
log_callback(f"엑셀 로드 에러: {e}\n{traceback.format_exc()}")
def view_data(self):
conn = sqlite3.connect(self.db_path)
df = pd.read_sql_query("SELECT * FROM markets", conn)
conn.close()
return df
def reset_extract_count(self, log_callback=print):
conn = sqlite3.connect(self.db_path)
conn.execute("UPDATE markets SET extract_count = 0")
conn.commit()
conn.close()
log_callback("모든 추출 횟수가 초기화되었습니다.")
def extract_bookmarks(self, country, grade, count, extract_based, max_extract, log_callback=print):
try:
conn = sqlite3.connect(self.db_path)
query = "SELECT id, mall_name AS name, mall_url AS url FROM markets WHERE 1=1"
if country != "랜덤":
query += f" AND country = '{country}'"
if grade != "랜덤":
query += f" AND mall_grade = '{grade}'"
if extract_based:
query += " AND extract_count < ? ORDER BY extract_count ASC, RANDOM()"
query += f" LIMIT {count}"
df = pd.read_sql_query(query, conn, params=(max_extract,))
else:
query += " ORDER BY RANDOM()"
query += f" LIMIT {count}"
df = pd.read_sql_query(query, conn)
if df.empty:
log_callback("추출 가능한 데이터가 없습니다.")
conn.close()
return None
# 추출 횟수 업데이트
for record in df.to_dict("records"):
conn.execute("UPDATE markets SET extract_count = extract_count + 1 WHERE id = ?", (record["id"],))
conn.commit()
conn.close()
log_callback(f"{len(df)}개의 북마크를 추출했습니다.")
return df.to_dict("records")
except Exception as e:
log_callback(f"DB 쿼리 실행 오류: {e}")
return None
# =====================================================================
# 북마크 추가 작업을 백그라운드로 처리하는 모듈 (BookmarkWorker)
# =====================================================================
class BookmarkWorker(threading.Thread):
def __init__(self, bookmarks, folder_name, bookmarks_path, browser_path, selected_browser, remove_existing,
progress_callback, log_callback, completed_callback):
super().__init__()
self.bookmarks = bookmarks
self.folder_name = folder_name
self.bookmarks_path = bookmarks_path
self.browser_path = browser_path
self.selected_browser = selected_browser
self.remove_existing = remove_existing
self.progress_callback = progress_callback
self.log_callback = log_callback
self.completed_callback = completed_callback
def run(self):
try:
if not os.path.exists(self.bookmarks_path):
self.log_callback(f"즐겨찾기 JSON 파일을 찾을 수 없습니다: {self.bookmarks_path}")
return
try:
with open(self.bookmarks_path, "r", encoding="utf-8") as file:
file_content = file.read().strip()
if not file_content:
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
self.log_callback("JSON 파일이 비어 있어 기본값으로 초기화합니다.")
else:
bookmarks_data = json.loads(file_content)
except json.JSONDecodeError as e:
self.log_callback(f"JSON 파싱 오류: {e}")
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
if self.remove_existing:
bookmarks_data["roots"]["bookmark_bar"] = self.remove_existing_bookmarks(
bookmarks_data["roots"]["bookmark_bar"]
)
total = len(self.bookmarks)
bookmark_bar = bookmarks_data["roots"]["bookmark_bar"]
current_time = datetime.now().strftime("%m-%d-%H-%M-%S")
parent_folder_name = f"거상북마크-{current_time}"
parent_folder = {"type": "folder", "name": parent_folder_name, "children": []}
chunk_size = 100
for idx, start in enumerate(range(0, total, chunk_size), start=1):
sub_folder = {
"type": "folder",
"name": f"거상북마크-{self.folder_name}-{idx}",
"children": []
}
for bm in self.bookmarks[start:start+chunk_size]:
sub_folder["children"].append({
"type": "url",
"name": bm["name"],
"url": bm["url"]
})
parent_folder["children"].append(sub_folder)
progress = int((start + min(chunk_size, total - start)) / total * 100)
self.progress_callback(progress)
bookmark_bar["children"].append(parent_folder)
with open(self.bookmarks_path, "w", encoding="utf-8") as f:
json.dump(bookmarks_data, f, indent=4, ensure_ascii=False)
self.log_callback("즐겨찾기 추가 완료!")
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, self.selected_browser)
self.run_and_focus_copyman()
self.completed_callback()
except Exception as e:
self.log_callback(f"작업 오류: {e}\n{traceback.format_exc()}")
self.progress_callback(0)
def run_browser_with_profile(self, browser_path, bookmarks_path, browser_name):
if "웨일" in browser_name:
for proc in psutil.process_iter(attrs=["pid", "name"]):
if "whale" in proc.info["name"].lower():
try:
proc.terminate()
proc.wait(timeout=5)
self.log_callback("웨일 프로세스 종료됨.")
except Exception as e:
self.log_callback(f"웨일 종료 오류: {e}")
if os.path.exists(browser_path):
profile_dir = os.path.basename(os.path.dirname(bookmarks_path))
if "크롬" in browser_name.lower():
page_url = "chrome://bookmarks/"
elif "웨일" in browser_name.lower():
page_url = "whale://bookmarks/"
else:
page_url = "about:blank"
if profile_dir:
subprocess.Popen([browser_path, f"--profile-directory={profile_dir}", page_url])
self.log_callback(f"{browser_name} 프로필 {profile_dir}로 북마크 열기")
else:
subprocess.Popen([browser_path, page_url])
self.log_callback(f"{browser_name} 기본 프로필로 북마크 열기")
else:
self.log_callback(f"{browser_name} 경로가 올바르지 않습니다: {browser_path}")
def run_and_focus_copyman(self):
program_name = "@카피맨.exe"
shortcut_name = "@카피맨"
window_title_start = "카피맨"
try:
pid = self.is_program_running(program_name)
if pid:
self.log_callback(f"{program_name} 실행 중 (PID: {pid})")
if not self.focus_window_by_title(window_title_start):
self.log_callback("카피맨 창을 찾지 못함.")
else:
self.log_callback("카피맨 실행 안됨. 실행 시도...")
shortcut = self.find_shortcut_in_start_menu(shortcut_name)
if shortcut:
self.run_program(shortcut)
else:
self.log_callback("카피맨 바로가기 없음.")
except Exception as e:
self.log_callback(f"카피맨 실행 오류: {e}\n{traceback.format_exc()}")
def is_program_running(self, process_name):
for proc in psutil.process_iter(attrs=["pid", "name"]):
if process_name.lower() in proc.info["name"].lower():
return proc.info["pid"]
return None
def focus_window_by_title(self, title_start):
try:
import pygetwindow as gw
for window in gw.getAllWindows():
if window.title and window.title.startswith(title_start):
try:
window.activate()
self.log_callback(f"창 활성화: {window.title}")
return True
except Exception as e:
self.log_callback(f"창 활성화 실패: {e}")
return False
return False
except Exception as e:
self.log_callback(f"포커스 전환 오류: {e}")
return False
def find_shortcut_in_start_menu(self, shortcut_name):
user_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
all_users_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
for path in [user_menu, all_users_menu]:
shortcut = glob.glob(os.path.join(path, f"**\\{shortcut_name}.lnk"), recursive=True)
if shortcut:
return shortcut[0]
return None
def run_program(self, shortcut_path):
try:
subprocess.Popen([shortcut_path], shell=True)
self.log_callback(f"프로그램 실행: {shortcut_path}")
except Exception as e:
self.log_callback(f"실행 오류: {e}")
def remove_existing_bookmarks(self, node):
if not isinstance(node, dict):
return node
if node.get("type") == "folder" and node.get("name", "").startswith("거상북마크"):
self.log_callback(f"제거된 폴더: {node.get('name')}")
return None
if "children" in node:
node["children"] = [self.remove_existing_bookmarks(child)
for child in node["children"]
if self.remove_existing_bookmarks(child) is not None]
return node
# =====================================================================
# Flet UI와 백엔드 모듈을 통합한 메인 앱
# =====================================================================
def main(page: ft.Page):
page.title = "모듈화된 백엔드 & Flet 앱"
page.horizontal_alignment = "center"
page.vertical_alignment = "start"
page.padding = 20
# 기본 비밀번호 및 DB 핸들러 생성
stored_password = "365"
user_password = None
db_handler = DBHandler()
bookmarks_global = [] # 추출된 북마크 저장용
# 로그와 진행률 UI
log_text = ft.Text(value="", size=12)
progress_bar = ft.ProgressBar(width=300, value=0)
def log(msg):
nonlocal log_text
log_text.value += msg + "\n"
page.update()
def update_progress(val):
progress_bar.value = val / 100
page.update()
def task_completed():
log("작업 완료!")
dlg = ft.AlertDialog(
title=ft.Text("완료"),
content=ft.Text("즐겨찾기 추가 작업이 완료되었습니다."),
actions=[ft.TextButton("닫기", on_click=lambda e: close_dialog())],
modal=True
)
page.dialog = dlg
dlg.open = True
page.update()
def close_dialog():
page.dialog.open = False
page.update()
# 비밀번호 입력 다이얼로그
def show_password_dialog():
def on_password_submit(e):
nonlocal user_password
user_password = pwd_field.value
if user_password != stored_password:
result = ft.AlertDialog(
title=ft.Text("비밀번호 오류"),
content=ft.Text("비밀번호가 일치하지 않습니다. 프로그램을 종료합니다."),
actions=[ft.TextButton("닫기", on_click=lambda e: page.window_close())],
modal=True
)
page.dialog = result
result.open = True
page.update()
else:
pwd_dlg.open = False
page.update()
pwd_field = ft.TextField(label="비밀번호", password=True)
pwd_dlg = ft.AlertDialog(
title=ft.Text("비밀번호 입력"),
content=ft.Column([pwd_field]),
actions=[ft.TextButton("확인", on_click=on_password_submit)],
modal=True
)
page.dialog = pwd_dlg
pwd_dlg.open = True
page.update()
show_password_dialog()
# =================================================================
# UI 컨트롤 구성
# =================================================================
country_dropdown = ft.Dropdown(
label="국가",
options=[
ft.dropdown.Option("미국"),
ft.dropdown.Option("유럽"),
ft.dropdown.Option("중국"),
ft.dropdown.Option("일본"),
ft.dropdown.Option("한국"),
ft.dropdown.Option("기타"),
ft.dropdown.Option("랜덤")
],
value="중국"
)
grade_dropdown = ft.Dropdown(
label="등급",
options=[
ft.dropdown.Option("일반"),
ft.dropdown.Option("파워"),
ft.dropdown.Option("빅파워"),
ft.dropdown.Option("랜덤")
],
value="랜덤"
)
count_field = ft.TextField(label="갯수", value="1000", keyboard_type=ft.KeyboardType.NUMBER)
remove_existing_checkbox = ft.Checkbox(label="기존 북마크 제거", value=False)
extract_based_checkbox = ft.Checkbox(label="추출 횟수 기반 추출", value=False)
max_extract_field = ft.TextField(label="최대 추출 횟수", value="1", keyboard_type=ft.KeyboardType.NUMBER)
browser_dropdown = ft.Dropdown(
label="브라우저 선택",
options=[
ft.dropdown.Option("크롬"),
ft.dropdown.Option("웨일")
],
value="크롬"
)
# 파일 선택: 엑셀 파일 입력
file_picker_excel = ft.FilePicker(on_result=lambda e: on_excel_selected(e))
page.overlay.append(file_picker_excel)
def on_excel_selected(e: ft.FilePickerResultEvent):
if e.files is not None and len(e.files) > 0:
file_path = e.files[0].path
db_handler.load_excel(file_path, remove_existing_checkbox.value, log)
else:
log("엑셀 파일 선택 취소됨.")
db_input_button = ft.ElevatedButton("DB 입력", on_click=lambda e: file_picker_excel.pick_files(
allow_multiple=False, file_type=ft.FilePickerFileType.CUSTOM, allowed_extensions=["xlsx", "xls"]
))
view_data_button = ft.ElevatedButton("데이터 보기", on_click=lambda e: view_data_action())
def view_data_action():
df = db_handler.view_data()
if df.empty:
log("DB에 데이터가 없습니다.")
return
dlg = ft.AlertDialog(
title=ft.Text("DB 데이터"),
content=ft.Text(str(df)),
actions=[ft.TextButton("닫기", on_click=lambda e: close_dialog())],
modal=True
)
page.dialog = dlg
dlg.open = True
page.update()
reset_extract_button = ft.ElevatedButton("추출 횟수 초기화", on_click=lambda e: db_handler.reset_extract_count(log))
# 파일 선택: 브라우저 실행 파일 경로 설정 (Flet FilePicker 활용)
file_picker_browser = ft.FilePicker(on_result=lambda e: on_browser_selected(e))
page.overlay.append(file_picker_browser)
browser_path_field = ft.TextField(label="브라우저 경로", value="")
# 전역 변수 업데이트: 기본 경로 설정 (사용자 환경에 맞게 수정)
chrome_path = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
chrome_bookmarks_path = os.path.join(os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data\Default"), "Bookmarks")
whale_path = "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
whale_bookmarks_path = os.path.join(os.path.expandvars(r"%LOCALAPPDATA%\Naver\Naver Whale\User Data\Default"), "Bookmarks")
def on_browser_selected(e: ft.FilePickerResultEvent):
nonlocal chrome_path, whale_path
if e.files and len(e.files) > 0:
file_path = e.files[0].path
browser_path_field.value = file_path
if browser_dropdown.value == "크롬":
chrome_path = file_path
else:
whale_path = file_path
log(f"브라우저 경로 설정: {file_path}")
page.update()
else:
log("브라우저 파일 선택 취소됨.")
chrome_path_button = ft.ElevatedButton("크롬 경로 설정", on_click=lambda e: file_picker_browser.pick_files(
allow_multiple=False, file_type=ft.FilePickerFileType.CUSTOM, allowed_extensions=["exe"]
))
whale_path_button = ft.ElevatedButton("웨일 경로 설정", on_click=lambda e: file_picker_browser.pick_files(
allow_multiple=False, file_type=ft.FilePickerFileType.CUSTOM, allowed_extensions=["exe"]
))
run_button = ft.ElevatedButton("실행")
def run_task(e):
country = country_dropdown.value
grade = grade_dropdown.value
try:
cnt = int(count_field.value)
except:
cnt = 1000
remove_existing = remove_existing_checkbox.value
extract_based = extract_based_checkbox.value
try:
max_extract = int(max_extract_field.value)
except:
max_extract = 1
bookmarks_extracted = db_handler.extract_bookmarks(country, grade, cnt, extract_based, max_extract, log)
if bookmarks_extracted is None:
return
nonlocal bookmarks_global
bookmarks_global = bookmarks_extracted
folder_name = f"거상북마크-{grade}"
if browser_dropdown.value == "크롬":
selected_bookmarks_path = chrome_bookmarks_path
selected_browser_path = chrome_path
selected_browser = "크롬"
log("크롬 브라우저 선택됨.")
else:
selected_bookmarks_path = whale_bookmarks_path
selected_browser_path = whale_path
selected_browser = "웨일"
log("웨일 브라우저 선택됨.")
if not os.path.exists(selected_browser_path):
log("브라우저 실행 파일 경로가 유효하지 않습니다.")
return
if not os.path.exists(selected_bookmarks_path):
log("브라우저 북마크 경로가 유효하지 않습니다.")
return
worker = BookmarkWorker(bookmarks_global, folder_name, selected_bookmarks_path,
selected_browser_path, selected_browser, remove_existing,
update_progress, log, task_completed)
worker.start()
run_button.on_click = run_task
controls_row = ft.Row(controls=[country_dropdown, grade_dropdown, count_field, remove_existing_checkbox])
extract_row = ft.Row(controls=[extract_based_checkbox, max_extract_field, reset_extract_button])
browser_row = ft.Row(controls=[browser_dropdown, chrome_path_button, whale_path_button, browser_path_field])
action_row = ft.Row(controls=[db_input_button, view_data_button, run_button])
page.add(controls_row, extract_row, browser_row, action_row, progress_bar, ft.Text("로그:"), log_text)
page.update()
ft.app(target=main)

View File

@ -1,20 +0,0 @@
# chrome_sync.py
# 크롬 동기화 기능을 위한 플레이스홀더 모듈입니다.
# 실제 Chrome 계정 동기화 API를 사용하려면 별도의 구현이 필요합니다.
def get_chrome_bookmarks():
"""
크롬 동기화된 북마크를 가져오는 플레이스홀더 함수.
북마크는 이름, URL, 폴더 경로 정보를 포함합니다.
"""
return [
{"name": "Google", "url": "https://www.google.com", "folder": "검색엔진"},
{"name": "YouTube", "url": "https://www.youtube.com", "folder": "동영상"},
{"name": "GitHub", "url": "https://www.github.com", "folder": "개발"}
]
def get_chrome_extensions():
"""
크롬 동기화된 확장 프로그램 목록을 가져오는 플레이스홀더 함수.
"""
return ["Adblock", "Grammarly", "LastPass"]

View File

@ -1,370 +0,0 @@
import eel
import sqlite3
import subprocess
import json
import threading
import logging
import traceback
import os
import psutil
import glob
import pandas as pd
from datetime import datetime, time
# 로깅 기본 설정
logging.basicConfig(level=logging.INFO)
# =====================================================================
# DB 관련 기능을 담당하는 모듈 (DBHandler)
# =====================================================================
class DBHandler:
def __init__(self, db_path="markets.db"):
self.db_path = db_path
self.ensure_db()
def ensure_db(self):
if not os.path.exists(self.db_path):
conn = sqlite3.connect(self.db_path)
conn.execute("""
CREATE TABLE markets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
country TEXT,
mall_grade TEXT,
mall_name TEXT,
mall_url TEXT,
extract_count INTEGER DEFAULT 0
)
""")
conn.commit()
conn.close()
def load_excel(self, file_path, remove_existing=False, log_callback=print):
try:
ext = os.path.splitext(file_path)[-1].lower()
if ext == ".xls":
df = pd.read_excel(file_path, sheet_name=0, engine="xlrd")
elif ext == ".xlsx":
df = pd.read_excel(file_path, sheet_name=0, engine="openpyxl")
else:
log_callback("지원되지 않는 파일 형식입니다. (.xls 또는 .xlsx)")
return "지원되지 않는 파일 형식입니다. (.xls 또는 .xlsx)"
required_columns = ['country', 'mall_grade', 'mall_name', 'mall_url']
for col in required_columns:
if col not in df.columns:
df[col] = ""
df = df[required_columns]
df.drop_duplicates(subset=required_columns, inplace=True)
conn = sqlite3.connect(self.db_path)
if remove_existing:
conn.execute("DROP TABLE IF EXISTS markets")
conn.execute("""
CREATE TABLE markets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
country TEXT,
mall_grade TEXT,
mall_name TEXT,
mall_url TEXT,
extract_count INTEGER DEFAULT 0
)
""")
try:
conn.execute("ALTER TABLE markets ADD COLUMN extract_count INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass
for _, row in df.iterrows():
conn.execute("""
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
VALUES (?, ?, ?, ?)
""", (row['country'], row['mall_grade'], row['mall_name'], row['mall_url']))
conn.commit()
conn.close()
log_callback("DB 저장 완료")
return "DB 저장 완료"
except Exception as e:
msg = f"엑셀 로드 에러: {e}\n{traceback.format_exc()}"
log_callback(msg)
return msg
def view_data(self):
conn = sqlite3.connect(self.db_path)
df = pd.read_sql_query("SELECT * FROM markets", conn)
conn.close()
# HTML 테이블 형식으로 반환
return df.to_html()
def reset_extract_count(self, log_callback=print):
conn = sqlite3.connect(self.db_path)
conn.execute("UPDATE markets SET extract_count = 0")
conn.commit()
conn.close()
log_callback("모든 추출 횟수가 초기화되었습니다.")
return "모든 추출 횟수가 초기화되었습니다."
def extract_bookmarks(self, country, grade, count, extract_based, max_extract, log_callback=print):
try:
conn = sqlite3.connect(self.db_path)
query = "SELECT id, mall_name AS name, mall_url AS url FROM markets WHERE 1=1"
if country != "랜덤":
query += f" AND country = '{country}'"
if grade != "랜덤":
query += f" AND mall_grade = '{grade}'"
if extract_based:
query += " AND extract_count < ? ORDER BY extract_count ASC, RANDOM()"
query += f" LIMIT {count}"
df = pd.read_sql_query(query, conn, params=(max_extract,))
else:
query += " ORDER BY RANDOM()"
query += f" LIMIT {count}"
df = pd.read_sql_query(query, conn)
if df.empty:
log_callback("추출 가능한 데이터가 없습니다.")
conn.close()
return None
for record in df.to_dict("records"):
conn.execute("UPDATE markets SET extract_count = extract_count + 1 WHERE id = ?", (record["id"],))
conn.commit()
conn.close()
log_callback(f"{len(df)}개의 북마크를 추출했습니다.")
return df.to_dict("records")
except Exception as e:
msg = f"DB 쿼리 실행 오류: {e}"
log_callback(msg)
return None
# =====================================================================
# 북마크 추가 작업을 백그라운드로 처리하는 모듈 (BookmarkWorker)
# =====================================================================
class BookmarkWorker(threading.Thread):
def __init__(self, bookmarks, folder_name, bookmarks_path, browser_path, selected_browser, remove_existing,
progress_callback, log_callback, completed_callback):
super().__init__()
self.bookmarks = bookmarks
self.folder_name = folder_name
self.bookmarks_path = bookmarks_path
self.browser_path = browser_path
self.selected_browser = selected_browser
self.remove_existing = remove_existing
self.progress_callback = progress_callback
self.log_callback = log_callback
self.completed_callback = completed_callback
def run(self):
try:
if not os.path.exists(self.bookmarks_path):
self.log_callback(f"즐겨찾기 JSON 파일을 찾을 수 없습니다: {self.bookmarks_path}")
return
try:
with open(self.bookmarks_path, "r", encoding="utf-8") as file:
file_content = file.read().strip()
if not file_content:
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
self.log_callback("JSON 파일이 비어 있어 기본값으로 초기화합니다.")
else:
bookmarks_data = json.loads(file_content)
except json.JSONDecodeError as e:
self.log_callback(f"JSON 파싱 오류: {e}")
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
if self.remove_existing:
bookmarks_data["roots"]["bookmark_bar"] = self.remove_existing_bookmarks(
bookmarks_data["roots"]["bookmark_bar"]
)
total = len(self.bookmarks)
bookmark_bar = bookmarks_data["roots"]["bookmark_bar"]
current_time = datetime.now().strftime("%m-%d-%H-%M-%S")
parent_folder_name = f"거상북마크-{current_time}"
parent_folder = {"type": "folder", "name": parent_folder_name, "children": []}
chunk_size = 100
for idx, start in enumerate(range(0, total, chunk_size), start=1):
sub_folder = {
"type": "folder",
"name": f"거상북마크-{self.folder_name}-{idx}",
"children": []
}
for bm in self.bookmarks[start:start+chunk_size]:
sub_folder["children"].append({
"type": "url",
"name": bm["name"],
"url": bm["url"]
})
parent_folder["children"].append(sub_folder)
progress = int((start + min(chunk_size, total - start)) / total * 100)
self.progress_callback(progress)
bookmark_bar["children"].append(parent_folder)
with open(self.bookmarks_path, "w", encoding="utf-8") as f:
json.dump(bookmarks_data, f, indent=4, ensure_ascii=False)
self.log_callback("즐겨찾기 추가 완료!")
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, self.selected_browser)
self.run_and_focus_copyman()
self.completed_callback()
except Exception as e:
self.log_callback(f"작업 오류: {e}\n{traceback.format_exc()}")
self.progress_callback(0)
def run_browser_with_profile(self, browser_path, bookmarks_path, browser_name):
if "웨일" in browser_name:
for proc in psutil.process_iter(attrs=["pid", "name"]):
if "whale" in proc.info["name"].lower():
try:
proc.terminate()
proc.wait(timeout=5)
self.log_callback("웨일 프로세스 종료됨.")
except Exception as e:
self.log_callback(f"웨일 종료 오류: {e}")
if os.path.exists(browser_path):
profile_dir = os.path.basename(os.path.dirname(bookmarks_path))
if "크롬" in browser_name.lower():
page_url = "chrome://bookmarks/"
elif "웨일" in browser_name.lower():
page_url = "whale://bookmarks/"
else:
page_url = "about:blank"
if profile_dir:
subprocess.Popen([browser_path, f"--profile-directory={profile_dir}", page_url])
self.log_callback(f"{browser_name} 프로필 {profile_dir}로 북마크 열기")
else:
subprocess.Popen([browser_path, page_url])
self.log_callback(f"{browser_name} 기본 프로필로 북마크 열기")
else:
self.log_callback(f"{browser_name} 경로가 올바르지 않습니다: {browser_path}")
def run_and_focus_copyman(self):
program_name = "@카피맨.exe"
shortcut_name = "@카피맨"
window_title_start = "카피맨"
try:
pid = self.is_program_running(program_name)
if pid:
self.log_callback(f"{program_name} 실행 중 (PID: {pid})")
if not self.focus_window_by_title(window_title_start):
self.log_callback("카피맨 창을 찾지 못함.")
else:
self.log_callback("카피맨 실행 안됨. 실행 시도...")
shortcut = self.find_shortcut_in_start_menu(shortcut_name)
if shortcut:
self.run_program(shortcut)
else:
self.log_callback("카피맨 바로가기 없음.")
except Exception as e:
self.log_callback(f"카피맨 실행 오류: {e}\n{traceback.format_exc()}")
def is_program_running(self, process_name):
for proc in psutil.process_iter(attrs=["pid", "name"]):
if process_name.lower() in proc.info["name"].lower():
return proc.info["pid"]
return None
def focus_window_by_title(self, title_start):
try:
import pygetwindow as gw
for window in gw.getAllWindows():
if window.title and window.title.startswith(title_start):
try:
window.activate()
self.log_callback(f"창 활성화: {window.title}")
return True
except Exception as e:
self.log_callback(f"창 활성화 실패: {e}")
return False
return False
except Exception as e:
self.log_callback(f"포커스 전환 오류: {e}")
return False
def find_shortcut_in_start_menu(self, shortcut_name):
user_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
all_users_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
for path in [user_menu, all_users_menu]:
shortcut = glob.glob(os.path.join(path, f"**\\{shortcut_name}.lnk"), recursive=True)
if shortcut:
return shortcut[0]
return None
def run_program(self, shortcut_path):
try:
subprocess.Popen([shortcut_path], shell=True)
self.log_callback(f"프로그램 실행: {shortcut_path}")
except Exception as e:
self.log_callback(f"실행 오류: {e}")
def remove_existing_bookmarks(self, node):
if not isinstance(node, dict):
return node
if node.get("type") == "folder" and node.get("name", "").startswith("거상북마크"):
self.log_callback(f"제거된 폴더: {node.get('name')}")
return None
if "children" in node:
node["children"] = [self.remove_existing_bookmarks(child)
for child in node["children"]
if self.remove_existing_bookmarks(child) is not None]
return node
# 전역 DBHandler 생성
db_handler = DBHandler()
# =====================================================================
# Eel을 통한 UI와 백엔드 연동
# =====================================================================
@eel.expose
def load_excel(file_path, remove_existing):
result = db_handler.load_excel(file_path, remove_existing, log_callback=print)
eel.log(result)
@eel.expose
def view_data():
html_data = db_handler.view_data()
eel.show_data(html_data)
@eel.expose
def reset_extract_count():
result = db_handler.reset_extract_count(log_callback=print)
eel.log(result)
@eel.expose
def run_task(country, grade, count, remove_existing, extract_based, max_extract, browser_choice, chrome_path, whale_path):
bookmarks = db_handler.extract_bookmarks(country, grade, int(count), extract_based, int(max_extract), log_callback=print)
if bookmarks is None:
eel.log("북마크 추출 실패")
return
folder_name = f"거상북마크-{grade}"
if browser_choice == "크롬":
# 기본 크롬 북마크 경로 (사용자 환경에 맞게 수정 필요)
selected_bookmarks_path = os.path.join(os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data\Default"), "Bookmarks")
selected_browser_path = chrome_path
selected_browser = "크롬"
eel.log("크롬 브라우저 선택됨.")
else:
selected_bookmarks_path = os.path.join(os.path.expandvars(r"%LOCALAPPDATA%\Naver\Naver Whale\User Data\Default"), "Bookmarks")
selected_browser_path = whale_path
selected_browser = "웨일"
eel.log("웨일 브라우저 선택됨.")
if not os.path.exists(selected_browser_path):
eel.log("브라우저 실행 파일 경로가 유효하지 않습니다.")
return
if not os.path.exists(selected_bookmarks_path):
eel.log("브라우저 북마크 경로가 유효하지 않습니다.")
return
def progress_callback(val):
eel.update_progress(val)
def log_callback(msg):
eel.log(msg)
def completed_callback():
eel.log("작업 완료!")
eel.task_completed()
worker = BookmarkWorker(bookmarks, folder_name, selected_bookmarks_path,
selected_browser_path, selected_browser, remove_existing,
progress_callback, log_callback, completed_callback)
worker.start()
# Eel 초기화 (웹 폴더 내부에 index.html 파일이 있어야 합니다.)
eel.init("web")
# Eel 앱 실행 (index.html 열림)
eel.start("index.html", size=(900,700))

View File

@ -1,21 +0,0 @@
import dearpygui.dearpygui as dpg
def button_callback(sender, app_data, user_data):
# 기존 로그 텍스트에 새로운 메시지를 추가
current_log = dpg.get_value("log_text")
new_log = current_log + "Hello, world!\n"
dpg.set_value("log_text", new_log)
dpg.create_context()
with dpg.window(label="Example Window", tag="main_window"):
dpg.add_text("DearPyGui 2.0 Immediate Mode Example")
dpg.add_button(label="Click Me", callback=button_callback)
# 멀티라인 InputText 위젯을 로그 영역으로 사용
dpg.add_input_text(label="Log", tag="log_text", multiline=True, height=100, width=400, readonly=True)
dpg.create_viewport(title='Example App', width=600, height=400)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

View File

@ -1,458 +0,0 @@
import sys
import asyncio
from PySide6 import QtWidgets, QtCore, QtGui, QtWebEngineWidgets
from PySide6.QtCore import QUrl, QTimer
from PySide6.QtGui import QIcon, QAction
from PySide6.QtWidgets import QToolBar, QLineEdit, QMessageBox, QHBoxLayout, QWidget, QDialog, QFormLayout, QDialogButtonBox
from playwright.async_api import async_playwright
# chrome_sync 모듈 import (플레이스홀더)
import chrome_sync
# Playwright 자동화 작업을 위한 QThread 클래스
class PlaywrightThread(QtCore.QThread):
result_signal = QtCore.Signal(str)
def run(self):
asyncio.run(self.run_playwright())
async def run_playwright(self):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
page = await browser.new_page()
await page.goto("https://example.com")
screenshot_path = "screenshot.png"
await page.screenshot(path=screenshot_path)
await browser.close()
self.result_signal.emit(f"Playwright 자동화 완료: 스크린샷 저장 - {screenshot_path}")
# 각 브라우저 탭에 들어갈 위젯 클래스 (QWebEngineView 포함)
class BrowserTab(QtWidgets.QWidget):
def __init__(self, url, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.web_view = QtWebEngineWidgets.QWebEngineView()
layout.addWidget(self.web_view)
self.web_view.setUrl(QUrl(url))
# 북마크 수정/추가 다이얼로그
class BookmarkEditDialog(QDialog):
def __init__(self, bookmark=None, parent=None):
super().__init__(parent)
self.setWindowTitle("북마크 수정" if bookmark else "새 북마크 추가")
layout = QFormLayout(self)
self.name_edit = QLineEdit()
self.url_edit = QLineEdit()
self.folder_edit = QLineEdit()
if bookmark:
self.name_edit.setText(bookmark["name"])
self.url_edit.setText(bookmark["url"])
self.folder_edit.setText(bookmark.get("folder", ""))
layout.addRow("이름:", self.name_edit)
layout.addRow("주소:", self.url_edit)
layout.addRow("폴더 경로:", self.folder_edit)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
def getData(self):
return {
"name": self.name_edit.text().strip(),
"url": self.url_edit.text().strip(),
"folder": self.folder_edit.text().strip()
}
# 북마크 버튼 (QToolButton 확장) - 우클릭 시 수정 메뉴 제공
class BookmarkButton(QtWidgets.QToolButton):
def __init__(self, bookmark, edit_callback, parent=None):
super().__init__(parent)
self.bookmark = bookmark
self.edit_callback = edit_callback
self.setText(bookmark["name"])
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)
def show_context_menu(self, pos):
menu = QtWidgets.QMenu(self)
edit_action = menu.addAction("북마크 수정")
action = menu.exec(self.mapToGlobal(pos))
if action == edit_action:
self.edit_callback(self)
# 도구 패널(QTabWidget) - 세로 탭
class ToolPanel(QtWidgets.QTabWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTabPosition(QtWidgets.QTabWidget.West)
self.setMinimumWidth(250)
self.auto_hide = False
self.hide_timer = QTimer(self)
self.hide_timer.setInterval(500)
self.hide_timer.setSingleShot(True)
self.hide_timer.timeout.connect(self.hide_panel)
self.installEventFilter(self)
def eventFilter(self, obj, event):
if self.auto_hide:
if event.type() == QtCore.QEvent.Leave:
self.hide_timer.start()
elif event.type() == QtCore.QEvent.Enter:
self.hide_timer.stop()
return super().eventFilter(obj, event)
def hide_panel(self):
self.hide()
def show_panel(self):
self.show()
# 문자전송 탭의 간단한 예제 위젯
class MessageTab(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(QtWidgets.QLabel("문자전송 기능"))
self.message_input = QtWidgets.QLineEdit()
self.message_input.setPlaceholderText("보낼 메시지를 입력하세요")
self.send_button = QtWidgets.QPushButton("전송")
self.send_button.clicked.connect(self.send_message)
layout.addWidget(self.message_input)
layout.addWidget(self.send_button)
self.status_label = QtWidgets.QLabel("")
layout.addWidget(self.status_label)
def send_message(self):
message = self.message_input.text()
# 실제 문자 전송 API 호출 구현 필요
self.status_label.setText(f"전송됨: {message}")
# 메인 윈도우 클래스
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("크롬과 유사한 커스텀 브라우저")
self.resize(1920, 1080)
# 북마크 리스트 (각 항목은 dict: name, url, folder)
self.bookmarks = []
# 메인 레이아웃: 브라우저 영역과 도구 패널 (세로 탭)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.main_layout = QtWidgets.QHBoxLayout(central_widget)
self.main_layout.setContentsMargins(0, 0, 0, 0)
# 브라우저 영역 (네비게이션 바, 북마크 바, 브라우저 탭)
self.browser_area = QtWidgets.QWidget()
self.init_browser_area()
self.main_layout.addWidget(self.browser_area, 8)
# 도구 패널
self.tool_panel = ToolPanel()
self.init_tool_panel()
self.main_layout.addWidget(self.tool_panel, 2)
def init_browser_area(self):
layout = QtWidgets.QVBoxLayout(self.browser_area)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 1. 네비게이션 바 (아이콘 버튼 + 주소창 등)
self.nav_toolbar = QToolBar()
self.nav_toolbar.setIconSize(QtCore.QSize(24, 24))
self.nav_toolbar.setMovable(False)
layout.addWidget(self.nav_toolbar)
# 뒤로가기
back_icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowBack)
back_action = QAction(back_icon, "", self)
back_action.triggered.connect(self.navigate_back)
self.nav_toolbar.addAction(back_action)
# 앞으로가기
forward_icon = self.style().standardIcon(QtWidgets.QStyle.SP_ArrowForward)
forward_action = QAction(forward_icon, "", self)
forward_action.triggered.connect(self.navigate_forward)
self.nav_toolbar.addAction(forward_action)
# 새로고침
refresh_icon = self.style().standardIcon(QtWidgets.QStyle.SP_BrowserReload)
refresh_action = QAction(refresh_icon, "", self)
refresh_action.triggered.connect(self.refresh_page)
self.nav_toolbar.addAction(refresh_action)
# 주소창
self.url_bar = QLineEdit()
self.url_bar.returnPressed.connect(self.load_url_from_bar)
self.url_bar.setMinimumWidth(400)
self.nav_toolbar.addWidget(self.url_bar)
self.nav_toolbar.addSeparator()
# 북마크 추가 (아이콘)
bookmark_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DialogYesButton)
bookmark_action = QAction(bookmark_icon, "", self)
bookmark_action.triggered.connect(self.new_bookmark)
self.nav_toolbar.addAction(bookmark_action)
# 확장 프로그램 (아이콘)
ext_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DirIcon)
ext_action = QAction(ext_icon, "", self)
ext_action.triggered.connect(self.show_extensions)
self.nav_toolbar.addAction(ext_action)
# 2. 북마크 바 (주소창 바로 아래) - 오른쪽 끝에 도구 패널 토글 버튼 포함
self.bookmark_bar_widget = QWidget()
bookmark_layout = QHBoxLayout(self.bookmark_bar_widget)
bookmark_layout.setContentsMargins(2, 2, 2, 2)
self.bookmark_toolbar = QToolBar()
self.bookmark_toolbar.setMovable(False)
self.bookmark_toolbar.setIconSize(QtCore.QSize(20, 20))
bookmark_layout.addWidget(self.bookmark_toolbar)
bookmark_layout.addStretch()
self.tool_toggle_button = QtWidgets.QToolButton()
toggle_icon = self.style().standardIcon(QtWidgets.QStyle.SP_ComputerIcon)
self.tool_toggle_button.setIcon(toggle_icon)
self.tool_toggle_button.setCheckable(True)
self.tool_toggle_button.setChecked(True)
self.tool_toggle_button.clicked.connect(self.toggle_tool_panel)
self.tool_toggle_button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tool_toggle_button.customContextMenuRequested.connect(self.show_toggle_menu)
bookmark_layout.addWidget(self.tool_toggle_button)
layout.addWidget(self.bookmark_bar_widget)
self.load_sync_bookmarks()
# 3. 브라우저 탭 영역
self.browser_tabs = QtWidgets.QTabWidget()
self.browser_tabs.setTabsClosable(True)
self.browser_tabs.tabCloseRequested.connect(self.close_current_tab)
self.browser_tabs.currentChanged.connect(self.update_url_bar)
layout.addWidget(self.browser_tabs)
# 첫 탭 추가
self.add_new_tab("https://www.google.com", "Google")
def init_tool_panel(self):
# 도구 패널 탭 추가 (세로 탭)
# 배송비 계산기 탭
self.shipping_tab = QtWidgets.QWidget()
self.init_shipping_tab()
self.tool_panel.addTab(self.shipping_tab, "배송비")
# 금지어 관리 탭
self.prohibited_tab = QtWidgets.QWidget()
self.init_prohibited_tab()
self.tool_panel.addTab(self.prohibited_tab, "금지어")
# 카테고리 관리 탭
self.category_tab = QtWidgets.QWidget()
self.init_category_tab()
self.tool_panel.addTab(self.category_tab, "카테고리")
# Playwright 매크로 탭
self.playwright_tab = QtWidgets.QWidget()
self.init_playwright_tab()
self.tool_panel.addTab(self.playwright_tab, "Playwright")
# 문자전송 탭
self.message_tab = MessageTab()
self.tool_panel.addTab(self.message_tab, "문자전송")
def add_new_tab(self, url="https://www.google.com", label="New Tab"):
new_tab = BrowserTab(url)
index = self.browser_tabs.addTab(new_tab, label)
self.browser_tabs.setCurrentIndex(index)
new_tab.web_view.urlChanged.connect(lambda qurl, tab=new_tab: self.update_tab_title(tab, qurl))
new_tab.web_view.loadFinished.connect(lambda ok, tab=new_tab: self.update_url_bar())
def close_current_tab(self, index):
if self.browser_tabs.count() > 1:
self.browser_tabs.removeTab(index)
def update_tab_title(self, tab, qurl):
title = tab.web_view.title() or qurl.toString()
index = self.browser_tabs.indexOf(tab)
self.browser_tabs.setTabText(index, title)
if self.browser_tabs.currentWidget() == tab:
self.url_bar.setText(qurl.toString())
def update_url_bar(self):
current_tab = self.browser_tabs.currentWidget()
if current_tab:
url = current_tab.web_view.url().toString()
self.url_bar.setText(url)
def load_url_from_bar(self):
url_text = self.url_bar.text().strip()
if url_text and not url_text.startswith("http"):
url_text = "http://" + url_text
current_tab = self.browser_tabs.currentWidget()
if current_tab:
current_tab.web_view.setUrl(QUrl(url_text))
def navigate_back(self):
current_tab = self.browser_tabs.currentWidget()
if current_tab:
current_tab.web_view.back()
def navigate_forward(self):
current_tab = self.browser_tabs.currentWidget()
if current_tab:
current_tab.web_view.forward()
def refresh_page(self):
current_tab = self.browser_tabs.currentWidget()
if current_tab:
current_tab.web_view.reload()
# 북마크 추가 버튼 클릭 시: 새 북마크 다이얼로그 띄움
def new_bookmark(self):
dialog = BookmarkEditDialog(parent=self)
if dialog.exec() == QDialog.Accepted:
data = dialog.getData()
self.bookmarks.append(data)
self.add_bookmark_button(data)
# 북마크 바에 버튼 추가
def add_bookmark_button(self, bookmark):
btn = BookmarkButton(bookmark, edit_callback=self.edit_bookmark)
btn.clicked.connect(lambda checked, url=bookmark["url"]: self.open_bookmark(url))
self.bookmark_toolbar.addWidget(btn)
# 우클릭으로 북마크 수정 호출
def edit_bookmark(self, btn: BookmarkButton):
dialog = BookmarkEditDialog(bookmark=btn.bookmark, parent=self)
if dialog.exec() == QDialog.Accepted:
data = dialog.getData()
btn.bookmark = data
btn.setText(data["name"])
# 업데이트된 데이터를 bookmarks 리스트에도 반영
for bm in self.bookmarks:
if bm["url"] == btn.bookmark["url"]:
bm.update(data)
break
def open_bookmark(self, url):
current_tab = self.browser_tabs.currentWidget()
if current_tab:
current_tab.web_view.setUrl(QUrl(url))
def load_sync_bookmarks(self):
# chrome_sync 모듈을 통해 동기화된 북마크 가져오기 (플레이스홀더)
sync_bookmarks = chrome_sync.get_chrome_bookmarks()
for bm in sync_bookmarks:
if bm not in self.bookmarks:
self.bookmarks.append(bm)
self.add_bookmark_button(bm)
def show_extensions(self):
# chrome_sync 모듈을 통해 확장 프로그램 가져오기 (플레이스홀더)
ext_list = chrome_sync.get_chrome_extensions()
msg = "\n".join(ext_list)
QMessageBox.information(self, "확장 프로그램", f"사용 가능한 확장 프로그램:\n{msg}")
def init_shipping_tab(self):
layout = QtWidgets.QVBoxLayout(self.shipping_tab)
layout.addWidget(QtWidgets.QLabel("배송비 계산기"))
self.weight_input = QtWidgets.QLineEdit()
self.weight_input.setPlaceholderText("무게 입력")
self.size_input = QtWidgets.QLineEdit()
self.size_input.setPlaceholderText("크기 입력")
self.calculate_button = QtWidgets.QPushButton("계산")
self.calculate_result = QtWidgets.QLabel("결과:")
self.calculate_button.clicked.connect(self.calculate_shipping)
layout.addWidget(self.weight_input)
layout.addWidget(self.size_input)
layout.addWidget(self.calculate_button)
layout.addWidget(self.calculate_result)
def calculate_shipping(self):
try:
shipping_cost = float(self.weight_input.text()) * 0.5 + float(self.size_input.text()) * 0.3
self.calculate_result.setText(f"결과: {shipping_cost:.2f}")
except ValueError:
self.calculate_result.setText("올바른 숫자를 입력하세요.")
def init_prohibited_tab(self):
layout = QtWidgets.QVBoxLayout(self.prohibited_tab)
layout.addWidget(QtWidgets.QLabel("금지어 관리"))
self.prohibited_table = QtWidgets.QTableWidget(0, 3)
self.prohibited_table.setHorizontalHeaderLabels(["단어", "등급", "비고"])
layout.addWidget(self.prohibited_table)
btn_layout = QtWidgets.QHBoxLayout()
self.add_word_button = QtWidgets.QPushButton("추가")
self.remove_word_button = QtWidgets.QPushButton("삭제")
btn_layout.addWidget(self.add_word_button)
btn_layout.addWidget(self.remove_word_button)
layout.addLayout(btn_layout)
self.add_word_button.clicked.connect(self.add_prohibited_word)
self.remove_word_button.clicked.connect(self.remove_prohibited_word)
def add_prohibited_word(self):
row = self.prohibited_table.rowCount()
self.prohibited_table.insertRow(row)
self.prohibited_table.setItem(row, 0, QtWidgets.QTableWidgetItem("dummy_word"))
self.prohibited_table.setItem(row, 1, QtWidgets.QTableWidgetItem("1"))
self.prohibited_table.setItem(row, 2, QtWidgets.QTableWidgetItem("추가됨"))
def remove_prohibited_word(self):
row = self.prohibited_table.currentRow()
if row >= 0:
self.prohibited_table.removeRow(row)
def init_category_tab(self):
layout = QtWidgets.QVBoxLayout(self.category_tab)
layout.addWidget(QtWidgets.QLabel("카테고리 관리"))
self.category_table = QtWidgets.QTableWidget(0, 4)
self.category_table.setHorizontalHeaderLabels(["카테고리", "필터링", "금지여부", "추가배송비"])
layout.addWidget(self.category_table)
for cat in ["전자제품", "의류", "식품"]:
row = self.category_table.rowCount()
self.category_table.insertRow(row)
self.category_table.setItem(row, 0, QtWidgets.QTableWidgetItem(cat))
self.category_table.setItem(row, 1, QtWidgets.QTableWidgetItem("필터링"))
self.category_table.setItem(row, 2, QtWidgets.QTableWidgetItem("미금지"))
self.category_table.setItem(row, 3, QtWidgets.QTableWidgetItem("0"))
def init_playwright_tab(self):
layout = QtWidgets.QVBoxLayout(self.playwright_tab)
layout.addWidget(QtWidgets.QLabel("Playwright 매크로"))
self.playwright_run_button = QtWidgets.QPushButton("실행")
self.playwright_output = QtWidgets.QTextEdit()
self.playwright_output.setReadOnly(True)
layout.addWidget(self.playwright_run_button)
layout.addWidget(self.playwright_output)
self.playwright_run_button.clicked.connect(self.run_playwright_macro)
def run_playwright_macro(self):
self.playwright_run_button.setEnabled(False)
self.playwright_output.append("Playwright 실행 중...")
self.thread = PlaywrightThread()
self.thread.result_signal.connect(self.handle_playwright_result)
self.thread.finished.connect(lambda: self.playwright_run_button.setEnabled(True))
self.thread.start()
def handle_playwright_result(self, result):
self.playwright_output.append(result)
def toggle_tool_panel(self):
if self.tool_toggle_button.isChecked():
self.tool_panel.show_panel()
else:
self.tool_panel.hide()
def show_toggle_menu(self, pos):
menu = QtWidgets.QMenu()
action = QtWidgets.QAction("자동 숨김 모드", self)
action.setCheckable(True)
action.setChecked(self.tool_panel.auto_hide)
action.triggered.connect(self.toggle_auto_hide)
menu.addAction(action)
menu.exec(self.tool_toggle_button.mapToGlobal(pos))
def toggle_auto_hide(self, checked):
self.tool_panel.auto_hide = checked
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

View File

@ -1,83 +0,0 @@
import winreg
import os
def get_browser_path(browser_name):
try:
# 레지스트리 경로 설정
reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"
# 레지스트리 키 열기
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f"{reg_path}\\{browser_name}.exe") as key:
# 브라우저 설치 경로 가져오기
browser_path = winreg.QueryValue(key, None)
return browser_path
except FileNotFoundError:
return None
def get_specific_profile_path(browser_name, profile_name="Default"):
base_profile_path = get_browser_profile_path(browser_name)
if base_profile_path:
# 기본 프로파일 확인
specific_profile_path = os.path.join(base_profile_path, profile_name)
if os.path.exists(specific_profile_path):
return specific_profile_path
else:
# 기본 프로파일이 없으면 Profile 1 확인
fallback_profile_path = os.path.join(base_profile_path, "Profile 1")
return fallback_profile_path if os.path.exists(fallback_profile_path) else None
return None
def get_browser_profile_path(browser_name):
try:
# 레지스트리 경로 설정
reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, f"{reg_path}\\{browser_name}.exe") as key:
# 브라우저 설치 경로 가져오기
browser_path = winreg.QueryValue(key, None)
# 설치 경로에서 기본 프로파일 경로 추정
if browser_name == "chrome":
profile_path = os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\User Data")
elif browser_name == "whale":
profile_path = os.path.expandvars(r"%LOCALAPPDATA%\Naver\Naver Whale\User Data")
else:
return None
return profile_path if os.path.exists(profile_path) else None
except FileNotFoundError:
return None
if __name__ == "__main__":
# Chrome 브라우저 경로 가져오기
chrome_path = get_browser_path("chrome")
if chrome_path:
print(f"Chrome 설치 경로: {chrome_path}")
else:
print("Chrome 브라우저가 설치되어 있지 않습니다.")
# Whale 브라우저 경로 가져오기
whale_path = get_browser_path("whale")
if whale_path:
print(f"Whale 설치 경로: {whale_path}")
else:
print("Whale 브라우저가 설치되어 있지 않습니다.")
# Chrome 브라우저 프로파일 경로 가져오기
chrome_profile = get_browser_profile_path("chrome")
if chrome_profile:
print(f"Chrome 프로파일 경로: {chrome_profile}")
else:
print("Chrome 브라우저 프로파일 경로를 찾을 수 없습니다.")
# Whale 브라우저 프로파일 경로 가져오기
whale_profile = get_browser_profile_path("whale")
if whale_profile:
print(f"Whale 프로파일 경로: {whale_profile}")
else:
print("Whale 브라우저 프로파일 경로를 찾을 수 없습니다.")
# Chrome의 기본 프로파일 경로
chrome_default_profile = get_specific_profile_path("chrome")
print(f"Chrome 기본 프로파일 (Default 없으면 Profile 1): {chrome_default_profile}")
# Whale의 기본 프로파일 경로
whale_default_profile = get_specific_profile_path("whale")
print(f"Whale 기본 프로파일 (Default 없으면 Profile 1): {whale_default_profile}")

View File

@ -1,142 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>북마크 추가 앱 (Eel 버전)</title>
<script type="text/javascript" src="/eel.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.log { white-space: pre-wrap; background: #f0f0f0; padding: 10px; height: 200px; overflow: auto; }
</style>
</head>
<body>
<h1>북마크 추가 앱 (Eel 버전)</h1>
<!-- 비밀번호 입력 -->
<div>
<label for="password">비밀번호:</label>
<input type="password" id="password">
<button onclick="checkPassword()">확인</button>
</div>
<hr>
<!-- 메인 콘텐츠 (비밀번호가 일치할 때 보임) -->
<div id="main-content" style="display:none;">
<h2>DB 입력</h2>
<input type="text" id="excelPath" placeholder="엑셀 파일 경로">
<input type="checkbox" id="removeExistingDB"> 기존 DB 제거
<button onclick="loadExcel()">엑셀 로드</button>
<br><br>
<button onclick="viewData()">데이터 보기</button>
<button onclick="resetExtractCount()">추출 횟수 초기화</button>
<hr>
<h2>북마크 추가 옵션</h2>
<div>
<label>국가:</label>
<select id="country">
<option>미국</option>
<option>유럽</option>
<option selected>중국</option>
<option>일본</option>
<option>한국</option>
<option>기타</option>
<option>랜덤</option>
</select>
<label>등급:</label>
<select id="grade">
<option>일반</option>
<option>파워</option>
<option>빅파워</option>
<option selected>랜덤</option>
</select>
<label>갯수:</label>
<input type="number" id="count" value="1000">
</div>
<div>
<input type="checkbox" id="removeExistingBookmarks">
<label>기존 북마크 제거</label>
<input type="checkbox" id="extractBased">
<label>추출 횟수 기반 추출</label>
<label>최대 추출 횟수:</label>
<input type="number" id="maxExtract" value="1">
</div>
<div>
<label>브라우저 선택:</label>
<select id="browserChoice">
<option>크롬</option>
<option>웨일</option>
</select>
<br>
<label>크롬 경로:</label>
<input type="text" id="chromePath" placeholder="크롬 실행 파일 경로">
<br>
<label>웨일 경로:</label>
<input type="text" id="whalePath" placeholder="웨일 실행 파일 경로">
</div>
<button onclick="runTask()">실행</button>
<hr>
<h2>로그</h2>
<div class="log" id="log"></div>
<div>
<label>진행률:</label>
<progress id="progressBar" value="0" max="100"></progress>
</div>
</div>
<!-- 데이터 보기 결과 (간단한 팝업) -->
<div id="dataDialog" style="display:none; border:1px solid #ccc; padding:10px;"></div>
<script>
function checkPassword() {
let pwd = document.getElementById("password").value;
if(pwd !== "365") {
alert("비밀번호가 일치하지 않습니다. 프로그램을 종료합니다.");
window.close();
} else {
document.getElementById("main-content").style.display = "block";
}
}
function loadExcel() {
let path = document.getElementById("excelPath").value;
let removeExisting = document.getElementById("removeExistingDB").checked;
eel.load_excel(path, removeExisting);
}
function viewData() {
eel.view_data();
}
eel.expose(show_data);
function show_data(htmlData) {
let dlg = document.getElementById("dataDialog");
dlg.innerHTML = htmlData;
dlg.style.display = "block";
setTimeout(()=>{ dlg.style.display = "none"; }, 5000);
}
function resetExtractCount() {
eel.reset_extract_count();
}
function runTask() {
let country = document.getElementById("country").value;
let grade = document.getElementById("grade").value;
let count = document.getElementById("count").value;
let removeExisting = document.getElementById("removeExistingBookmarks").checked;
let extractBased = document.getElementById("extractBased").checked;
let maxExtract = document.getElementById("maxExtract").value;
let browserChoice = document.getElementById("browserChoice").value;
let chromePath = document.getElementById("chromePath").value;
let whalePath = document.getElementById("whalePath").value;
eel.run_task(country, grade, count, removeExisting, extractBased, maxExtract, browserChoice, chromePath, whalePath);
}
eel.expose(log);
function log(msg) {
let logDiv = document.getElementById("log");
logDiv.innerText += msg + "\n";
}
eel.expose(update_progress);
function update_progress(val) {
document.getElementById("progressBar").value = val;
}
eel.expose(task_completed);
function task_completed() {
alert("작업이 완료되었습니다!");
}
</script>
</body>
</html>

Binary file not shown.