This commit is contained in:
parent
a0689ddb87
commit
0160e89f49
|
|
@ -0,0 +1,63 @@
|
|||
## Why this file is included
|
||||
|
||||
This program has been frozen with cx_Freeze. The freezing process
|
||||
resulted in certain components from the cx_Freeze software being included
|
||||
in the frozen application, in particular bootstrap code for launching
|
||||
the frozen python script. The cx_Freeze software is subject to the
|
||||
license set out below.
|
||||
|
||||
# Licensing
|
||||
|
||||
- Copyright © 2020-2025, Marcelo Duarte.
|
||||
- Copyright © 2007-2019, Anthony Tuininga.
|
||||
- Copyright © 2001-2006, Computronix (Canada) Ltd., Edmonton, Alberta,
|
||||
Canada.
|
||||
- All rights reserved.
|
||||
|
||||
NOTE: This license is derived from the Python Software Foundation
|
||||
License which can be found at
|
||||
<https://docs.python.org/3/license.html#psf-license-agreement-for-python-release>
|
||||
|
||||
## License for cx_Freeze
|
||||
|
||||
1. This LICENSE AGREEMENT is between the copyright holders and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise
|
||||
using cx_Freeze software in source or binary form and its associated
|
||||
documentation.
|
||||
2. Subject to the terms and conditions of this License Agreement, the
|
||||
copyright holders hereby grant Licensee a nonexclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test,
|
||||
perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use cx_Freeze alone or in any derivative
|
||||
version, provided, however, that this License Agreement and this
|
||||
notice of copyright are retained in cx_Freeze alone or in any
|
||||
derivative version prepared by Licensee.
|
||||
3. In the event Licensee prepares a derivative work that is based on or
|
||||
incorporates cx_Freeze or any part thereof, and wants to make the
|
||||
derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary
|
||||
of the changes made to cx_Freeze.
|
||||
4. The copyright holders are making cx_Freeze available to Licensee on
|
||||
an "AS IS" basis. THE COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
|
||||
WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT
|
||||
LIMITATION, THE COPYRIGHT HOLDERS MAKE NO AND DISCLAIM ANY
|
||||
REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY
|
||||
PARTICULAR PURPOSE OR THAT THE USE OF CX_FREEZE WILL NOT INFRINGE
|
||||
ANY THIRD PARTY RIGHTS.
|
||||
5. THE COPYRIGHT HOLDERS SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER
|
||||
USERS OF CX_FREEZE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
||||
DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE
|
||||
USING CX_FREEZE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY THEREOF.
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between the
|
||||
copyright holders and Licensee. This License Agreement does not
|
||||
grant permission to use copyright holder's trademarks or trade name
|
||||
in a trademark sense to endorse or promote products or services of
|
||||
Licensee, or any third party.
|
||||
8. By copying, installing or otherwise using cx_Freeze, Licensee agrees
|
||||
to be bound by the terms and conditions of this License Agreement.
|
||||
|
||||
Computronix® is a registered trademark of Computronix (Canada) Ltd.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
|
|
@ -0,0 +1,59 @@
|
|||
[2025-03-25 21:08:36,340] [MainThread] [DEBUG] [sp_manager.py:update_client_with_token:36] Client updated with JWT token
|
||||
[2025-03-25 21:08:36,340] [MainThread] [DEBUG] [sp_manager.py:login:121] 로그인 성공
|
||||
[2025-03-25 21:08:36,340] [MainThread] [DEBUG] [sp_manager.py:login:122] response : user=User(id='909d2ef8-7053-4006-ab40-49eb49f20383', app_metadata={'provider': 'email', 'providers': ['email']}, user_metadata={'email': 'leensoo1nt@gmail.com', 'email_verified': False, 'phone_verified': False, 'sub': '909d2ef8-7053-4006-ab40-49eb49f20383'}, aud='authenticated', confirmation_sent_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 784925, tzinfo=TzInfo(UTC)), recovery_sent_at=None, email_change_sent_at=None, new_email=None, new_phone=None, invited_at=None, action_link=None, email='leensoo1nt@gmail.com', phone='', created_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 774578, tzinfo=TzInfo(UTC)), confirmed_at=datetime.datetime(2025, 2, 18, 15, 16, 28, 322591, tzinfo=TzInfo(UTC)), email_confirmed_at=datetime.datetime(2025, 2, 18, 15, 16, 28, 322591, tzinfo=TzInfo(UTC)), phone_confirmed_at=None, last_sign_in_at=datetime.datetime(2025, 3, 25, 12, 10, 42, 925387, tzinfo=TzInfo(UTC)), role='authenticated', updated_at=datetime.datetime(2025, 3, 25, 12, 10, 42, 927200, tzinfo=TzInfo(UTC)), identities=[UserIdentity(id='909d2ef8-7053-4006-ab40-49eb49f20383', identity_id='02512f8f-12ee-4925-87f1-7e83f4dca8ed', user_id='909d2ef8-7053-4006-ab40-49eb49f20383', identity_data={'email': 'leensoo1nt@gmail.com', 'email_verified': False, 'phone_verified': False, 'sub': '909d2ef8-7053-4006-ab40-49eb49f20383'}, provider='email', created_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781602, tzinfo=TzInfo(UTC)), last_sign_in_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781558, tzinfo=TzInfo(UTC)), updated_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781602, tzinfo=TzInfo(UTC)))], is_anonymous=False, factors=None) session=Session(provider_token=None, provider_refresh_token=None, access_token='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5MDlkMmVmOC03MDUzLTQwMDYtYWI0MC00OWViNDlmMjAzODMiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzQyOTA4MjQyLCJpYXQiOjE3NDI5MDQ2NDIsImVtYWlsIjoibGVlbnNvbzFudEBnbWFpbC5jb20iLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7ImVtYWlsIjoibGVlbnNvbzFudEBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiOTA5ZDJlZjgtNzA1My00MDA2LWFiNDAtNDllYjQ5ZjIwMzgzIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NDI5MDQ2NDJ9XSwic2Vzc2lvbl9pZCI6ImE5ZjU4Zjk1LTRlYTYtNDkzOC1iNWUxLTRkOTFhYjNjNGQ0MiIsImlzX2Fub255bW91cyI6ZmFsc2V9.V28ngwVftDVEFnezjT1FWy8gj1flJJpZfz8CePZY-dE', refresh_token='PR7Eo-oZTceqiroZhuVP5Q', expires_in=3600, expires_at=1742908242, token_type='bearer', user=User(id='909d2ef8-7053-4006-ab40-49eb49f20383', app_metadata={'provider': 'email', 'providers': ['email']}, user_metadata={'email': 'leensoo1nt@gmail.com', 'email_verified': False, 'phone_verified': False, 'sub': '909d2ef8-7053-4006-ab40-49eb49f20383'}, aud='authenticated', confirmation_sent_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 784925, tzinfo=TzInfo(UTC)), recovery_sent_at=None, email_change_sent_at=None, new_email=None, new_phone=None, invited_at=None, action_link=None, email='leensoo1nt@gmail.com', phone='', created_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 774578, tzinfo=TzInfo(UTC)), confirmed_at=datetime.datetime(2025, 2, 18, 15, 16, 28, 322591, tzinfo=TzInfo(UTC)), email_confirmed_at=datetime.datetime(2025, 2, 18, 15, 16, 28, 322591, tzinfo=TzInfo(UTC)), phone_confirmed_at=None, last_sign_in_at=datetime.datetime(2025, 3, 25, 12, 10, 42, 925387, tzinfo=TzInfo(UTC)), role='authenticated', updated_at=datetime.datetime(2025, 3, 25, 12, 10, 42, 927200, tzinfo=TzInfo(UTC)), identities=[UserIdentity(id='909d2ef8-7053-4006-ab40-49eb49f20383', identity_id='02512f8f-12ee-4925-87f1-7e83f4dca8ed', user_id='909d2ef8-7053-4006-ab40-49eb49f20383', identity_data={'email': 'leensoo1nt@gmail.com', 'email_verified': False, 'phone_verified': False, 'sub': '909d2ef8-7053-4006-ab40-49eb49f20383'}, provider='email', created_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781602, tzinfo=TzInfo(UTC)), last_sign_in_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781558, tzinfo=TzInfo(UTC)), updated_at=datetime.datetime(2025, 2, 18, 15, 15, 24, 781602, tzinfo=TzInfo(UTC)))], is_anonymous=False, factors=None))
|
||||
[2025-03-25 21:08:36,340] [MainThread] [DEBUG] [sp_manager.py:login:123] user_info : {'id': '909d2ef8-7053-4006-ab40-49eb49f20383', 'email': 'leensoo1nt@gmail.com', 'nickname': 'Unknown'}
|
||||
[2025-03-25 21:08:36,559] [MainThread] [WARNING] [sp_manager.py:get_full_user_info:168] user_resp : data=[{'id': '909d2ef8-7053-4006-ab40-49eb49f20383', 'nickname': '리앤수', 'created_at': '2025-02-19T00:15:24.774065', 'updated_at': '2025-02-19T00:15:24.774065', 'membership_level': 'premium', 'last_login': '2025-03-25T12:07:05.488341', 'privacy_consent': True, 'privacy_consent_date': '2025-02-19T00:13:56.357521+09:00', 'license_consent': True, 'license_consent_date': '2025-02-19T00:13:56.357521+09:00', 'username': '한효상', 'email': 'leensoo1nt@gmail.com', 'email_confirmed_at': '2025-02-19T00:16:28.322591+09:00', 'payment_info': None, 'payment_period_end': None, 'requested_api_call_count': None, 'current_concurrent': 24, 'role': None, 'authenticated_by_admin': True, 'price_settings': None, 'category_settings': None}] count=None
|
||||
[2025-03-25 21:08:36,574] [MainThread] [DEBUG] [login_dialog.py:handle_login:119] 로그인 성공: {'id': '909d2ef8-7053-4006-ab40-49eb49f20383', 'nickname': '리앤수', 'created_at': '2025-02-19T00:15:24.774065', 'updated_at': '2025-02-19T00:15:24.774065', 'membership_level': 'premium', 'last_login': '2025-03-25T12:07:05.488341', 'privacy_consent': True, 'privacy_consent_date': '2025-02-19T00:13:56.357521+09:00', 'license_consent': True, 'license_consent_date': '2025-02-19T00:13:56.357521+09:00', 'username': '한효상', 'email': 'leensoo1nt@gmail.com', 'email_confirmed_at': '2025-02-19T00:16:28.322591+09:00', 'payment_info': None, 'payment_period_end': None, 'requested_api_call_count': None, 'current_concurrent': 24, 'role': None, 'authenticated_by_admin': True, 'price_settings': None, 'category_settings': None, 'membership_level_data': {'level': 'premium', 'api_call_limit': 3000, 'max_rows_per_query': 100, 'accessible_tables': ['common_banned_words', 'user_roles'], 'created_at': '2025-01-23T23:55:48.673088', 'concurrent_login_limit': 4, 'updated_at': None}}
|
||||
[2025-03-25 21:08:36,754] [MainThread] [DEBUG] [sp_manager.py:check_membership_validity:563] check_membership_validity - period_end_str : None
|
||||
[2025-03-25 21:08:36,759] [MainThread] [DEBUG] [databaseManager.py:create_table:22] 데이터베이스 테이블 생성 완료
|
||||
[2025-03-25 21:08:36,765] [MainThread] [DEBUG] [databaseManager.py:fetch_all:41] 데이터 로드 완료
|
||||
[2025-03-25 21:08:38,176] [MainThread] [INFO] [main_window.py:on_login_button_clicked:161] 로그인 버튼 클릭 - QR 로그인 요청
|
||||
[2025-03-25 21:08:38,182] [Dummy-2] [DEBUG] [jjim_runner.py:start_browser:113] 작업 디렉토리 변경: D:\py\jjim2\build\exe.win-amd64-3.11
|
||||
[2025-03-25 21:08:38,183] [Dummy-2] [DEBUG] [jjim_runner.py:start_browser:121] 브라우저 실행 파일이 없습니다: D:\py\jjim2\build\exe.win-amd64-3.11\browsers\chromium-1140\chrome-win\chrome.exe
|
||||
[2025-03-25 21:08:38,183] [Dummy-2] [ERROR] [jjim_runner.py:start_browser:180] 브라우저 초기화 오류: 브라우저 실행 파일이 없습니다: D:\py\jjim2\build\exe.win-amd64-3.11\browsers\chromium-1140\chrome-win\chrome.exe
|
||||
Traceback (most recent call last):
|
||||
File "D:\py\jjim2\src\jjim_runner.py", line 122, in start_browser
|
||||
raise FileNotFoundError(f"브라우저 실행 파일이 없습니다: {browser_path}")
|
||||
FileNotFoundError: 브라우저 실행 파일이 없습니다: D:\py\jjim2\build\exe.win-amd64-3.11\browsers\chromium-1140\chrome-win\chrome.exe
|
||||
|
||||
[2025-03-25 21:08:43,929] [MainThread] [INFO] [main_window.py:on_login_button_clicked:161] 로그인 버튼 클릭 - QR 로그인 요청
|
||||
[2025-03-25 21:08:43,931] [Dummy-3] [DEBUG] [jjim_runner.py:start_browser:113] 작업 디렉토리 변경: D:\py\jjim2\build\exe.win-amd64-3.11\lib
|
||||
[2025-03-25 21:08:43,933] [Dummy-3] [DEBUG] [jjim_runner.py:start_browser:127] D:\py\jjim2\build\exe.win-amd64-3.11\lib\browsers\user_data 디렉토리가 생성되었습니다.
|
||||
[2025-03-25 21:08:43,934] [Dummy-3] [DEBUG] [jjim_runner.py:start_browser:132] D:\py\jjim2\build\exe.win-amd64-3.11\lib\browsers\cache 디렉토리가 생성되었습니다.
|
||||
[2025-03-25 21:08:43,936] [Dummy-3] [ERROR] [jjim_runner.py:start_browser:180] 브라우저 초기화 오류: [WinError 2] 지정된 파일을 찾을 수 없습니다
|
||||
Traceback (most recent call last):
|
||||
File "D:\py\jjim2\src\jjim_runner.py", line 143, in start_browser
|
||||
self.playwright = await async_playwright().start()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\py\jjim2\Lib\site-packages\playwright\async_api\_context_manager.py", line 51, in start
|
||||
return await self.__aenter__()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\py\jjim2\Lib\site-packages\playwright\async_api\_context_manager.py", line 46, in __aenter__
|
||||
playwright = AsyncPlaywright(next(iter(done)).result())
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\py\jjim2\Lib\site-packages\playwright\_impl\_transport.py", line 120, in connect
|
||||
self._proc = await asyncio.create_subprocess_exec(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\Python311\Lib\asyncio\subprocess.py", line 223, in create_subprocess_exec
|
||||
transport, protocol = await loop.subprocess_exec(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\Python311\Lib\asyncio\base_events.py", line 1708, in subprocess_exec
|
||||
transport = await self._make_subprocess_transport(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\Python311\Lib\asyncio\windows_events.py", line 399, in _make_subprocess_transport
|
||||
transp = _WindowsSubprocessTransport(self, protocol, args, shell,
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\Python311\Lib\asyncio\base_subprocess.py", line 36, in __init__
|
||||
self._start(args=args, shell=shell, stdin=stdin, stdout=stdout,
|
||||
File "D:\Python311\Lib\asyncio\windows_events.py", line 929, in _start
|
||||
self._proc = windows_utils.Popen(
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
File "D:\Python311\Lib\asyncio\windows_utils.py", line 153, in __init__
|
||||
super().__init__(args, stdin=stdin_rfd, stdout=stdout_wfd,
|
||||
File "D:\Python311\Lib\subprocess.py", line 1026, in __init__
|
||||
self._execute_child(args, executable, preexec_fn, close_fds,
|
||||
File "D:\Python311\Lib\subprocess.py", line 1538, in _execute_child
|
||||
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
FileNotFoundError: [WinError 2] 지정된 파일을 찾을 수 없습니다
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
|
@ -2,6 +2,7 @@
|
|||
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton
|
||||
from PySide6.QtGui import QPixmap, QFont
|
||||
from PySide6.QtCore import QTimer, QCoreApplication, Qt
|
||||
import re
|
||||
|
||||
class QRDialog(QDialog):
|
||||
"""
|
||||
|
|
@ -29,11 +30,36 @@ class QRDialog(QDialog):
|
|||
self.info_label.setFont(QFont("Arial", 12, QFont.Bold))
|
||||
layout.addWidget(self.info_label)
|
||||
|
||||
# 로그인 안내 메시지
|
||||
self.additional_info_label = QLabel(inner_text)
|
||||
self.additional_info_label.setAlignment(Qt.AlignCenter)
|
||||
self.additional_info_label.setFont(QFont("Arial", 10, QFont.Bold))
|
||||
layout.addWidget(self.additional_info_label)
|
||||
|
||||
match = re.search(r'(\d+)', inner_text)
|
||||
if match:
|
||||
number_str = match.group(1)
|
||||
# 최대 한 번 분리하여 앞, 뒤로 나눔
|
||||
parts = re.split(r'\d+', inner_text, maxsplit=1)
|
||||
text_before = parts[0].strip()
|
||||
text_after = parts[1].strip() if len(parts) > 1 else ""
|
||||
else:
|
||||
number_str = ""
|
||||
text_before = inner_text
|
||||
text_after = ""
|
||||
|
||||
# 앞부분 텍스트 라벨
|
||||
self.before_label = QLabel(text_before)
|
||||
self.before_label.setAlignment(Qt.AlignCenter)
|
||||
self.before_label.setFont(QFont("Arial", 10))
|
||||
layout.addWidget(self.before_label)
|
||||
|
||||
# 추출한 숫자를 크게 굵게 표시하는 라벨
|
||||
self.number_label = QLabel(number_str)
|
||||
self.number_label.setAlignment(Qt.AlignCenter)
|
||||
self.number_label.setFont(QFont("Arial", 30, QFont.Bold))
|
||||
layout.addWidget(self.number_label)
|
||||
|
||||
# 뒷부분 텍스트 라벨
|
||||
self.after_label = QLabel(text_after)
|
||||
self.after_label.setAlignment(Qt.AlignCenter)
|
||||
self.after_label.setFont(QFont("Arial", 10))
|
||||
layout.addWidget(self.after_label)
|
||||
|
||||
# 카운트다운 라벨
|
||||
self.countdown_label = QLabel()
|
||||
|
|
@ -68,7 +94,8 @@ class QRDialog(QDialog):
|
|||
else:
|
||||
self.timer.stop()
|
||||
self.reject()
|
||||
|
||||
|
||||
|
||||
|
||||
class LoginSuccessDialog(QDialog):
|
||||
"""로그인 성공 시 표시되는 다이얼로그 (1초 후 자동 닫힘)"""
|
||||
|
|
|
|||
3
main.py
3
main.py
|
|
@ -1,6 +1,8 @@
|
|||
import sys
|
||||
import logging
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from PySide6.QtGui import QIcon
|
||||
|
||||
from src.main_window import MainWindow
|
||||
from src.logger_module import Logger
|
||||
from login.login_dialog import LoginDialog
|
||||
|
|
@ -24,6 +26,7 @@ if __name__ == "__main__":
|
|||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
app.setWindowIcon(QIcon("jjim.ico"))
|
||||
|
||||
# 로그인 다이얼로그 실행 (모달)
|
||||
login_dialog = LoginDialog(logger)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# setup.py
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
base = None
|
||||
if sys.platform == "win32":
|
||||
base = "Win32GUI"
|
||||
|
||||
# 필요한 파일 경로 설정
|
||||
base_dir = os.path.dirname(__file__)
|
||||
browsers_dir = os.path.join(base_dir, 'src', 'browsers')
|
||||
chromium_dir = os.path.join(browsers_dir, 'src', 'browsers', 'chromium-1140')
|
||||
|
||||
# ✅ 기존 포함 파일 + DLL 추가
|
||||
include_files = [
|
||||
('src/browsers/chromium-1140', 'lib/src/browsers/chromium-1140'),
|
||||
('jjim.ico', 'jjim.ico'),
|
||||
]
|
||||
for src, dest in include_files:
|
||||
if not os.path.exists(src):
|
||||
print(f"경로가 존재하지 않습니다: {src}")
|
||||
|
||||
|
||||
build_exe_options = {
|
||||
"packages": [
|
||||
"asyncio",
|
||||
"os",
|
||||
"sys",
|
||||
"re",
|
||||
"logging",
|
||||
"sqlite3",
|
||||
"pandas",
|
||||
"PySide6",
|
||||
"playwright",
|
||||
],
|
||||
'include_files': include_files,
|
||||
"excludes": [
|
||||
],
|
||||
"optimize": 1,
|
||||
"zip_include_packages": ["*"],
|
||||
"zip_exclude_packages": [],
|
||||
}
|
||||
|
||||
executables = [
|
||||
Executable(
|
||||
script="main.py",
|
||||
base=base,
|
||||
target_name="jjim.exe",
|
||||
icon="jjim.ico"
|
||||
)
|
||||
]
|
||||
|
||||
setup(
|
||||
name="Jjim",
|
||||
version="1.0",
|
||||
description="찜기",
|
||||
options={"build_exe": build_exe_options},
|
||||
executables=executables
|
||||
)
|
||||
|
|
@ -24,12 +24,13 @@ class Jjim_Runner(QThread):
|
|||
jjim_complete = Signal(bool, str)
|
||||
|
||||
market_progress_signal = Signal(int)
|
||||
product_progress_signal = Signal(int)
|
||||
product_progress_signal = Signal(str)
|
||||
|
||||
def __init__(self, logger, db_manager):
|
||||
def __init__(self, logger, db_manager, debug_mode):
|
||||
super().__init__()
|
||||
self.logger = logger
|
||||
self.db_manager = db_manager
|
||||
self.debug_mode = debug_mode # 디버그 모드 여부 (True이면 headless False)
|
||||
self.browser = None
|
||||
self.context = None
|
||||
self.page = None
|
||||
|
|
@ -38,6 +39,9 @@ class Jjim_Runner(QThread):
|
|||
self.loop = None
|
||||
self._initialized = False
|
||||
|
||||
def set_debug_mode(self, debug_mode):
|
||||
self.debug_mode = debug_mode
|
||||
|
||||
async def monitor_login(self, page, login_url, timeout=90):
|
||||
"""
|
||||
1초마다 현재 URL을 감시하여, QR 페이지 URL과 달라지면 로그인 완료로 판단.
|
||||
|
|
@ -64,19 +68,35 @@ class Jjim_Runner(QThread):
|
|||
self.loop.run_forever()
|
||||
self.loop.close()
|
||||
|
||||
# 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') # _internal 디렉토리 포함
|
||||
# if os.path.exists(internal_dir): # _internal 디렉토리가 존재하면 base_dir로 설정
|
||||
# return internal_dir
|
||||
|
||||
# else: # 일반 Python 실행 환경
|
||||
# base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# return base_dir
|
||||
|
||||
def get_base_dir(self):
|
||||
"""
|
||||
실행 환경에 따라 base_dir을 설정하는 메서드.
|
||||
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
|
||||
패키징된 경우, 실행 파일의 위치 또는 실행 파일이 있는 폴더 아래의 'lib' 폴더를 기준으로 합니다.
|
||||
개발 환경에서는 src 폴더의 상위 디렉토리를 기준으로 합니다.
|
||||
"""
|
||||
if getattr(sys, 'frozen', False): # 패키징된 경우
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_dir = os.path.dirname(sys.executable)
|
||||
internal_dir = os.path.join(base_dir, '_internal') # _internal 디렉토리 포함
|
||||
if os.path.exists(internal_dir): # _internal 디렉토리가 존재하면 base_dir로 설정
|
||||
return internal_dir
|
||||
|
||||
else: # 일반 Python 실행 환경
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 패키징된 결과물에서는 browsers 폴더가 base_dir이 아니라 base_dir/lib 에 있을 수 있음.
|
||||
if not os.path.exists(os.path.join(base_dir, "browsers")):
|
||||
alt_dir = os.path.join(base_dir, "lib")
|
||||
if os.path.exists(os.path.join(alt_dir, "browsers")):
|
||||
base_dir = alt_dir
|
||||
else:
|
||||
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
||||
return base_dir
|
||||
|
||||
async def start_browser(self):
|
||||
|
|
@ -87,6 +107,12 @@ class Jjim_Runner(QThread):
|
|||
try:
|
||||
|
||||
base_path = self.get_base_dir()
|
||||
|
||||
# 작업 디렉토리를 base_path로 변경
|
||||
os.chdir(base_path)
|
||||
self.logger.log(f"작업 디렉토리 변경: {base_path}", level=logging.DEBUG)
|
||||
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = os.path.join(base_path, "browsers")
|
||||
|
||||
browser_path = os.path.join(base_path, 'browsers', 'chromium-1140', 'chrome-win', 'chrome.exe')
|
||||
user_data_dir = os.path.join(base_path, 'browsers', 'user_data')
|
||||
cache_dir = os.path.join(base_path, 'browsers', 'cache')
|
||||
|
|
@ -116,42 +142,21 @@ class Jjim_Runner(QThread):
|
|||
|
||||
self.playwright = await async_playwright().start()
|
||||
|
||||
# 디버그 모드에 따라 headless 옵션 설정
|
||||
headless_value = False if self.debug_mode else True
|
||||
|
||||
# # 1. 시크릿 브라우저 실행
|
||||
# self.browser = await self.playwright.chromium.launch(
|
||||
# headless=True,
|
||||
# executable_path=browser_path,
|
||||
# args=[
|
||||
# '--disable-popup-blocking',
|
||||
# '--start-maximized',
|
||||
# '--window-size=1920,1080'
|
||||
# ]
|
||||
# )
|
||||
# self.context = await self.browser.new_context()
|
||||
# self.page = await self.context.new_page()
|
||||
|
||||
# 사용자설정 브라우저 실행
|
||||
self.browser = await self.playwright.chromium.launch_persistent_context(
|
||||
user_data_dir=user_data_dir,
|
||||
headless=False,
|
||||
# 1. 시크릿 브라우저 실행
|
||||
self.browser = await self.playwright.chromium.launch(
|
||||
headless=headless_value,
|
||||
executable_path=browser_path,
|
||||
permissions=["geolocation", "notifications"],
|
||||
geolocation={"latitude": 37.5665, "longitude": 126.9780},
|
||||
locale="ko-KR",
|
||||
args=[
|
||||
'--disable-popup-blocking',
|
||||
'--start-maximized',
|
||||
'--window-size=1920,1080'
|
||||
],
|
||||
user_agent=self.user_agent
|
||||
]
|
||||
)
|
||||
|
||||
# launch_persistent_context()는 바로 페이지를 생성할 수 있으므로:
|
||||
self.page = await self.browser.new_page()
|
||||
|
||||
# 첫 번째 기본 탭 닫기
|
||||
if self.browser.pages:
|
||||
await self.browser.pages[0].close()
|
||||
self.context = await self.browser.new_context()
|
||||
self.page = await self.context.new_page()
|
||||
|
||||
# 자동화 흔적 제거 스크립트 추가
|
||||
await self.page.add_init_script("""
|
||||
|
|
@ -169,18 +174,42 @@ class Jjim_Runner(QThread):
|
|||
|
||||
self._initialized = True
|
||||
self.logger.log("브라우저 초기화 완료", level=20)
|
||||
|
||||
# await self.page.goto("https://smartstore.naver.com/tam_design")
|
||||
except Exception as e:
|
||||
self.logger.log(f"브라우저 초기화 오류: {e}", level=40)
|
||||
self.logger.log(f"브라우저 초기화 오류: {e}", level=40, exc_info=True)
|
||||
|
||||
def start_login(self):
|
||||
"""
|
||||
메인윈도우에서 로그인 버튼 클릭 시 호출.
|
||||
브라우저 초기화가 완료될 때까지 기다린 후 로그인 시도를 시작합니다.
|
||||
QThread의 이벤트 루프에서 _start_login() 코루틴을 실행합니다.
|
||||
"""
|
||||
if not self._initialized:
|
||||
self.logger.log("브라우저가 초기화되지 않음", level=40)
|
||||
return
|
||||
asyncio.run_coroutine_threadsafe(self._start_login(), self.loop)
|
||||
import time
|
||||
|
||||
# 최대 3초간 self.loop가 생성될 때까지 기다림
|
||||
timeout = 3
|
||||
start_time = time.time()
|
||||
while self.loop is None:
|
||||
if time.time() - start_time > timeout:
|
||||
self.logger.log("start_login: 이벤트 루프 초기화 대기 시간 초과", level=logging.ERROR)
|
||||
return
|
||||
time.sleep(0.1)
|
||||
|
||||
async def wait_for_init(timeout=30):
|
||||
start = asyncio.get_event_loop().time()
|
||||
while not self._initialized:
|
||||
await asyncio.sleep(0.1)
|
||||
if asyncio.get_event_loop().time() - start > timeout:
|
||||
raise TimeoutError("브라우저 초기화 대기 시간 초과")
|
||||
|
||||
async def start_login_when_ready():
|
||||
try:
|
||||
await wait_for_init()
|
||||
await self._start_login()
|
||||
except Exception as e:
|
||||
self.logger.log(f"start_login 에러: {e}", level=logging.ERROR)
|
||||
|
||||
asyncio.run_coroutine_threadsafe(start_login_when_ready(), self.loop)
|
||||
|
||||
async def _start_login(self):
|
||||
"""
|
||||
|
|
@ -194,7 +223,7 @@ class Jjim_Runner(QThread):
|
|||
try:
|
||||
await self.page.wait_for_selector("img#qrImage", timeout=10000)
|
||||
except Exception as e:
|
||||
self.logger.log(f"QR 코드 로드 오류: {e}", level=40)
|
||||
self.logger.log(f"QR 코드 로드 오류: {e}", level=40, exc_info=True)
|
||||
return
|
||||
await asyncio.sleep(2) # QR 코드 렌더링 대기
|
||||
qrImage = await self.page.query_selector("img#qrImage")
|
||||
|
|
@ -265,79 +294,112 @@ class Jjim_Runner(QThread):
|
|||
self.logger.log(f"마켓 리스트: {market_lists}", level=20)
|
||||
asyncio.run_coroutine_threadsafe(self._start_jjim(market_lists), self.loop)
|
||||
|
||||
|
||||
|
||||
async def _start_jjim(self, market_lists):
|
||||
try:
|
||||
total_markets = len(market_lists)
|
||||
# 전체 마켓 진행률 초기화
|
||||
for m_idx, market in enumerate(market_lists):
|
||||
market_name = market.get("market_name")
|
||||
market_url = market.get("market_url")
|
||||
self.logger.log(f"{market_name} 진행 시작", level=logging.INFO)
|
||||
# 전체상품 페이지 구성
|
||||
cp = 1
|
||||
page_url = market_url.rstrip("/") + f"/category/ALL?cp={cp}"
|
||||
|
||||
# 초기 전체상품 페이지로 이동
|
||||
page_url = market_url.rstrip("/") + "/category/ALL?cp=1"
|
||||
await self.page.goto(page_url)
|
||||
await asyncio.sleep(2)
|
||||
# 전체상품수 요소 추출 (예: <span class="_6lgM26zUO6">(총 <strong>4,061</strong>개)</span>)
|
||||
await asyncio.sleep(2) # 페이지 로드 대기
|
||||
|
||||
# 전체상품수 정보 추출
|
||||
total_info_elem = await self.page.query_selector("div#CategoryProducts span._6lgM26zUO6")
|
||||
total_text = await total_info_elem.inner_text() if total_info_elem else ""
|
||||
# 정규식을 이용해 숫자만 추출
|
||||
match = re.search(r'[\d,]+', total_text)
|
||||
total_products = int(match.group(0).replace(',', '')) if match else 0
|
||||
self.logger.log(f"{market_name}의 전체 상품수: {total_products}", level=logging.DEBUG)
|
||||
clicked_count = 0
|
||||
|
||||
# 현재 마켓 내 상품 찜 진행률 초기화
|
||||
self.product_progress_signal.emit(0)
|
||||
|
||||
# 진행률 계산용 변수 (이미 처리한 버튼 수, 전체 버튼 수)
|
||||
processed_buttons = 0
|
||||
total_buttons_overall = 0
|
||||
|
||||
# 초기 상품 진행률 표시 (예: "0/0")
|
||||
self.product_progress_signal.emit(f"{processed_buttons}/{total_products}")
|
||||
|
||||
# 페이지 이동은 제공된 "다음" 버튼 정보를 이용
|
||||
while True:
|
||||
# 페이지 내 제품 리스트 선택 (예: li 요소)
|
||||
products = await self.page.query_selector_all("div#CategoryProducts li")
|
||||
total_products_page = len(products)
|
||||
self.logger.log(f"{market_name} cp={cp}의 상품수: {total_products_page}", level=logging.DEBUG)
|
||||
# 찜버튼 자체를 모두 선택 (버튼 클래스 "zzim_button" 사용)
|
||||
buttons = await self.page.query_selector_all("div#CategoryProducts button.zzim_button")
|
||||
num_buttons = len(buttons)
|
||||
total_buttons_overall += num_buttons
|
||||
self.logger.log(f"{market_name} 현재 페이지의 찜버튼 개수: {num_buttons}", level=logging.DEBUG)
|
||||
|
||||
for p_idx, product in enumerate(products):
|
||||
btn = await product.query_selector("button[type='button']")
|
||||
if btn is None:
|
||||
continue
|
||||
# 만약 버튼에 disabled 속성이 있다면 클릭하지 않음
|
||||
# 각 버튼에 대해 처리 (이미 눌린 버튼도 count)
|
||||
for b_idx, btn in enumerate(buttons):
|
||||
processed_buttons += 1 # 모든 버튼은 처리 대상으로 포함
|
||||
# 버튼이 비활성화 상태이면 그냥 진행 (click 시도하지 않음)
|
||||
disabled_attr = await btn.get_attribute("disabled")
|
||||
if disabled_attr is not None:
|
||||
continue
|
||||
# aria-pressed 상태 확인
|
||||
state = await btn.get_attribute("aria-pressed")
|
||||
if state == "false":
|
||||
try:
|
||||
await btn.click()
|
||||
clicked_count += 1
|
||||
self.logger.log(f"[{market_name}] {p_idx+1}번 상품 찜하기 클릭", level=logging.DEBUG)
|
||||
await asyncio.sleep(1) # 너무 빠르지 않게
|
||||
except Exception as click_error:
|
||||
self.logger.log(f"[{market_name}] {p_idx+1}번 상품 클릭 오류: {click_error}", level=logging.ERROR, exc_info=True)
|
||||
# 현재 마켓 내 찜 진행률 업데이트
|
||||
if total_products > 0:
|
||||
prod_progress = int((clicked_count / total_products) * 100)
|
||||
self.product_progress_signal.emit(prod_progress)
|
||||
# 다음 페이지 존재 여부 확인
|
||||
next_btn = await self.page.query_selector("div#CategoryProducts a.fAUKm1ewwo._2Ar8-aEUTq._nlog_click")
|
||||
if next_btn:
|
||||
hidden = await next_btn.get_attribute("aria-hidden")
|
||||
if hidden == "true":
|
||||
self.logger.log(f"{market_name}의 마지막 페이지 도달", level=logging.DEBUG)
|
||||
break
|
||||
self.logger.log(f"[{market_name}] {b_idx+1}번째 버튼은 비활성화됨", level=logging.DEBUG)
|
||||
else:
|
||||
await next_btn.click()
|
||||
cp += 1
|
||||
await asyncio.sleep(2)
|
||||
state = await btn.get_attribute("aria-pressed")
|
||||
# 아직 찜되지 않았다면 클릭 시도
|
||||
if state == "false":
|
||||
try:
|
||||
await btn.click()
|
||||
self.logger.log(f"[{market_name}] {b_idx+1}번째 버튼 클릭", level=logging.DEBUG)
|
||||
await asyncio.sleep(0.1)
|
||||
except Exception as click_error:
|
||||
self.logger.log(f"[{market_name}] {b_idx+1}번째 버튼 클릭 오류: {click_error}",
|
||||
level=logging.ERROR, exc_info=True)
|
||||
# 진행률 업데이트: "처리된/전체" 형태로
|
||||
progress_text = f"{processed_buttons}/{total_products}"
|
||||
self.product_progress_signal.emit(progress_text)
|
||||
|
||||
# 페이지 이동: 현재 페이지 번호 추출 후 바로 다음 숫자 버튼이 있으면 클릭, 없으면 "다음" 버튼 클릭
|
||||
current_page_elem = await self.page.query_selector("div#CategoryProducts a.UWN4IvaQza._nlog_click[aria-current='true']")
|
||||
if current_page_elem:
|
||||
current_page_text = await current_page_elem.inner_text()
|
||||
try:
|
||||
current_page = int(current_page_text.strip())
|
||||
except ValueError:
|
||||
self.logger.log("현재 페이지 번호 변환 오류", level=logging.ERROR)
|
||||
break
|
||||
self.logger.log(f"현재 페이지: {current_page}", level=logging.INFO)
|
||||
next_page_clicked = False
|
||||
# 페이지 번호 버튼들에서 현재 번호+1 찾기
|
||||
page_buttons = await self.page.query_selector_all("div#CategoryProducts a.UWN4IvaQza._nlog_click")
|
||||
for btn in page_buttons:
|
||||
text = await btn.inner_text()
|
||||
if text.strip().isdigit():
|
||||
try:
|
||||
page_num = int(text.strip())
|
||||
if page_num == current_page + 1:
|
||||
await btn.click()
|
||||
await asyncio.sleep(0.5)
|
||||
next_page_clicked = True
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
if not next_page_clicked:
|
||||
# 숫자 버튼으로 다음 페이지가 없으면 "다음" 버튼 클릭
|
||||
next_btn = await self.page.query_selector("div#CategoryProducts a.fAUKm1ewwo._2Ar8-aEUTq._nlog_click")
|
||||
if next_btn:
|
||||
hidden = await next_btn.get_attribute("aria-hidden")
|
||||
if hidden == "true":
|
||||
self.logger.log(f"{market_name}의 마지막 페이지 도달", level=logging.DEBUG)
|
||||
break
|
||||
else:
|
||||
await next_btn.click()
|
||||
await asyncio.sleep(0.5)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
self.logger.log("현재 페이지 번호 버튼을 찾을 수 없습니다.", level=logging.ERROR)
|
||||
break
|
||||
|
||||
# 마켓 진행률 업데이트 (전체 마켓 기준)
|
||||
market_progress = int(((m_idx + 1) / total_markets) * 100)
|
||||
self.market_progress_signal.emit(market_progress)
|
||||
self.logger.log(f"{market_name} 처리 완료", level=logging.DEBUG)
|
||||
|
||||
self.jjim_complete.emit(True, "모든 마켓의 찜하기 작업 완료")
|
||||
self.logger.log(f"{market_name} 처리 완료: 처리된 버튼 {processed_buttons} / 전체 버튼 {total_products}", level=logging.DEBUG)
|
||||
|
||||
self.jjim_complete.emit(True, f"[{total_markets}]개 마켓의 [{total_buttons_overall}]개 상품찜 하기 작업 완료 ")
|
||||
except Exception as e:
|
||||
self.logger.log(f"찜 중 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
self.jjim_complete.emit(False, f"찜 중 오류: {e}")
|
||||
self.jjim_complete.emit(False, f"찜 중 오류: {e}", exc_info=True)
|
||||
|
|
|
|||
|
|
@ -27,25 +27,25 @@ class MainWindow(QWidget):
|
|||
self.resize(1000, 700)
|
||||
self.main_layout = QVBoxLayout()
|
||||
|
||||
# 메뉴바 생성
|
||||
self.menu_bar = QMenuBar(self)
|
||||
# # 메뉴바 생성
|
||||
# self.menu_bar = QMenuBar(self)
|
||||
|
||||
# 설정 메뉴
|
||||
self.settings_menu = QMenu("설정", self)
|
||||
self.menu_bar.addMenu(self.settings_menu)
|
||||
# # 설정 메뉴
|
||||
# self.settings_menu = QMenu("설정", self)
|
||||
# self.menu_bar.addMenu(self.settings_menu)
|
||||
|
||||
# 도움말 메뉴
|
||||
self.help_menu = QMenu("도움말", self)
|
||||
self.menu_bar.addMenu(self.help_menu)
|
||||
# # 도움말 메뉴
|
||||
# self.help_menu = QMenu("도움말", self)
|
||||
# self.menu_bar.addMenu(self.help_menu)
|
||||
|
||||
# 도움말 메뉴에 항목 추가
|
||||
self.help_action = QAction("도움말 보기", self)
|
||||
self.help_action.triggered.connect(self.show_help_dialog)
|
||||
self.help_menu.addAction(self.help_action)
|
||||
# # 도움말 메뉴에 항목 추가
|
||||
# self.help_action = QAction("도움말 보기", self)
|
||||
# self.help_action.triggered.connect(self.show_help_dialog)
|
||||
# self.help_menu.addAction(self.help_action)
|
||||
|
||||
# 사용자 정보 메뉴
|
||||
self.user_menu = QMenu("사용자 정보", self)
|
||||
self.menu_bar.addMenu(self.user_menu)
|
||||
# # 사용자 정보 메뉴
|
||||
# self.user_menu = QMenu("사용자 정보", self)
|
||||
# self.menu_bar.addMenu(self.user_menu)
|
||||
|
||||
# 로그창 추가
|
||||
self.log_display = QTextEdit()
|
||||
|
|
@ -68,7 +68,7 @@ class MainWindow(QWidget):
|
|||
self.debug_toggle.clicked.connect(self.on_debug_toggle)
|
||||
|
||||
# 스타일 적용 (모던한 폰트, 패딩 등)
|
||||
button_style = """
|
||||
self.button_style = """
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
|
|
@ -81,8 +81,24 @@ class MainWindow(QWidget):
|
|||
background-color: #005F99;
|
||||
}
|
||||
"""
|
||||
self.login_button.setStyleSheet(button_style)
|
||||
self.jjim_button.setStyleSheet(button_style)
|
||||
|
||||
# 로그인 완료 스타일 적용 (모던한 폰트, 패딩 등)
|
||||
self.complete_button_style = """
|
||||
QPushButton {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background-color: #AAAAAA; /* 회색 배경 */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #888888; /* 마우스 오버 시 좀 더 어두운 회색 */
|
||||
}
|
||||
"""
|
||||
|
||||
self.login_button.setStyleSheet(self.button_style)
|
||||
self.jjim_button.setStyleSheet(self.complete_button_style)
|
||||
self.top_button_layout.addWidget(self.login_button)
|
||||
self.top_button_layout.addWidget(self.jjim_button)
|
||||
self.top_button_layout.addWidget(self.debug_toggle)
|
||||
|
|
@ -137,8 +153,16 @@ class MainWindow(QWidget):
|
|||
# GUI 로그 연결
|
||||
self.logger.set_gui_logger(self.append_gui_log)
|
||||
|
||||
self.jjim_runner = None
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_login_button_clicked(self):
|
||||
self.logger.log("로그인 버튼 클릭 - QR 로그인 요청", level=logging.INFO)
|
||||
debug_mode = self.debug_toggle.isChecked()
|
||||
|
||||
# jjim_runner QThread 인스턴스 생성 및 브라우저 초기화
|
||||
self.jjim_runner = Jjim_Runner(self.logger, self.db_manager)
|
||||
self.jjim_runner = Jjim_Runner(self.logger, self.db_manager, debug_mode)
|
||||
self.jjim_runner.login_ready.connect(self.on_qr_ready)
|
||||
self.jjim_runner.login_complete.connect(self.on_login_complete)
|
||||
self.jjim_runner.login_in_progress.connect(self.on_login_in_progress)
|
||||
|
|
@ -148,10 +172,6 @@ class MainWindow(QWidget):
|
|||
self.jjim_runner.start()
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_login_button_clicked(self):
|
||||
self.logger.log("로그인 버튼 클릭 - QR 로그인 요청", level=logging.INFO)
|
||||
|
||||
# 로그인 프로세스 시작 전에 잠시 기다리라는 메시지창을 띄웁니다.
|
||||
self.show_wait_message("잠시만 기다려주세요.\nQR 코드 로딩 중입니다...")
|
||||
|
||||
|
|
@ -207,9 +227,16 @@ class MainWindow(QWidget):
|
|||
success_dialog = LoginSuccessDialog(self)
|
||||
success_dialog.exec()
|
||||
|
||||
|
||||
# 로그인 완료 후 버튼 활성화
|
||||
self.login_button.setEnabled(False)
|
||||
self.login_button.setStyleSheet(self.complete_button_style)
|
||||
|
||||
|
||||
self.login_button.setText("로그인 완료")
|
||||
|
||||
self.jjim_button.setEnabled(True)
|
||||
self.jjim_button.setStyleSheet(self.button_style)
|
||||
|
||||
@Slot()
|
||||
def on_jjim_button_clicked(self):
|
||||
|
|
@ -226,6 +253,7 @@ class MainWindow(QWidget):
|
|||
def on_debug_toggle(self, state):
|
||||
mode = "ON" if state else "OFF"
|
||||
self.logger.log(f"디버그모드 {mode}", level=logging.INFO)
|
||||
self.jjim_runner.set_debug_mode(state)
|
||||
|
||||
@Slot(bool, str)
|
||||
def on_jjim_complete(self, success, message):
|
||||
|
|
@ -234,27 +262,25 @@ class MainWindow(QWidget):
|
|||
else:
|
||||
QMessageBox.warning(self, "찜찜 실패", message)
|
||||
|
||||
|
||||
def get_base_dir(self):
|
||||
"""
|
||||
실행 환경에 따라 base_dir을 설정하는 메서드.
|
||||
"""
|
||||
if getattr(sys, 'frozen', False): # 패키징된 경우
|
||||
base_dir = os.path.dirname(sys.executable)
|
||||
internal_dir = os.path.join(base_dir, 'src') # _internal 디렉토리 포함
|
||||
if os.path.exists(internal_dir):
|
||||
return internal_dir
|
||||
else:
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
return base_dir
|
||||
|
||||
@Slot(int)
|
||||
def update_market_progress(self, value):
|
||||
self.market_progress_bar.setValue(value)
|
||||
|
||||
@Slot(int)
|
||||
def update_product_progress(self, value):
|
||||
self.product_progress_bar.setValue(value)
|
||||
@Slot(str)
|
||||
def update_product_progress(self, progress_text):
|
||||
"""
|
||||
progress_text: "현재/전체" 형식의 문자열 예) "3/59"
|
||||
"""
|
||||
try:
|
||||
current, total = progress_text.split('/')
|
||||
current = int(current)
|
||||
total = int(total)
|
||||
percentage = int((current / total) * 100) if total != 0 else 0
|
||||
self.product_progress_bar.setValue(percentage)
|
||||
self.product_progress_bar.setFormat(f"{progress_text} ({percentage}%)")
|
||||
except Exception as e:
|
||||
self.product_progress_bar.setValue(0)
|
||||
self.product_progress_bar.setFormat("0/0 (0%)")
|
||||
|
||||
def append_gui_log(self, message):
|
||||
self.log_display.append(message)
|
||||
|
|
|
|||
BIN
temp_qr.png
BIN
temp_qr.png
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.9 KiB |
Loading…
Reference in New Issue