Compare commits
4 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e806dc0538 | |
|
|
3059ba375c | |
|
|
49b7172187 | |
|
|
c56dcd8a29 |
167
main.py
167
main.py
|
|
@ -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)
|
||||
|
|
|
|||
BIN
markets.db
BIN
markets.db
Binary file not shown.
Binary file not shown.
8
setup.py
8
setup.py
|
|
@ -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.
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
__version__ = "1.3.1"
|
||||
__version__ = "1.3.4"
|
||||
|
|
|
|||
440
t2.py
440
t2.py
|
|
@ -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.
|
|
@ -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"]
|
||||
|
|
@ -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))
|
||||
|
|
@ -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()
|
||||
|
|
@ -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())
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue