This commit is contained in:
Envy_PC 2025-03-27 13:47:41 +09:00
parent 664007167d
commit f766f5706b
7 changed files with 436 additions and 7 deletions

12
main.py
View File

@ -2,6 +2,8 @@ import sqlite3
import subprocess
import json
from datetime import datetime, time
from src.version import __version__
import logging
import traceback
import os
@ -278,7 +280,7 @@ class MainWindow(QMainWindow):
QMessageBox.critical(self, "비밀번호 오류", "비밀번호가 일치하지 않습니다. 프로그램을 종료합니다.")
sys.exit()
self.setWindowTitle("크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님)")
self.setWindowTitle(f"크롬 즐겨찾기 추가 프로그램 (by 내차는언제타냐 feat.거상110+님) - v{__version__}")
self.setGeometry(300, 300, 800, 500)
# UI 구성
@ -333,10 +335,10 @@ class MainWindow(QMainWindow):
# 스핀박스: 갯수
self.count_label = QLabel("갯수")
self.count_spinbox = QSpinBox()
self.count_spinbox.setMinimum(100)
self.count_spinbox.setMaximum(1000)
self.count_spinbox.setSingleStep(100)
self.count_spinbox.setValue(100)
self.count_spinbox.setMinimum(1000)
self.count_spinbox.setMaximum(50000)
self.count_spinbox.setSingleStep(1000)
self.count_spinbox.setValue(1000)
# 기존 북마크 제거 체크박스
self.remove_existing_checkbox = QCheckBox("기존 북마크 제거")

View File

@ -1,10 +1,11 @@
from cx_Freeze import setup, Executable
import os
import sys
from src.version import __version__
# 애플리케이션 이름과 버전
application_name = "크롬 즐겨찾기 추가 프로그램"
application_version = "1.2"
application_version = __version__
# 실행 파일 생성 설정
base = None
@ -40,7 +41,7 @@ executables = [
setup(
name=application_name,
version=application_version,
description="크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+)",
description=f"크롬 즐겨찾기 추가 프로그램 (내차는언제타냐 feat.110+ - v{__version__})",
options={"build_exe": build_options},
executables=executables,
)

0
src/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

1
src/version.py Normal file
View File

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

425
t2.py Normal file
View File

@ -0,0 +1,425 @@
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
# 로그 기본 설정
logging.basicConfig(level=logging.INFO)
# --- 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:
# JSON 파일 읽기
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 파일 파싱 중 오류 발생: {str(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)
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 단위)
chunk_size = 100
for idx, chunk_start in enumerate(range(0, total_bookmarks, chunk_size), start=1):
folder_name = f"거상북마크-{self.folder_name}-{idx}"
sub_folder = {
"type": "folder",
"name": folder_name,
"children": []
}
for bookmark in self.bookmarks[chunk_start:chunk_start + chunk_size]:
sub_folder["children"].append({
"type": "url",
"name": bookmark['name'],
"url": bookmark['url']
})
parent_folder["children"].append(sub_folder)
progress = int((chunk_start + len(self.bookmarks[chunk_start:chunk_start + chunk_size])) / total_bookmarks * 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)
self.log_callback("즐겨찾기 추가 작업이 완료되었습니다!")
self.run_browser_with_profile(self.browser_path, self.bookmarks_path, browser_name=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.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"웨일 브라우저 프로세스 종료 중 오류 발생: {str(e)}")
if os.path.exists(browser_path):
profile_directory = os.path.basename(os.path.dirname(bookmarks_path))
if "크롬" in browser_name.lower():
bookmark_page = "chrome://bookmarks/"
elif "웨일" in browser_name.lower():
bookmark_page = "whale://bookmarks/"
else:
bookmark_page = "about:blank"
if profile_directory:
subprocess.Popen([
browser_path,
f"--profile-directory={profile_directory}",
bookmark_page
])
self.log_callback(f"{browser_name} 북마크 관리 페이지를 '{profile_directory}' 프로필로 열었습니다.")
else:
subprocess.Popen([browser_path, bookmark_page])
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(f"'{window_title_start}'로 시작하는 창을 찾을 수 없습니다.")
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)
else:
self.log_callback(f"'{shortcut_name}' 바로가기를 찾을 수 없습니다.")
except Exception as e:
self.log_callback(f"카피맨 실행 중 오류 발생: {str(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_start_menu = os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs")
all_users_start_menu = os.path.expandvars(r"%ProgramData%\Microsoft\Windows\Start Menu\Programs")
for start_menu_path in [user_start_menu, all_users_start_menu]:
shortcut_path = glob.glob(os.path.join(start_menu_path, f"**\\{shortcut_name}.lnk"), recursive=True)
if shortcut_path:
return shortcut_path[0]
return None
def run_program(self, shortcut_path):
try:
subprocess.Popen([shortcut_path], shell=True)
self.log_callback(f"프로그램 실행: {shortcut_path}")
except Exception as e:
self.log_callback(f"프로그램 실행 실패: {e}")
def remove_existing_bookmarks(self, node):
if not isinstance(node, dict):
return node
if node.get("type") == "folder" and node.get("name", "").startswith("거상북마크"):
self.log_callback(f"제거된 폴더: {node.get('name')}")
return None
if "children" in node:
node["children"] = [
self.remove_existing_bookmarks(child)
for child in node["children"]
if self.remove_existing_bookmarks(child) is not None
]
return node
# --- Flet 기반 메인 앱 ---
def main(page: ft.Page):
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"
stored_password = "365"
user_password = None # 입력받은 비밀번호 저장
# --- 로그 업데이트 함수 ---
log_display = ft.Text(value="", size=12)
def log(msg: str):
log_display.value += msg + "\n"
page.update()
# --- 진행률 업데이트 함수 ---
progress_bar = ft.ProgressBar(width=300, value=0)
def update_progress(val: int):
progress_bar.value = val / 100
page.update()
# --- 작업 완료 후 호출 함수 ---
def task_completed():
log("작업이 완료되었습니다!")
dlg = ft.AlertDialog(
title=ft.Text("완료"),
content=ft.Text("즐겨찾기 추가 작업이 완료되었습니다."),
actions=[ft.TextButton("닫기", on_click=lambda e: close_dialog())],
modal=True
)
page.dialog = dlg
dlg.open = True
page.update()
def close_dialog():
page.dialog.open = False
page.update()
# --- 비밀번호 입력 다이얼로그 (모달) ---
def show_password_dialog():
def on_password_submit(e):
nonlocal user_password
user_password = password_field.value
if user_password != stored_password:
result_dialog = 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.update()
else:
pwd_dlg.open = False
page.update()
password_field = ft.TextField(label="비밀번호", password=True)
pwd_dlg = ft.AlertDialog(
title=ft.Text("비밀번호 입력"),
content=ft.Column([password_field]),
actions=[ft.TextButton("확인", on_click=on_password_submit)],
modal=True
)
page.dialog = pwd_dlg
pwd_dlg.open = True
page.update()
show_password_dialog()
# --- UI 컨트롤 ---
country_dropdown = ft.Dropdown(
label="국가",
options=[
ft.dropdown.Option("미국"),
ft.dropdown.Option("유럽"),
ft.dropdown.Option("중국"),
ft.dropdown.Option("일본"),
ft.dropdown.Option("한국"),
ft.dropdown.Option("기타"),
ft.dropdown.Option("랜덤")
],
value="중국"
)
grade_dropdown = ft.Dropdown(
label="등급",
options=[
ft.dropdown.Option("일반"),
ft.dropdown.Option("파워"),
ft.dropdown.Option("빅파워"),
ft.dropdown.Option("랜덤")
],
value="랜덤"
)
count_field = ft.TextField(label="갯수", value="1000", keyboard_type=ft.KeyboardType.NUMBER)
remove_existing_checkbox = ft.Checkbox(label="기존 북마크 제거", value=False)
extract_based_checkbox = ft.Checkbox(label="추출 횟수 기반 추출", value=False)
max_extract_field = ft.TextField(label="최대 추출 횟수", value="1", keyboard_type=ft.KeyboardType.NUMBER)
browser_dropdown = ft.Dropdown(
label="브라우저 선택",
options=[
ft.dropdown.Option("크롬"),
ft.dropdown.Option("웨일")
],
value="크롬"
)
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("실행")
# --- 실행 버튼 이벤트 (run_task) ---
def run_task(e):
country = country_dropdown.value
grade = grade_dropdown.value
try:
count = int(count_field.value)
except:
count = 1000
remove_existing = remove_existing_checkbox.value
extract_based = extract_based_checkbox.value
try:
max_extract = int(max_extract_field.value)
except:
max_extract = 1
# 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)}")
return
folder_name = f"거상북마크-{grade}"
if browser_dropdown.value == "크롬":
selected_bookmarks_path = chrome_bookmarks_path
selected_browser_path = chrome_path
selected_browser = "크롬"
log("크롬 브라우저가 선택되었습니다.")
else:
selected_bookmarks_path = whale_bookmarks_path
selected_browser_path = whale_path
selected_browser = "웨일"
log("웨일 브라우저가 선택되었습니다.")
if not os.path.exists(selected_browser_path):
log("선택된 브라우저 실행 파일 경로가 유효하지 않습니다.")
return
if not os.path.exists(selected_bookmarks_path):
log("선택된 브라우저의 북마크 경로가 유효하지 않습니다.")
return
worker = BookmarkWorker(
bookmarks, 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])
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.update()
ft.app(target=main)