Compare commits

...

4 Commits

Author SHA1 Message Date
9700X_PC e806dc0538 업데이트 2025-03-29 17:30:14 +09:00
Envy_PC 3059ba375c . 2025-03-29 16:00:18 +09:00
9700X_PC 49b7172187 1.3.3 2025-03-28 09:11:01 +09:00
Envy_PC c56dcd8a29 . 2025-03-27 15:03:19 +09:00
16 changed files with 1439 additions and 189 deletions

167
main.py
View File

@ -3,6 +3,8 @@ 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
@ -33,6 +35,7 @@ class BookmarkWorker(QThread):
self.browser_path = browser_path
self.selected_browser = selected_browser
self.remove_existing = remove_existing
self.chunk_size = 1000
def run(self):
try:
@ -76,7 +79,7 @@ class BookmarkWorker(QThread):
}
# 하위 폴더 생성
chunk_size = 100 # 하위 폴더에 넣을 북마크 수
chunk_size = self.chunk_size # 하위 폴더에 넣을 북마크 수
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
# 하위 폴더 이름 생성
folder_name = f"거상북마크-{self.folder_name}-{idx}" # 하위 폴더 이름 (예: 거상북마크-중국-1)
@ -294,6 +297,11 @@ class MainWindow(QMainWindow):
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를 삭제하고 새롭게 데이터를 입력합니다.")
@ -332,14 +340,23 @@ 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.remove_existing_checkbox = QCheckBox("기존 북마크 제거")
self.remove_existing_checkbox.setToolTip("체크하면 '거상북마크'로 시작하는 모든 북마크를 제거합니다.")
@ -384,6 +401,9 @@ 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)
@ -400,6 +420,7 @@ class MainWindow(QMainWindow):
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)
@ -421,6 +442,16 @@ 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"
@ -452,6 +483,21 @@ class MainWindow(QMainWindow):
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):
@ -595,6 +641,38 @@ 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:
@ -606,8 +684,16 @@ class MainWindow(QMainWindow):
# 파일 확장자 확인
ext = os.path.splitext(file_path)[-1].lower()
# 필수 열 이름 정의
required_columns = ['country', 'mall_grade', 'mall_name', 'mall_url']
# 열 이름 매핑 정의
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())
# 엑셀 파일 읽기
if ext == ".xls":
@ -630,22 +716,34 @@ class MainWindow(QMainWindow):
self.log("지원되지 않는 파일 형식입니다. .xls 또는 .xlsx 파일을 선택하세요.")
return
# 엑셀에서 가져온 열과 필수 열 비교
available_columns = [col for col in required_columns if col in df.columns]
if not available_columns:
self.log("엑셀 파일에 필수 열이 없습니다. 필요한 열: " + ", ".join(required_columns))
# 엑셀 열 이름과 정의된 이름 매핑
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']}) 중 하나라도 있어야 합니다.")
return
# 필요한 열만 가져오기
df = df[available_columns]
# 필요한 열만 선택 (매핑된 내부 이름 사용)
available_columns = [col for col in all_expected_columns if col in df.columns]
df = df[available_columns].copy()
# 누락된 열은 빈 값으로 추가
for col in required_columns:
# 누락된 열은 빈 값으로 채우기
for col in all_expected_columns:
if col not in df.columns:
df[col] = "" # 누락된 열은 빈 값으로 채움
df[col] = ""
# 열 이름을 정렬하여 설정
df = df[required_columns]
# 열 순서 설정 (데이터베이스 테이블 순서와 맞춤)
df = df[['country', 'mall_grade', 'mall_name', 'mall_url']]
# datetime.time 타입 데이터를 문자열로 변환
def convert_time_to_string(x):
@ -663,6 +761,7 @@ 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 제거 및 새 테이블 생성
@ -686,22 +785,42 @@ class MainWindow(QMainWindow):
# 컬럼이 이미 있는 경우 무시
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():
conn.execute("""
INSERT INTO markets (country, mall_grade, mall_name, mall_url)
VALUES (?, ?, ?, ?)
""", (row['country'], row['mall_grade'], row['mall_name'], row['mall_url']))
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.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)
@ -904,6 +1023,8 @@ class MainWindow(QMainWindow):
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.progress.connect(self.progress_bar.setValue)
self.worker.log.connect(self.log)
self.worker.completed.connect(self.task_completed)

Binary file not shown.

BIN
requirements.txt Normal file

Binary file not shown.

View File

@ -17,14 +17,16 @@ main_file = "main.py"
# 필요한 추가 파일 설정 (예: 리소스 파일, 아이콘 등)
include_files = [
("markets.db", "markets.db"), # 데이터베이스 파일 (필요 시)
("markets.db", "markets.db"),
("추가양식.xlsx", "lib/src/추가양식.xlsx"),
("bookmaker.ico", "lib/src/bookmaker.ico"), # 아이콘 파일 포함 확인
]
# 빌드 옵션
build_options = {
"packages": ["os", "sys", "sqlite3", "subprocess", "psutil", "pygetwindow", "glob","json", "pandas", "datetime", "PySide6"],
"packages": ["os", "sys", "sqlite3", "subprocess", "psutil", "pygetwindow", "glob","json", "pandas", "datetime", "PySide6", "openpyxl", "xlrd"],
"include_files": include_files,
"excludes": [], # tkinter 미사용 시 제외
"excludes": ['PySide6.QtAsyncio.events'], # tkinter 미사용 시 제외
}
# 실행 파일 설정

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
__version__ = "1.3.1"
__version__ = "1.3.4"

440
t2.py
View File

@ -2,22 +2,140 @@ import flet as ft
import sqlite3
import subprocess
import json
from datetime import datetime, time
import threading
import logging
import traceback
import os
import sys
import psutil
import glob
import pandas as pd
from datetime import datetime, time
# 로 기본 설정
# 로 기본 설정
logging.basicConfig(level=logging.INFO)
# --- BookmarkWorker (백그라운드 작업) ---
# =====================================================================
# 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):
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
@ -31,7 +149,6 @@ class BookmarkWorker(threading.Thread):
def run(self):
try:
# JSON 파일 읽기
if not os.path.exists(self.bookmarks_path):
self.log_callback(f"즐겨찾기 JSON 파일을 찾을 수 없습니다: {self.bookmarks_path}")
return
@ -45,91 +162,75 @@ class BookmarkWorker(threading.Thread):
else:
bookmarks_data = json.loads(file_content)
except json.JSONDecodeError as e:
self.log_callback(f"JSON 파일 파 오류 발생: {str(e)}")
self.log_callback(f"JSON 파싱 오류: {e}")
bookmarks_data = {"roots": {"bookmark_bar": {"children": []}}}
self.log_callback("JSON 파일을 기본값으로 초기화합니다.")
# 기존 북마크 제거
if self.remove_existing:
bookmarks_data["roots"]["bookmark_bar"] = self.remove_existing_bookmarks(
bookmarks_data["roots"]["bookmark_bar"]
)
total_bookmarks = len(self.bookmarks)
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": []
}
parent_folder = {"type": "folder", "name": parent_folder_name, "children": []}
# 하위 폴더에 일정 개수씩 북마크 추가 (chunk 단위)
chunk_size = 100
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
folder_name = f"거상북마크-{self.folder_name}-{idx}"
for idx, start in enumerate(range(0, total, chunk_size), start=1):
sub_folder = {
"type": "folder",
"name": folder_name,
"name": f"거상북마크-{self.folder_name}-{idx}",
"children": []
}
for bookmark in self.bookmarks[chunk_start:chunk_start + chunk_size]:
for bm in self.bookmarks[start:start+chunk_size]:
sub_folder["children"].append({
"type": "url",
"name": bookmark['name'],
"url": bookmark['url']
"name": bm["name"],
"url": bm["url"]
})
parent_folder["children"].append(sub_folder)
progress = int((chunk_start + len(self.bookmarks[chunk_start:chunk_start + chunk_size])) / total_bookmarks * 100)
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 file:
json.dump(bookmarks_data, file, indent=4, ensure_ascii=False)
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.log_callback("즐겨찾기 추가 작업이 완료되었습니다!")
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, browser_name=self.selected_browser)
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"오류 발생: {str(e)}\n{traceback.format_exc()}")
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="브라우저"):
# 웨일 브라우저의 경우 기존 프로세스 종료 시도
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("웨일 브라우저의 기존 프로세스를 종료했습니다.")
self.log_callback("웨일 프로세스 종료됨.")
except Exception as e:
self.log_callback(f"웨일 브라우저 프로세스 종료 오류 발생: {str(e)}")
self.log_callback(f"웨일 종료 오류: {e}")
if os.path.exists(browser_path):
profile_directory = os.path.basename(os.path.dirname(bookmarks_path))
profile_dir = os.path.basename(os.path.dirname(bookmarks_path))
if "크롬" in browser_name.lower():
bookmark_page = "chrome://bookmarks/"
page_url = "chrome://bookmarks/"
elif "웨일" in browser_name.lower():
bookmark_page = "whale://bookmarks/"
page_url = "whale://bookmarks/"
else:
bookmark_page = "about:blank"
if profile_directory:
subprocess.Popen([
browser_path,
f"--profile-directory={profile_directory}",
bookmark_page
])
self.log_callback(f"{browser_name} 북마크 관리 페이지를 '{profile_directory}' 프로필로 열었습니다.")
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, bookmark_page])
self.log_callback(f"{browser_name} 북마크 관리 페이지를 기본 프로필로 열었습니다.")
subprocess.Popen([browser_path, page_url])
self.log_callback(f"{browser_name} 기본 프로필로 북마크 열기")
else:
self.log_callback(f"{browser_name} 실행 파일을 찾을 수 없습니다: {browser_path}")
self.log_callback(f"{browser_name} 경로가 올바르지 않습니다: {browser_path}")
def run_and_focus_copyman(self):
program_name = "@카피맨.exe"
@ -140,16 +241,16 @@ class BookmarkWorker(threading.Thread):
if pid:
self.log_callback(f"{program_name} 실행 중 (PID: {pid})")
if not self.focus_window_by_title(window_title_start):
self.log_callback(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
self.log_callback("카피맨 창을 찾지 못함.")
else:
self.log_callback(f"{program_name} 실행 중이 아님. 실행 시도 중...")
shortcut_path = self.find_shortcut_in_start_menu(shortcut_name)
if shortcut_path:
self.run_program(shortcut_path)
self.log_callback("카피맨 실행 안됨. 실행 시도...")
shortcut = self.find_shortcut_in_start_menu(shortcut_name)
if shortcut:
self.run_program(shortcut)
else:
self.log_callback(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")
self.log_callback("카피맨 바로가기 없음.")
except Exception as e:
self.log_callback(f"카피맨 실행 오류 발생: {str(e)}\n{traceback.format_exc()}")
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"]):
@ -164,23 +265,23 @@ class BookmarkWorker(threading.Thread):
if window.title and window.title.startswith(title_start):
try:
window.activate()
self.log_callback(f"프로그램 창으로 전환: {window.title}")
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}")
self.log_callback(f"포커스 전환 오류: {e}")
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]
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):
@ -188,7 +289,7 @@ class BookmarkWorker(threading.Thread):
subprocess.Popen([shortcut_path], shell=True)
self.log_callback(f"프로그램 실행: {shortcut_path}")
except Exception as e:
self.log_callback(f"프로그램 실행 실패: {e}")
self.log_callback(f"실행 오류: {e}")
def remove_existing_bookmarks(self, node):
if not isinstance(node, dict):
@ -197,46 +298,41 @@ class BookmarkWorker(threading.Thread):
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
]
node["children"] = [self.remove_existing_bookmarks(child)
for child in node["children"]
if self.remove_existing_bookmarks(child) is not None]
return node
# --- Flet 기반 메인 앱 ---
# =====================================================================
# Flet UI와 백엔드 모듈을 통합한 메인 앱
# =====================================================================
def main(page: ft.Page):
page.title = "즐겨찾기 추가 앱 (Flet 버전)"
page.title = "모듈화된 백엔드 & Flet 앱"
page.horizontal_alignment = "center"
page.vertical_alignment = "start"
page.padding = 20
# 전역 상태 변수 (필요에 따라 수정)
db_path = "markets.db"
bookmarks = [] # DB에서 추출한 북마크 목록
chrome_path = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
whale_path = "C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
# 브라우저별 북마크 파일 경로 (사용자 환경에 맞게 설정 필요)
chrome_bookmarks_path = "C:\\Users\\User\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Bookmarks"
whale_bookmarks_path = "C:\\Users\\User\\AppData\\Local\\Naver\\Naver Whale\\User Data\\Default\\Bookmarks"
# 기본 비밀번호 및 DB 핸들러 생성
stored_password = "365"
user_password = None # 입력받은 비밀번호 저장
user_password = None
db_handler = DBHandler()
bookmarks_global = [] # 추출된 북마크 저장용
# --- 로그 업데이트 함수 ---
log_display = ft.Text(value="", size=12)
def log(msg: str):
log_display.value += msg + "\n"
# 로그와 진행률 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()
# --- 진행률 업데이트 함수 ---
progress_bar = ft.ProgressBar(width=300, value=0)
def update_progress(val: int):
def update_progress(val):
progress_bar.value = val / 100
page.update()
# --- 작업 완료 후 호출 함수 ---
def task_completed():
log("작업 완료되었습니다!")
log("작업 완료!")
dlg = ft.AlertDialog(
title=ft.Text("완료"),
content=ft.Text("즐겨찾기 추가 작업이 완료되었습니다."),
@ -251,29 +347,29 @@ def main(page: ft.Page):
page.dialog.open = False
page.update()
# --- 비밀번호 입력 다이얼로그 (모달) ---
# 비밀번호 입력 다이얼로그
def show_password_dialog():
def on_password_submit(e):
nonlocal user_password
user_password = password_field.value
user_password = pwd_field.value
if user_password != stored_password:
result_dialog = ft.AlertDialog(
result = ft.AlertDialog(
title=ft.Text("비밀번호 오류"),
content=ft.Text("비밀번호가 일치하지 않습니다. 프로그램을 종료합니다."),
actions=[ft.TextButton("닫기", on_click=lambda e: page.window_close())],
modal=True
)
page.dialog = result_dialog
result_dialog.open = True
page.dialog = result
result.open = True
page.update()
else:
pwd_dlg.open = False
page.update()
password_field = ft.TextField(label="비밀번호", password=True)
pwd_field = ft.TextField(label="비밀번호", password=True)
pwd_dlg = ft.AlertDialog(
title=ft.Text("비밀번호 입력"),
content=ft.Column([password_field]),
content=ft.Column([pwd_field]),
actions=[ft.TextButton("확인", on_click=on_password_submit)],
modal=True
)
@ -283,7 +379,9 @@ def main(page: ft.Page):
show_password_dialog()
# --- UI 컨트롤 ---
# =================================================================
# UI 컨트롤 구성
# =================================================================
country_dropdown = ft.Dropdown(
label="국가",
options=[
@ -323,20 +421,79 @@ def main(page: ft.Page):
value="크롬"
)
db_input_button = ft.ElevatedButton("DB 입력", on_click=lambda e: log("DB 입력 버튼 클릭 - 엑셀 파일 선택 기능 구현 필요"))
view_data_button = ft.ElevatedButton("데이터 보기", on_click=lambda e: log("데이터 보기 버튼 클릭 - DB 테이블 표시 기능 구현 필요"))
chrome_path_button = ft.ElevatedButton("크롬 경로 설정", on_click=lambda e: log("크롬 경로 설정 버튼 클릭 - 파일 선택 구현 필요"))
whale_path_button = ft.ElevatedButton("웨일 경로 설정", on_click=lambda e: log("웨일 경로 설정 버튼 클릭 - 파일 선택 구현 필요"))
run_button = ft.ElevatedButton("실행")
# 파일 선택: 엑셀 파일 입력
file_picker_excel = ft.FilePicker(on_result=lambda e: on_excel_selected(e))
page.overlay.append(file_picker_excel)
# --- 실행 버튼 이벤트 (run_task) ---
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:
count = int(count_field.value)
cnt = int(count_field.value)
except:
count = 1000
cnt = 1000
remove_existing = remove_existing_checkbox.value
extract_based = extract_based_checkbox.value
try:
@ -344,82 +501,41 @@ def main(page: ft.Page):
except:
max_extract = 1
# DB에서 조건에 맞는 데이터 추출 (간략화된 예제)
try:
conn = sqlite3.connect(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)
conn.close()
if df.empty:
log("추출 가능한 데이터가 없습니다.")
return
nonlocal bookmarks
bookmarks = df.to_dict("records")
log(f"{len(bookmarks)}개의 북마크를 추출했습니다.")
except Exception as ex:
log(f"DB 쿼리 실행 중 오류 발생: {str(ex)}")
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("크롬 브라우저가 선택되었습니다.")
log("크롬 브라우저 선택됨.")
else:
selected_bookmarks_path = whale_bookmarks_path
selected_browser_path = whale_path
selected_browser = "웨일"
log("웨일 브라우저가 선택되었습니다.")
log("웨일 브라우저 선택됨.")
if not os.path.exists(selected_browser_path):
log("선택된 브라우저 실행 파일 경로가 유효하지 않습니다.")
log("브라우저 실행 파일 경로가 유효하지 않습니다.")
return
if not os.path.exists(selected_bookmarks_path):
log("선택된 브라우저 북마크 경로가 유효하지 않습니다.")
log("브라우저 북마크 경로가 유효하지 않습니다.")
return
worker = BookmarkWorker(
bookmarks, folder_name, selected_bookmarks_path,
selected_browser_path, selected_browser, remove_existing,
update_progress, log, task_completed
)
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])
browser_row = ft.Row(controls=[browser_dropdown, chrome_path_button, whale_path_button])
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("로그:", size=14),
log_display
)
page.add(controls_row, extract_row, browser_row, action_row, progress_bar, ft.Text("로그:"), log_text)
page.update()
ft.app(target=main)

Binary file not shown.

20
test/chrome_sync.py Normal file
View File

@ -0,0 +1,20 @@
# 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"]

370
test/t3.py Normal file
View File

@ -0,0 +1,370 @@
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))

21
test/t4.py Normal file
View File

@ -0,0 +1,21 @@
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()

458
test/t5.py Normal file
View File

@ -0,0 +1,458 @@
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())

142
web/index.html Normal file
View File

@ -0,0 +1,142 @@
<!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>

BIN
추가양식.xlsx Normal file

Binary file not shown.