From 569c38282508b424c42e2b050514fee373eacc23 Mon Sep 17 00:00:00 2001 From: R5600U_PC Date: Mon, 23 Sep 2024 23:57:58 +0900 Subject: [PATCH] first commit --- .gitignore | 4 + Include/site/python3.11/greenlet/greenlet.h | 164 ++++ __pycache__/base64_to_image.cpython-311.pyc | Bin 0 -> 7965 bytes __pycache__/browser_control.cpython-311.pyc | Bin 0 -> 30130 bytes __pycache__/gui.cpython-311.pyc | Bin 0 -> 15853 bytes __pycache__/logger_module.cpython-311.pyc | Bin 0 -> 3721 bytes __pycache__/toggleSwitch.cpython-311.pyc | Bin 0 -> 6303 bytes __pycache__/whale_translator.cpython-311.pyc | Bin 0 -> 10694 bytes appTranslator.log | 846 +++++++++++++++++++ base64_to_image.py | 121 +++ browser_control.py | 484 +++++++++++ gui.py | 265 ++++++ logger_module.py | 56 ++ main.py | 16 + requirements.txt | Bin 0 -> 1048 bytes toggleSwitch.py | 84 ++ translator.py | 20 + whale_translator.py | 172 ++++ win.py | 15 + 19 files changed, 2247 insertions(+) create mode 100644 .gitignore create mode 100644 Include/site/python3.11/greenlet/greenlet.h create mode 100644 __pycache__/base64_to_image.cpython-311.pyc create mode 100644 __pycache__/browser_control.cpython-311.pyc create mode 100644 __pycache__/gui.cpython-311.pyc create mode 100644 __pycache__/logger_module.cpython-311.pyc create mode 100644 __pycache__/toggleSwitch.cpython-311.pyc create mode 100644 __pycache__/whale_translator.cpython-311.pyc create mode 100644 appTranslator.log create mode 100644 base64_to_image.py create mode 100644 browser_control.py create mode 100644 gui.py create mode 100644 logger_module.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 toggleSwitch.py create mode 100644 translator.py create mode 100644 whale_translator.py create mode 100644 win.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24f18cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +Inclode/ +Lib/ +Scripts/ +pyvenv.cfg diff --git a/Include/site/python3.11/greenlet/greenlet.h b/Include/site/python3.11/greenlet/greenlet.h new file mode 100644 index 0000000..d02a16e --- /dev/null +++ b/Include/site/python3.11/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/__pycache__/base64_to_image.cpython-311.pyc b/__pycache__/base64_to_image.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d3d1d455505eded8b5cff7b51645a76a81e69dc GIT binary patch literal 7965 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 Zsco$rMsXZX$;)3qD&PL}Gc585{})T)#nb=* literal 0 HcmV?d00001 diff --git a/__pycache__/browser_control.cpython-311.pyc b/__pycache__/browser_control.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c975d1089e05943b3c6641b7b1bd0103dc28a6a GIT binary patch literal 30130 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 PefSp=|JNzrlHvHj_VGU7 literal 0 HcmV?d00001 diff --git a/__pycache__/gui.cpython-311.pyc b/__pycache__/gui.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf52c9ed4cef73518894759dd9d88e3e673de23a GIT binary patch literal 15853 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 literal 0 HcmV?d00001 diff --git a/__pycache__/toggleSwitch.cpython-311.pyc b/__pycache__/toggleSwitch.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6ed9ae0120965a7dd8e81ea064051672b5666bf GIT binary patch literal 6303 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 X({oA?KqJ-RCx^e|J^y(^KNtUh(&sF2 literal 0 HcmV?d00001 diff --git a/__pycache__/whale_translator.cpython-311.pyc b/__pycache__/whale_translator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ce8f105c2117aee7a7f8f89f11db4d9f226684d GIT binary patch literal 10694 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 +2024-09-23 23:43:29,388 - base64_to_image.py:79 - default_logger - DEBUG - 원본 이미지 다운로드 실패. +2024-09-23 23:43:29,592 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:43:29,601 - whale_translator.py:74 - default_logger - DEBUG - 가상 데스크톱 2로 전환되었습니다. +2024-09-23 23:43:43,417 - whale_translator.py:117 - default_logger - DEBUG - 번역 완료: https://img.alicdn.com/imgextra/i4/2210213220218/O1CN018J25xq1DTtHTtbBlK_!!2210213220218.jpg_Q75.jpg +2024-09-23 23:43:43,429 - whale_translator.py:83 - default_logger - DEBUG - 가상 데스크톱 1로 전환되었습니다. +2024-09-23 23:43:44,431 - browser_control.py:368 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:43:44,431 - browser_control.py:369 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:43:44,605 - base64_to_image.py:53 - default_logger - DEBUG - 이미지 다운로드 중 오류 발생: cannot identify image file <_io.BytesIO object at 0x0000029F835502C0> +2024-09-23 23:43:44,608 - base64_to_image.py:79 - default_logger - DEBUG - 원본 이미지 다운로드 실패. +2024-09-23 23:43:44,811 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:43:44,825 - whale_translator.py:74 - default_logger - DEBUG - 가상 데스크톱 2로 전환되었습니다. +2024-09-23 23:43:58,638 - whale_translator.py:117 - default_logger - DEBUG - 번역 완료: https://img.alicdn.com/imgextra/i1/2210213220218/O1CN01AYmhzJ1DTtHY6lpDn_!!2210213220218.jpg_Q75.jpg +2024-09-23 23:43:58,650 - whale_translator.py:83 - default_logger - DEBUG - 가상 데스크톱 1로 전환되었습니다. +2024-09-23 23:43:59,655 - browser_control.py:368 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:43:59,655 - browser_control.py:369 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:43:59,888 - base64_to_image.py:53 - default_logger - DEBUG - 이미지 다운로드 중 오류 발생: cannot identify image file <_io.BytesIO object at 0x0000029F83550360> +2024-09-23 23:43:59,888 - base64_to_image.py:79 - default_logger - DEBUG - 원본 이미지 다운로드 실패. +2024-09-23 23:44:00,097 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:44:00,111 - whale_translator.py:74 - default_logger - DEBUG - 가상 데스크톱 2로 전환되었습니다. +2024-09-23 23:44:13,901 - whale_translator.py:117 - default_logger - DEBUG - 번역 완료: https://img.alicdn.com/imgextra/i2/2210213220218/O1CN0175owwm1DTtHT8ohnz_!!2210213220218.jpg_Q75.jpg +2024-09-23 23:44:13,915 - whale_translator.py:83 - default_logger - DEBUG - 가상 데스크톱 1로 전환되었습니다. +2024-09-23 23:44:14,916 - browser_control.py:368 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:14,916 - browser_control.py:369 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:15,135 - base64_to_image.py:53 - default_logger - DEBUG - 이미지 다운로드 중 오류 발생: cannot identify image file <_io.BytesIO object at 0x0000029F835503B0> +2024-09-23 23:44:15,135 - base64_to_image.py:79 - default_logger - DEBUG - 원본 이미지 다운로드 실패. +2024-09-23 23:44:15,341 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:44:15,357 - whale_translator.py:74 - default_logger - DEBUG - 가상 데스크톱 2로 전환되었습니다. +2024-09-23 23:44:29,159 - whale_translator.py:117 - default_logger - DEBUG - 번역 완료: https://img.alicdn.com/imgextra/i1/2210213220218/O1CN01xXwaPe1DTtHT8mUZn_!!2210213220218.jpg_Q75.jpg +2024-09-23 23:44:29,173 - whale_translator.py:83 - default_logger - DEBUG - 가상 데스크톱 1로 전환되었습니다. +2024-09-23 23:44:30,177 - browser_control.py:368 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:30,177 - browser_control.py:369 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:30,733 - base64_to_image.py:53 - default_logger - DEBUG - 이미지 다운로드 중 오류 발생: cannot identify image file <_io.BytesIO object at 0x0000029F83550F90> +2024-09-23 23:44:30,733 - base64_to_image.py:79 - default_logger - DEBUG - 원본 이미지 다운로드 실패. +2024-09-23 23:44:30,939 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:44:30,950 - whale_translator.py:74 - default_logger - DEBUG - 가상 데스크톱 2로 전환되었습니다. +2024-09-23 23:44:44,752 - whale_translator.py:117 - default_logger - DEBUG - 번역 완료: https://img.alicdn.com/imgextra/i3/2210213220218/O1CN01GddRXb1DTtHUrEYCO_!!2210213220218.jpg_Q75.jpg +2024-09-23 23:44:44,762 - whale_translator.py:83 - default_logger - DEBUG - 가상 데스크톱 1로 전환되었습니다. +2024-09-23 23:44:45,763 - browser_control.py:368 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:45,763 - browser_control.py:369 - default_logger - DEBUG - 크롬 창으로 포커스 이동. +2024-09-23 23:44:45,763 - base64_to_image.py:83 - default_logger - DEBUG - 클립보드에 Base64 이미지나 'html > whale-ocr' 데이터가 없습니다. +2024-09-23 23:44:45,966 - browser_control.py:331 - default_logger - DEBUG - 이미지 붙여넣기 완료. +2024-09-23 23:44:45,966 - whale_translator.py:77 - default_logger - DEBUG - 가상 데스크톱 전환 중 오류 발생: Desktop number 2 exceeds the number of desktops, 1. +2024-09-23 23:44:45,968 - gui.py:229 - default_logger - DEBUG - 번역 작업 중 오류 발생: (1400, 'SetForegroundWindow', '잘못된 창 핸들입니다.') diff --git a/base64_to_image.py b/base64_to_image.py new file mode 100644 index 0000000..dd4cbb0 --- /dev/null +++ b/base64_to_image.py @@ -0,0 +1,121 @@ +import base64 +import pyperclip +import win32clipboard +from io import BytesIO +from PIL import Image +import requests +import numpy as np +import cv2 +import time +import random + +class base64TOImage: + def __init__(self, app, logger, browser_controller): + self.app = app + self.logger = logger + self.browser_controller = browser_controller # BrowserController 인스턴스를 전달받음 + + def get_clipboard_data(self): + """클립보드의 텍스트 데이터를 가져옵니다.""" + try: + return pyperclip.paste() # 클립보드의 텍스트 데이터를 가져옴 + except Exception as e: + self.logger.debug(f"클립보드 데이터를 가져오는 중 오류 발생: {e}") + return None + + def set_image_to_clipboard(self, image): + """이미지를 클립보드에 넣는 함수 (Windows 전용)""" + output = BytesIO() + image.save(output, "BMP") + data = output.getvalue()[14:] # BMP 헤더 제거 + output.close() + + # 클립보드에 이미지 데이터 넣기 + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + + def base64_to_image(self, base64_data): + """Base64 데이터를 이미지로 변환하는 함수""" + if base64_data.startswith('data:image'): + header, encoded = base64_data.split(',', 1) + img_data = base64.b64decode(encoded) + image = Image.open(BytesIO(img_data)) + return image + else: + self.logger.debug("유효하지 않은 Base64 이미지 데이터입니다.") + return None + + def download_image_from_url(self, url, max_retries=3): + """URL에서 이미지를 다운로드하고 PIL 이미지 객체로 반환""" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate, br", + "DNT": "1", # Do Not Track 요청 헤더 + "Connection": "keep-alive", + "Upgrade-Insecure-Requests": "1", + "Cache-Control": "max-age=0" + } + + retries = 0 + while retries < max_retries: + try: + self.logger.debug(f"이미지 URL 다운로드 중: {url}") + response = requests.get(url, headers=headers, stream=True) + + # 상태 코드가 200이 아니면 재시도 + if response.status_code == 200: + # OpenCV로 이미지를 로드하여 변환 + image = np.asarray(bytearray(response.content), dtype="uint8") + image = cv2.imdecode(image, cv2.IMREAD_COLOR) + + # OpenCV에서 이미지를 PIL로 변환 + if image is not None: + pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) + return pil_image + else: + self.logger.debug(f"이미지 파일 형식이 올바르지 않습니다. 대상 URL: {url}") + return None + else: + self.logger.debug(f"이미지 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries + 1}/{max_retries}") + retries += 1 + time.sleep(random.randint(2, 5)) # 2~5초 대기 후 재시도 + except Exception as e: + self.logger.debug(f"이미지 로딩 중 오류 발생: {e}. 재시도 {retries + 1}/{max_retries}") + retries += 1 + time.sleep(random.randint(2, 5)) # 예외 발생 시 대기 후 재시도 + + self.logger.debug("이미지 다운로드 최대 재시도 횟수를 초과했습니다.") + return None + + def process_clipboard(self, original_url): + """클립보드의 내용을 처리하고, 필요한 경우 이미지 변환 또는 URL 이미지 복사""" + clipboard_data = self.get_clipboard_data() + + if clipboard_data.startswith('data:image'): + # 클립보드에 있는 Base64 데이터를 이미지로 변환 + image = self.base64_to_image(clipboard_data) + if image: + self.logger.debug("Base64 이미지 변환 성공, 클립보드에 다시 저장 중...") + self.set_image_to_clipboard(image) + self.logger.debug("이미지가 클립보드에 저장되었습니다.") + else: + self.logger.debug("Base64 이미지 변환 실패.") + + elif clipboard_data.strip() == "html > whale-ocr": + # 'html > whale-ocr' 감지 시 원본 이미지 URL에서 이미지를 다운로드하여 클립보드에 복사 + if original_url: + image = self.download_image_from_url(original_url) + if image: + self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 다시 저장 중...") + self.set_image_to_clipboard(image) + self.logger.debug("원본 이미지가 클립보드에 저장되었습니다.") + else: + self.logger.debug("원본 이미지 다운로드 실패.") + else: + self.logger.debug("원본 이미지 URL을 찾을 수 없습니다.") + else: + self.logger.debug("클립보드에 Base64 이미지나 'html > whale-ocr' 데이터가 없습니다.") diff --git a/browser_control.py b/browser_control.py new file mode 100644 index 0000000..be9b629 --- /dev/null +++ b/browser_control.py @@ -0,0 +1,484 @@ +from playwright.sync_api import sync_playwright +import re +import pyautogui +import time +import win32gui, win32con +from bs4 import BeautifulSoup + +class BrowserController: + def __init__(self, app, logger): + self.app = app + self.logger = logger + self.chrome_window_name = "퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome" + self.whale_window_name = "새 탭 - Whale" + self.chrome_hwnd = None + self.whale_hwnd = None + + self.playwright = None + self.browser = None + self.page = None + + def get_page(self): + return self.page + + def start_browser(self): + """크롬 브라우저 실행 및 페이지 로딩""" + self.logger.debug('크롬 브라우저 실행 중...') + + # Playwright를 수동으로 실행하여 브라우저 유지 + self.playwright = sync_playwright().start() + self.browser = self.playwright.chromium.launch(headless=False) # 브라우저 비헤드리스 모드 실행 + + # 창 크기 설정 (1920x1080) + context = self.browser.new_context( + viewport={"width": 1920, "height": 1080} + ) + + # 페이지 열기 + self.page = context.new_page() + # self.page.goto('https://percenty.co.kr/') # 원하는 페이지로 이동 + self.page.goto('https://percenty.co.kr/signin') # 원하는 페이지로 이동 + self.logger.debug('newPage 로딩 ...') + # 사용자는 이 시점에 로그인 및 상세페이지 편집 모드로 들어감 + + + # 페이지 제목을 가져와서 창 제목으로 활용 + page_title = self.page.title() + self.logger.debug(f'페이지 제목: {page_title}') + + # 창 핸들 찾기 (동적으로 얻은 페이지 제목 사용) + self.chrome_hwnd = self.find_window_by_title(page_title) + if not self.chrome_hwnd: + self.logger.debug('크롬 창을 찾을 수 없습니다.') + else: + self.logger.debug(f'크롬 창 핸들: {self.chrome_hwnd}') + + self.page.wait_for_load_state('networkidle') + + def login(self, admin_id, user_id, admin_password, user_password, is_admin=False): + """로그인 처리""" + self.logger.debug(f'로그인 시도 중: {"관리자" if is_admin else "직원"} 계정') + + if is_admin: + # 관리자 로그인 처리 + self.page.fill('input[placeholder="이메일 주소 입력"]', admin_id) # 관리자 ID 입력 + self.page.fill('input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]', admin_password) # 관리자 비밀번호 입력 + self.page.click('button:has-text("로그인 하기")') # 관리자 로그인 버튼 클릭 + else: + # 관리자 토글 버튼을 클릭해서 직원 로그인 화면 활성화 + admin_toggle = self.page.locator('button[role="switch"]') + if admin_toggle.get_attribute("aria-checked") == "true": + admin_toggle.click() # 관리자 모드에서 직원 모드로 전환 + + self.page.fill('input[placeholder="이메일 주소 입력"]', admin_id) # 관리자 ID 입력 + self.page.fill('input[placeholder="직원 아이디 입력"]', user_id) # 직원 ID 입력 + self.page.fill('input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]', user_password) # 직원 비밀번호 입력 + self.page.click('button:has-text("직원 로그인 하기")') # 직원 로그인 버튼 클릭 + + self.logger.debug(f'로그인 완료: {"관리자" if is_admin else "직원"} 계정') + + self.page.wait_for_load_state('networkidle') + + self.close_ad_if_exists() + + + def close_browser(self): + """브라우저 종료""" + if self.browser: + self.browser.close() + self.playwright.stop() + self.logger.debug('브라우저 종료됨.') + + def find_window_by_title(self, window_name): + """창 제목을 통해 핸들을 찾는 메서드""" + def enum_windows_callback(hwnd, result): + if win32gui.IsWindowVisible(hwnd) and window_name in win32gui.GetWindowText(hwnd): + result.append(hwnd) + result = [] + win32gui.EnumWindows(enum_windows_callback, result) + return result[0] if result else None + + def switch_to_chrome(self): + """크롬으로 포커스 전환""" + if self.chrome_hwnd: + win32gui.ShowWindow(self.chrome_hwnd, win32con.SW_RESTORE) + win32gui.SetForegroundWindow(self.chrome_hwnd) + self.logger.debug('크롬 창으로 포커스 이동.') + else: + self.logger.debug('크롬 창을 찾을 수 없습니다.') + + def switch_to_whale(self): + """웨일 브라우저로 포커스 전환""" + if not self.whale_hwnd: + self.whale_hwnd = self.find_window_by_title(self.whale_window_name) + if self.whale_hwnd: + win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) + win32gui.SetForegroundWindow(self.whale_hwnd) + self.logger.debug('웨일 창으로 포커스 이동.') + else: + self.logger.debug('웨일 창을 찾을 수 없습니다.') + + def extract_image_urls(self): + """HTML에서 이미지 URL 추출 및 img 태그 삭제 후 소스 버튼 다시 클릭""" + self.logger.debug('이미지 URL을 추출 중...') + + # 소스 버튼 클릭 + self.page.click("button[data-cke-tooltip-text='소스']") + self.logger.debug('소스 버튼 클릭 완료.') + + # 'data-value' 속성 값을 추출 (textarea 요소) + textarea = self.page.wait_for_selector('div.ck-source-editing-area') + data_value = textarea.get_attribute("data-value") + + if data_value: + self.logger.debug('data-value 속성에서 HTML 수집 완료.') + + # 이미지 URL 추출 + image_urls = self.fetch_image_urls(data_value) + self.logger.debug(f'추출된 이미지 URL 수: {len(image_urls)}') + # 추출된 URL 반환 + self.logger.debug('img 태그를 삭제 중...') + + self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기 + + # data-value 속성을 가진 요소 선택 + data_value_element = self.page.query_selector('div.ck-source-editing-area') + + new_value = "" + + if data_value_element: + # 속성 변경 (원하는 텍스트로 변경하거나 ""으로 변경) + # self.page.evaluate('(element, value) => element.setAttribute("data-value", value)', data_value_element, new_value) + self.page.evaluate(f'() => document.querySelector("div.ck-source-editing-area").setAttribute("data-value", "{new_value}")') + + # 데이터가 제대로 변경되었는지 확인 + updated_value = data_value_element.get_attribute('data-value') + self.logger(f'Updated data-value: {updated_value}') + + else: + self.logger('Element with data-value not found.') + + + self.logger.debug('img 태그 삭제 완료.') + + # 소스 버튼 다시 클릭 + self.page.click("button[data-cke-tooltip-text='소스']") + self.logger.debug('소스 버튼 재 클릭 완료.') + + return image_urls + else: + self.logger.debug('data-value 속성에 데이터가 없습니다.') + return [] + + def fetch_image_urls(self, html_content): + """ + HTML 콘텐츠에서 모든 태그의 URL을 순서대로 추출 + """ + soup = BeautifulSoup(html_content, 'html.parser') + image_urls = [] + + # class="image_resized"를 가진 모든 태그 찾기 + images_resized = soup.find_all('img', class_='image_resized') + for img in images_resized: + if img and 'src' in img.attrs and img['src'] not in image_urls: + image_urls.append(img['src']) + + #
내부의 모든 태그 찾기 + figures = soup.find_all('figure', class_='image') + for figure in figures: + img_tag = figure.find('img') + if img_tag and 'src' in img_tag.attrs and img_tag['src'] not in image_urls: + image_urls.append(img_tag['src']) + + return image_urls + + def close_ad_if_exists(self): + + """광고 다이얼로그가 있으면 닫기 버튼을 클릭하는 메서드""" + try: + # 광고 다이얼로그가 나타날 때까지 기다림 + dialog_selector = "div.ant-modal-wrap.ant-modal-centered" + close_button_selector = "div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default" + + # 3초 동안 다이얼로그 대기 + self.page.wait_for_selector(dialog_selector, timeout=3000) + self.logger.debug("다이얼로그가 발견되었습니다. 닫기 버튼을 클릭합니다.") + + # 닫기 버튼 클릭 + close_button = self.page.query_selector(close_button_selector) + if close_button: + close_button.click() + self.logger.debug("다이얼로그를 성공적으로 닫았습니다.") + else: + self.logger.debug("닫기 버튼을 찾지 못했습니다.") + + except Exception as e: + # 다이얼로그가 없거나 다른 문제가 발생한 경우 + self.logger.debug(f"다이얼로그가 발견되지 않았거나 오류 발생: {e}") + + def go_to_new_product_page(self): + """신규 상품 등록 페이지로 이동""" + try: + self.page.click('span.ant-menu-title-content:has-text("신규 상품 등록")') + self.logger.debug("신규 상품 등록 페이지로 이동 완료.") + except Exception as e: + self.logger.debug(f"신규 상품 등록 페이지 이동 중 오류: {str(e)}") + + def get_product_edit_buttons(self): + """현재 페이지의 세부사항 수정 및 업로드 버튼을 찾기""" + try: + # 페이지 로딩을 기다림 + self.page.wait_for_load_state('networkidle') # 네트워크 요청이 모두 끝날 때까지 대기 + + # 페이지 끝까지 스크롤하여 모든 동적 요소 로드 + self.scroll_page_to_bottom() + + + # 스크롤하여 모든 버튼을 화면에 표시 (가장 하단까지 스크롤) + self.page.evaluate("""window.scrollTo(0, document.body.scrollHeight);""") + self.logger.debug("페이지를 아래로 스크롤했습니다.") + + # 버튼 선택 (확실한 선택자를 사용하여 확인) + buttons = self.page.locator('button:has-text("세부사항 수정 및 업로드")') + count = buttons.count() + self.logger.debug(f"수정할 상품 개수: {count}") + + # 모든 버튼을 리스트로 반환 + return [buttons.nth(i) for i in range(count)] + + except Exception as e: + self.logger.debug(f"상품 수정 버튼을 찾는 중 오류: {str(e)}") + return [] + + def open_product_edit_dialog(self, button): + """상품 수정 다이얼로그 열기""" + try: + # 요소가 화면에 없을 경우 스크롤하여 보이도록 함 + button.scroll_into_view_if_needed() + self.logger.debug("상품의 '세부사항 수정 및 업로드' 버튼을 화면에 보이도록 스크롤.") + + button.click() + self.logger.debug("세부사항 수정 다이얼로그 열기 완료.") + self.page.wait_for_selector('div.ant-tabs-nav') # 다이얼로그가 완전히 로딩될 때까지 기다림 + except Exception as e: + self.logger.debug(f"세부사항 수정 다이얼로그 열기 중 오류: {str(e)}") + + def click_detail_tab(self): + """상세페이지 탭 클릭""" + try: + self.page.click('div.ant-tabs-tab:has-text("상세페이지")') + self.logger.debug("상세페이지 탭 클릭 완료.") + except Exception as e: + self.logger.debug(f"상세페이지 탭 클릭 중 오류: {str(e)}") + + def extract_image_urls(self): + """상세페이지에서 이미지 URL 추출""" + try: + # 소스 편집 모드로 전환 + self.page.click('button[data-cke-tooltip-text="소스"]') + self.logger.debug("소스 버튼 클릭 완료.") + + # 'data-value' 속성 값을 추출 (textarea 요소) + textarea = self.page.wait_for_selector('div.ck-source-editing-area') + data_value = textarea.get_attribute("data-value") + + # HTML 소스에서 이미지 URL 추출 + image_urls = self.fetch_image_urls(data_value) + self.logger.debug(f'추출된 이미지 URL 수: {len(image_urls)}') + + # HTML 소스에서 이미지 URL 삭제 + self.logger.debug('img 태그를 삭제 중...') + self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기 + # data-value 속성을 가진 요소 선택 + data_value_element = self.page.query_selector('div.ck-source-editing-area') + new_value = "" + if data_value_element: + # 속성 변경 (원하는 텍스트로 변경하거나 ""으로 변경) + # self.page.evaluate('(element, value) => element.setAttribute("data-value", value)', data_value_element, new_value) + self.page.evaluate(f'() => document.querySelector("div.ck-source-editing-area").setAttribute("data-value", "{new_value}")') + # 데이터가 제대로 변경되었는지 확인 + updated_value = data_value_element.get_attribute('data-value') + self.logger.debug(f'Updated data-value: {updated_value}') + else: + self.logger.debug('Element with data-value not found.') + self.logger.debug('img 태그 삭제 완료.') + + + # img 태그의 class 삭제 후 다시 소스 버튼 클릭 + self.page.click('button[data-cke-tooltip-text="소스"]') + self.logger.debug('소스 버튼 재 클릭 완료.') + + return image_urls + except Exception as e: + self.logger.debug(f"이미지 URL 추출 중 오류: {str(e)}") + return [] + + def translate_image(self, url): + """이미지 번역 진행""" + try: + self.whale_translator.translate_image(url) + self.logger.debug(f"이미지 번역 완료: {url}") + except Exception as e: + self.logger.debug(f"이미지 번역 중 오류: {str(e)}") + + def paste_image_in_chrome(self, base64toimage, url): + """크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력""" + try: + self.switch_to_chrome() # 크롬으로 포커스 이동 + base64toimage.process_clipboard(url) # 클립보드 내용이 base64일 경우 이미지로 변환 + pyautogui.hotkey('ctrl', 'v') # 클립보드 이미지 붙여넣기 + pyautogui.press('enter') # 엔터 키 입력 + self.logger.debug("이미지 붙여넣기 완료.") + except Exception as e: + self.logger.debug(f"이미지 붙여넣기 중 오류: {str(e)}") + + def save_product_edit(self): + """상품 수정 후 저장 버튼 클릭""" + try: + self.page.click('button:has-text("저장하기")') + self.logger.debug("상품 수정 내용 저장 완료.") + self.page.keyboard.press("Escape") # ESC로 다이얼로그 닫기 + except Exception as e: + self.logger.debug(f"저장 버튼 클릭 중 오류: {str(e)}") + + def go_to_next_page(self): + """다음 페이지로 이동""" + try: + next_button = self.page.query_selector('li.ant-pagination-item-next') + if next_button: + next_button.click() + self.page.wait_for_load_state('domcontentloaded') + self.logger.debug("다음 페이지로 이동 완료.") + return True + else: + self.logger.debug("다음 페이지가 없습니다.") + return False + except Exception as e: + self.logger.debug(f"다음 페이지로 이동 중 오류: {str(e)}") + return False + + def switch_to_chrome(self): + """크롬으로 포커스 전환""" + try: + if not self.chrome_hwnd: + self.chrome_hwnd = self.find_window_by_title(self.chrome_window_name) + if self.chrome_hwnd: + win32gui.ShowWindow(self.chrome_hwnd, win32con.SW_RESTORE) + win32gui.SetForegroundWindow(self.chrome_hwnd) + self.logger.debug('크롬 창으로 포커스 이동.') + self.logger.debug('크롬 창으로 포커스 이동.') + else: + self.logger.debug('크롬 창을 찾을 수 없습니다.') + self.logger.debug('크롬 창을 찾을 수 없습니다.') + except Exception as e: + self.logger.debug(f"크롬 포커스 전환 중 오류: {str(e)}") + + + + + def scroll_with_wheel(self, direction="down", pause_time=0.5, max_scrolls=20): + """ + 휠 스크롤을 사용하여 페이지를 위나 아래로 천천히 스크롤. + + Parameters: + - direction: 스크롤 방향 ("down"은 아래로, "up"은 위로). + - pause_time: 스크롤 사이의 대기 시간 (초). + - max_scrolls: 최대 스크롤 횟수. + """ + scroll_count = 0 + last_height = self.page.evaluate("document.body.scrollHeight") + + while scroll_count < max_scrolls: + if direction == "down": + # 아래로 스크롤 + self.page.mouse.wheel(0, 1000) + elif direction == "up": + # 위로 스크롤 + self.page.mouse.wheel(0, -1000) + else: + raise ValueError("direction 인자는 'down' 또는 'up'만 허용됩니다.") + + time.sleep(pause_time) + + # 스크롤 후 높이 확인 + new_height = self.page.evaluate("document.body.scrollHeight") + + # 아래로 스크롤 시, 페이지의 끝에 도달한 경우 종료 + if direction == "down" and new_height == last_height: + break + elif direction == "up" and new_height == 0: + break # 위로 스크롤 시, 페이지의 시작에 도달하면 종료 + + last_height = new_height + scroll_count += 1 + + def collect_product_info(self): + """ + 상품 정보를 수집하는 메서드 + """ + try: + # 페이지를 아래로 스크롤하여 모든 상품 로드 + self.scroll_with_wheel('down') + self.scroll_with_wheel('up') + + product_infos = [] + for i in range(1, 51): # 1부터 최대 50까지 상품 처리 + try: + # 각 상품의 CSS 선택자를 동적으로 생성하여 접근 + product_name_selector = f"div#root div:nth-child({i}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div.sc-dPZUQH.mXDuy > span.sc-dSIIpw.Nrwqu.Body3Regular14.CharacterPrimary85" + product_price_selector = f"div#root div:nth-child({i}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div:nth-child(3) > div:nth-child(1) > span.sc-dSIIpw.Nrwqu.Body3Regular14.CharacterPrimary85" + product_image_selector = f"div#root div:nth-child({i}) > div > li > div > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img" + + product_name_element = self.page.locator(product_name_selector) + product_price_element = self.page.locator(product_price_selector) + product_image_element = self.page.locator(product_image_selector) + + if product_name_element and product_price_element and product_image_element: + product_info = { + "name": product_name_element.text_content().strip(), + "price": product_price_element.text_content().strip(), + "image_url": product_image_element.get_attribute('src') + } + self.logger.debug(f"상품 {i}: {product_info}") + product_infos.append(product_info) + except Exception as e: + self.logger.error(f"상품 {i} 정보 수집 중 오류 발생: {str(e)}") + continue + + return product_infos + except Exception as e: + self.logger.error(f"상품 정보 수집 중 오류 발생: {str(e)}") + return [] + + def click_modify_button_by_text(self, index): + """인덱스에 해당하는 '세부사항 수정 및 업로드' 버튼 클릭""" + try: + button_selector = f'(//button[span[text()="세부사항 수정 및 업로드"]])[{index}]' + + # 버튼이 화면에 보이도록 스크롤 후 클릭 + button = self.page.query_selector(button_selector) + if button: + button.scroll_into_view_if_needed() + self.page.evaluate('arguments[0].click();', button) + self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 완료') + else: + self.logger.debug(f'{index}번째 상품의 수정 버튼을 찾지 못했습니다.') + except Exception as e: + self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 중 오류: {str(e)}') + + + def scroll_page_to_bottom(self, pause_time=1): + """페이지의 맨 아래까지 스크롤하여 모든 동적 요소를 로드""" + self.logger.debug('페이지 스크롤 시작...') + previous_height = self.page.evaluate("() => document.body.scrollHeight") + + while True: + self.page.evaluate("window.scrollBy(0, window.innerHeight);") # 한 화면씩 스크롤 + time.sleep(pause_time) # 페이지 로딩 대기 + current_height = self.page.evaluate("() => document.body.scrollHeight") + if current_height == previous_height: + break # 더 이상 스크롤할 내용이 없으면 종료 + previous_height = current_height + self.logger.debug('페이지 스크롤 완료.') + + diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..fdc8de9 --- /dev/null +++ b/gui.py @@ -0,0 +1,265 @@ +from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout +from PyQt5.QtCore import Qt, QRect, QSettings +from toggleSwitch import ToggleSwitch +from browser_control import BrowserController +from whale_translator import WhaleTranslator +from base64_to_image import base64TOImage +class TranslationApp(QWidget): + def __init__(self, logger=None): + super().__init__() + self.initUI() + self.logger = logger + self.settings = QSettings("WhenRideMycar", "TranslationApp") # QSettings 초기화 + self.browser_controller = BrowserController(self, self.logger) + self.whale_translator = WhaleTranslator(self, self.logger, debug_mode=True) # 디버그 모드 켜기 + self.base64TOImage = base64TOImage(self, logger, self.browser_controller) + self.running = False + + # 이전에 저장된 설정 불러오기 + self.load_settings() + + def initUI(self): + self.setWindowFlags(Qt.WindowStaysOnTopHint) + self.setGeometry(QRect(1740, 500, 180, 400)) + self.setWindowTitle('이미지 번역 도구') + + # 로그 + self.log = QTextEdit(self) + self.log.setReadOnly(True) + + # 관리자 토글 + self.admin_toggle = ToggleSwitch(self) + self.admin_toggle.clicked.connect(self.on_toggle_clicked) + + # 관리자 ID 및 PW + self.admin_id_label = QLabel("관리자 ID:", self) + self.admin_id_input = QLineEdit(self) + self.admin_pw_label = QLabel("관리자 PW:", self) + self.admin_pw_input = QLineEdit(self) + self.admin_pw_input.setEchoMode(QLineEdit.Password) + + # 직원 ID 및 PW + self.user_id_label = QLabel("직원 ID:", self) + self.user_id_input = QLineEdit(self) + self.user_pw_label = QLabel("직원 PW:", self) + self.user_pw_input = QLineEdit(self) + self.user_pw_input.setEchoMode(QLineEdit.Password) + + # 크롬 실행 버튼 및 번역 버튼 + self.start_chrome_button = QPushButton('크롬 실행', self) + self.translate_button = QPushButton('번역 시작', self) + self.pause_button = QPushButton('일시정지', self) + self.exit_button = QPushButton('종료', self) + + # 레이아웃 설정 + layout = QVBoxLayout() + + # 관리자 토글 버튼 및 로그인 관련 필드 추가 + toggle_layout = QHBoxLayout() + toggle_layout.addWidget(QLabel("관리자 여부:", self)) + toggle_layout.addWidget(self.admin_toggle) + layout.addLayout(toggle_layout) + + # 관리자 ID/PW + layout.addWidget(self.admin_id_label) + layout.addWidget(self.admin_id_input) + layout.addWidget(self.admin_pw_label) + layout.addWidget(self.admin_pw_input) + + # 직원 ID/PW + layout.addWidget(self.user_id_label) + layout.addWidget(self.user_id_input) + layout.addWidget(self.user_pw_label) + layout.addWidget(self.user_pw_input) + + + # 크롬 및 번역 관련 버튼 + layout.addWidget(self.start_chrome_button) + layout.addWidget(self.translate_button) + layout.addWidget(self.pause_button) + layout.addWidget(self.exit_button) + self.setLayout(layout) + + layout.addWidget(self.log) + + # 기본 상태 설정 + self.on_toggle_clicked(False) + + # 버튼 이벤트 연결 + self.start_chrome_button.clicked.connect(self.start_browser) + self.translate_button.clicked.connect(self.start_translation) + self.pause_button.clicked.connect(self.pause_translation) + self.exit_button.clicked.connect(self.close) + + def on_toggle_clicked(self, is_checked): + """관리자 토글 상태에 따라 필드 활성화/비활성화""" + if is_checked: + # 관리자 모드 + # self.admin_id_label.setVisible(True) + # self.admin_id_input.setVisible(True) + self.admin_pw_label.setVisible(True) + self.admin_pw_input.setVisible(True) + self.user_id_label.setVisible(False) + self.user_id_input.setVisible(False) + self.user_pw_label.setVisible(False) + self.user_pw_input.setVisible(False) + else: + # 직원 모드 + # self.admin_id_label.setVisible(False) + # self.admin_id_input.setVisible(False) + self.admin_pw_label.setVisible(False) + self.admin_pw_input.setVisible(False) + self.user_id_label.setVisible(True) + self.user_id_input.setVisible(True) + self.user_pw_label.setVisible(True) + self.user_pw_input.setVisible(True) + + def start_browser(self): + """크롬 브라우저 실행 후 로그인""" + self.logger.debug('크롬 브라우저를 실행합니다...') + self.browser_controller.start_browser() + + # 관리자 토글 상태에 따라 로그인 + + if self.admin_toggle.isChecked(): + admin_id = self.admin_id_input.text() + admin_pw = self.admin_pw_input.text() + user_id = self.user_id_input.text() + user_pw = self.user_pw_input.text() + self.browser_controller.login(admin_id, user_id, admin_pw, user_pw, is_admin=True) + else: + admin_id = self.admin_id_input.text() + admin_pw = self.admin_pw_input.text() + user_id = self.user_id_input.text() + user_pw = self.user_pw_input.text() + self.browser_controller.login(admin_id, user_id, admin_pw, user_pw, is_admin=False) + + # 로그인 정보 저장 + self.save_settings() + + def save_settings(self): + """QSettings에 사용자 정보 저장""" + self.settings.setValue("admin/id", self.admin_id_input.text()) + self.settings.setValue("admin/pw", self.admin_pw_input.text()) + self.settings.setValue("user/id", self.user_id_input.text()) + self.settings.setValue("user/pw", self.user_pw_input.text()) + self.settings.setValue("admin/toggle", self.admin_toggle.isChecked()) + + def load_settings(self): + """QSettings에서 사용자 정보 불러오기""" + self.admin_id_input.setText(self.settings.value("admin/id", "")) + self.admin_pw_input.setText(self.settings.value("admin/pw", "")) + self.user_id_input.setText(self.settings.value("user/id", "")) + self.user_pw_input.setText(self.settings.value("user/pw", "")) + admin_toggle_state = self.settings.value("admin/toggle", "false") == "true" + self.admin_toggle.setChecked(admin_toggle_state) + self.on_toggle_clicked(admin_toggle_state) + + def start_translation(self): + self.logger.debug('번역 작업을 시작합니다...') + self.running = True # 번역 작업이 시작됨 + + try: + # # 1. 광고 다이얼로그가 나타날 경우 닫기 처리 + # self.logger.debug('광고 다이얼로그 닫기 처리 중...') + # self.browser_controller.close_ad_dialog_if_present() + + # 2. "신규 상품 등록" 페이지로 이동 + self.logger.debug('신규 상품 등록 페이지로 이동 중...') + self.browser_controller.go_to_new_product_page() + + # # Playwright에서 페이지 스크롤 후 "세부사항 수정 및 업로드" 버튼 수집 + # self.browser_controller.scroll_page_to_bottom() + + # 3. 각 상품에 대해 "세부사항 수정 및 업로드" 작업을 수행 + page_number = 1 + while self.running: + self.logger.debug(f'현재 페이지: {page_number}') + + # 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기 + product_buttons = self.browser_controller.get_product_edit_buttons() + + if not product_buttons: + self.logger.debug('수정할 상품이 없습니다. 번역 작업을 종료합니다.') + break + + # 5. 각 상품에 대해 번역 작업 수행 + for index, button in enumerate(product_buttons, start=1): + if not self.running: + self.logger.debug('번역 작업이 중단되었습니다.') + break + + self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...') + + # 상품 수정 다이얼로그 열기 + self.browser_controller.open_product_edit_dialog(button) + + # 상세페이지 탭 클릭 + self.browser_controller.click_detail_tab() + + # 이미지 URL 추출 + image_urls = self.browser_controller.extract_image_urls() + + # 이미지 번역 작업 진행 + for url in image_urls: + if not self.running: + self.logger.debug('번역 작업이 중단되었습니다.') + break + + self.whale_translator.translate_image(url) + self.browser_controller.paste_image_in_chrome(self.base64TOImage, url) + + # 수정 후 저장 + self.logger.debug('상품 세부사항 저장 중...') + self.browser_controller.save_product_edit() + + self.logger.debug('상품 수정 완료.') + + # 6. 다음 페이지로 이동 (있으면) + if not self.browser_controller.go_to_next_page(): + self.logger.debug('더 이상 페이지가 없습니다. 작업을 종료합니다.') + break + page_number += 1 + + if self.running: + self.logger.debug('모든 상품 번역 및 저장 완료.') + self.running = False # 작업 종료 후 상태를 False로 전환 + + except Exception as e: + self.logger.debug(f'번역 작업 중 오류 발생: {str(e)}') + self.running = False + + def start_translation_by_one(self): + self.logger.debug('번역 작업을 시작합니다...') + self.running = True # 번역 작업이 시작됨 + + # Playwright에서 이미지 URL 추출 + image_urls = self.browser_controller.extract_image_urls() + + # 추출된 URL을 WhaleTranslator에 전달하여 번역 진행 + for url in image_urls: + if not self.running: # 작업이 중단되었는지 확인 + self.logger.debug('번역 작업이 중단되었습니다.') + break + + self.whale_translator.translate_image(url) + # 번역 후 크롬으로 포커스를 옮기고 이미지를 붙여넣기 + self.browser_controller.paste_image_in_chrome(self.base64TOImage) + + page = BrowserController.get_page() + page.click('button:has-text("저장하기")') # 모든 이미지 번역 완료 후 저장하기 클릭 + + if self.running: + self.logger.debug('모든 이미지 번역 및 붙여넣기 완료.') + self.running = False # 작업 종료 후 상태를 False로 전환 + + def pause_translation(self): + self.logger.debug('번역 작업을 중단합니다...') + self.running = False # 번역 작업 중단 + + def close(self): + self.logger.debug('프로그램을 종료합니다...') + self.save_settings() + self.browser_controller.close_browser() # 브라우저 종료 + self.whale_translator.close_all_virtual_desktops() + super().close() diff --git a/logger_module.py b/logger_module.py new file mode 100644 index 0000000..79b0c93 --- /dev/null +++ b/logger_module.py @@ -0,0 +1,56 @@ +import logging +import os +from logging.handlers import RotatingFileHandler +from PyQt5.QtCore import pyqtSignal, QObject + +def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, backup_count=5): + """로거 설정을 위한 함수""" + formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s') + + # RotatingFileHandler를 사용하여 로그 파일 설정 + handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8') + handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.setLevel(level) + logger.addHandler(handler) + + # 콘솔 로그 출력을 위한 핸들러가 이미 추가되었는지 확인 + if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers): + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler.setLevel(level) + logger.addHandler(console_handler) + + return logger + +class QTextEditLogger(logging.Handler, QObject): + appendHtml = pyqtSignal(str) # HTML 메시지를 전달할 시그널 정의 + scrollToBottom = pyqtSignal() # 스크롤을 최하단으로 이동시키는 시그널 + + def __init__(self): + logging.Handler.__init__(self) + QObject.__init__(self) + + def emit(self, record): + msg = self.format(record) # 로그 레코드를 문자열로 포매팅 + + color = { + logging.DEBUG: "black", + logging.INFO: "grey", + logging.WARNING: "orange", + logging.ERROR: "red", + logging.CRITICAL: "purple", + }.get(record.levelno, "black") + + # HTML 스타일을 적용한 메시지 생성 + message = f"{msg}
" + self.appendHtml.emit(message) # HTML 메시지로 변경 + self.scrollToBottom.emit() # 스크롤 시그널 발생 + + def close(self): + self.flush() + logging.Handler.close(self) + + def flush(self): + pass # 필요 시 정리 작업 수행 diff --git a/main.py b/main.py new file mode 100644 index 0000000..b317dfa --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +from PyQt5.QtWidgets import QApplication +from gui import TranslationApp +from logger_module import setup_logger + +def main(): + # 로깅 설정 + logger = setup_logger('default_logger', f'appTranslator.log') + + # PyQt5 앱 실행 + app = QApplication([]) + window = TranslationApp(logger) # 로거를 TranslationApp에 전달 + window.show() + app.exec_() + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..dfaac63fb8dbac61cf8d5eea5ccc9f2efee2e08c GIT binary patch literal 1048 zcmYLIO;5r=6ntkBe+q`SAb9Yg7vh1$j~I^xu#_gyRxDrscy;DYmrVnOeY5jsX5aq( zE-bgX?X9)aR(Nmh+iJWU8`wRbf!$bQDZVqiwkunpu1X(k{G`6V1kbKxDSc;(YU=yP zzPsPy*66nbOAd6;Ds?#sz(eN1 zL4{IY+9#^>OC^CmI-0s)>Ulzag@4@3ee9sU6%yH^_t|SqgSEu>*RH9ur5)ghnAX-{ zsT`~PLRMoaX3qv*H+X70I*MB4Mw%Mb=3qL)RcQtIH5jBIN*h%L?bnNyDMawQdI+FX~{0%kD4JS|e z$dmNYQ;Sp3WHF!Of{IK;9gv)6E1aYzhqa?8o1r&5WKehOnX>a2PLC7ac2iAOBE#f` t+en`|x=1DK;?SLtvKgu;F!2@;&l%KRiMLn@?%|sASK>Zru8NGY>pvA|l`jAQ literal 0 HcmV?d00001 diff --git a/toggleSwitch.py b/toggleSwitch.py new file mode 100644 index 0000000..230af5c --- /dev/null +++ b/toggleSwitch.py @@ -0,0 +1,84 @@ +from PyQt5.QtCore import Qt, QRect, QPropertyAnimation, pyqtProperty, pyqtSignal, QPoint +from PyQt5.QtGui import QPainter, QColor +from PyQt5.QtWidgets import QWidget + +class ToggleSwitch(QWidget): + clicked = pyqtSignal(bool) + + def __init__(self, parent=None): + super(ToggleSwitch, self).__init__(parent) + self.setFixedSize(60, 30) + self._checked = False + self._circle_color_checked = QColor('red') + self._circle_color_unchecked = QColor('gray') + self._background_color = QColor('white') + self._circle_pos = QPoint(0, 0) # Circle's initial position. + self.animation = QPropertyAnimation(self, b"circle_pos") + self.animation.setDuration(250) + + self._init_position() + + @pyqtProperty(QPoint) + def circle_pos(self): + return self._circle_pos + + @circle_pos.setter + def circle_pos(self, pos): + self._circle_pos = pos + self.update() + + def _init_position(self): + if self._checked: + self._circle_pos.setX(30) + else: + self._circle_pos.setX(0) + + def mousePressEvent(self, event): + if event.button() == Qt.LeftButton: + self._checked = not self._checked + self.clicked.emit(self._checked) + self._update_animation() + self.update() + super(ToggleSwitch, self).mousePressEvent(event) + + def _update_animation(self): + if self._checked: + self.animation.setStartValue(QPoint(0, 0)) + self.animation.setEndValue(QPoint(30, 0)) + else: + self.animation.setStartValue(QPoint(30, 0)) + self.animation.setEndValue(QPoint(0, 0)) + self.animation.start() + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.setPen(Qt.NoPen) + painter.setBrush(self._background_color) + painter.drawRoundedRect(QRect(0, 0, 60, 30), 15, 15) + + circle_color = self._circle_color_checked if self._checked else self._circle_color_unchecked + + painter.setBrush(circle_color) + painter.drawEllipse(self._circle_pos.x(), self._circle_pos.y(), 30, 30) + + def setChecked(self, checked): + if self._checked != checked: + self._checked = checked + self._update_animation() + self.update() + + def isChecked(self): + return self._checked + + def setState(self, state): + """ToggleSwitch의 상태를 설정합니다. + + Args: + state (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다. + """ + if self._checked != state: + self._checked = state + self._update_animation() + self.clicked.emit(self._checked) + self.update() \ No newline at end of file diff --git a/translator.py b/translator.py new file mode 100644 index 0000000..b1f3e2c --- /dev/null +++ b/translator.py @@ -0,0 +1,20 @@ +import re +from playwright.sync_api import sync_playwright + +def fetch_image_urls(): + with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + page = browser.new_page() + page.goto('https://percentry.co.kr') + # 추가적인 로그인 및 네비게이션 로직 + + # 이미지 URL 수집 + content = page.locator("div.ck-source-editing-area").inner_html() + urls = re.findall(r'src="(https://file\.percenty\.co\.kr[^"]+\.(?:jpg|jpeg|png|gif))"', content) + browser.close() + return urls + +def translate_images(app, image_urls): + for index, url in enumerate(image_urls): + app.log.append(f'{index+1}/{len(image_urls)} 이미지 번역 중...') + # 번역 로직 diff --git a/whale_translator.py b/whale_translator.py new file mode 100644 index 0000000..2e5ac98 --- /dev/null +++ b/whale_translator.py @@ -0,0 +1,172 @@ +import pyautogui +import pyperclip +import time +import win32gui, win32con +from pyvda import VirtualDesktop, get_virtual_desktops +import subprocess + +class WhaleTranslator: + def __init__(self, app, logger, debug_mode=False): + self.app = app + self.logger = logger + self.debug_mode = debug_mode + self.newtab = "about:newtab" + self.whale_window_name = "새 탭 - Whale" + self.whale_hwnd = None + + self.ensure_virtual_desktop_2_exists() + self.start_whale_browser() + self.return_to_virtual_desktop_1() + + + def start_whale_browser(self): + # Whale 브라우저 실행 + whale_path = r"C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe" # whale.exe 경로 지정 + # 웨일을 시크릿 모드로 실행 + subprocess.Popen([whale_path, '--incognito']) + + # subprocess.Popen(["C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe", '--incognito']) # 경로 확인 필요 + + # 창 크기 조정 + time.sleep(2) # 창이 뜰 때까지 대기 + hwnd = win32gui.FindWindow(None, self.whale_window_name) # Whale 브라우저 창 이름 + if hwnd: + win32gui.ShowWindow(hwnd, win32con.SW_NORMAL) + win32gui.SetWindowPos(hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER) + else: + self.logger.debug("Whale 창을 찾을 수 없습니다.") + + def find_whale_window(self): + """웨일 창 핸들을 찾는 메서드""" + if not self.whale_hwnd: + self.whale_hwnd = self.find_window_by_title(self.whale_window_name) + return self.whale_hwnd + + def find_window_by_title(self, window_name): + def enum_windows_callback(hwnd, result): + if win32gui.IsWindowVisible(hwnd) and window_name in win32gui.GetWindowText(hwnd): + result.append(hwnd) + result = [] + win32gui.EnumWindows(enum_windows_callback, result) + return result[0] if result else None + + def ensure_virtual_desktop_2_exists(self): + """가상 데스크톱 2가 존재하는지 확인하고, 없으면 생성""" + try: + # 현재 활성화된 가상 데스크톱 수 확인 + desktops = get_virtual_desktops() + number_of_desktops = len(desktops) + + # 가상 데스크톱 2가 존재하지 않으면 생성 + if number_of_desktops < 2: + pyautogui.hotkey('win', 'ctrl', 'd') # 새 가상데스크탑 생성 + self.logger.debug("가상 데스크톱 2가 생성되었습니다.") + time.sleep(1) + else: + self.switch_to_virtual_desktop_2() + self.close_whale_window_if_exists() + self.logger.debug("가상 데스크톱 2가 이미 존재합니다.") + + except Exception as e: + self.logger.debug(f"가상 데스크톱 확인/생성 중 오류 발생: {e}") + + + def switch_to_virtual_desktop_2(self): + """가상 데스크톱 2로 전환""" + try: + VirtualDesktop(2).go() + self.logger.debug("가상 데스크톱 2로 전환되었습니다.") + time.sleep(1) + except Exception as e: + self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}") + + def return_to_virtual_desktop_1(self): + """가상 데스크톱 1로 복귀""" + try: + VirtualDesktop(1).go() + self.logger.debug("가상 데스크톱 1로 전환되었습니다.") + time.sleep(1) + except Exception as e: + self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}") + + def translate_image(self, url): + # 가상 데스크톱 2에서 웨일 작업 수행 + self.switch_to_virtual_desktop_2() + + if self.find_whale_window(): + win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) # 웨일 창 활성화 + win32gui.SetForegroundWindow(self.whale_hwnd) + + pyautogui.moveTo(960,580) # 마우스 센터로 이동 + + pyautogui.hotkey('ctrl', 'l') # 웨일 브라우저의 주소창으로 이동 + self.enter_url(url) + + pyautogui.rightClick() + time.sleep(0.2) # 컨텍스트 메뉴 대기 + pyautogui.press('c') # 번역된 이미지 클립보드에 복사 + time.sleep(1) # 이미지 로딩 대기 + + pyautogui.rightClick() + time.sleep(0.2) # 컨텍스트 메뉴 대기 + pyautogui.press('r') # 번역 클릭 + time.sleep(5) # 번역 완료 대기 + + pyautogui.rightClick() + time.sleep(0.2) # 컨텍스트 메뉴 대기 + pyautogui.press('c') # 번역된 이미지 클립보드에 복사 + pyautogui.hotkey('ctrl', 'l') # 새 탭으로 이동 + pyautogui.typewrite(self.newtab) # URL을 입력 + self.enter_url(self.newtab) + self.logger.debug(f'번역 완료: {url}') + self.return_to_virtual_desktop_1() + else: + self.logger.debug('웨일 창을 찾을 수 없습니다.') + + def enter_url(self, url): + """입력기를 영어로 전환한 후 pyautogui로 URL 입력""" + # 입력기를 영어로 전환 (한영 전환키 사용) + pyautogui.hotkey('alt', 'shift') # 혹은 'ctrl', 'shift'를 사용할 수도 있음 + time.sleep(0.5) # 입력 모드 전환 후 잠시 대기 + + # 주소창으로 이동 후 URL 입력 + pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동 + time.sleep(0.5) + pyautogui.typewrite(url) # URL 입력 + pyautogui.press('enter') # Enter 키 입력 + time.sleep(1) # 페이지 로딩 대기 + + + def close_whale_window_if_exists(self): + """'새 탭 - Whale' 창이 존재하면 종료""" + whale_window_name = "새 탭 - Whale" + whale_hwnd = self.find_window_by_title(whale_window_name) + + if whale_hwnd: + self.logger.debug(f"'{whale_window_name}' 창을 찾았습니다. 종료 중...") + win32gui.PostMessage(whale_hwnd, win32con.WM_CLOSE, 0, 0) # 창을 종료하는 메시지 전송 + time.sleep(1) + self.logger.debug(f"'{whale_window_name}' 창을 종료했습니다.") + else: + self.logger.debug(f"'{whale_window_name}' 창을 찾지 못했습니다.") + + + def close_all_virtual_desktops(self): + """모든 가상 데스크톱을 종료""" + try: + desktops = get_virtual_desktops() + number_of_desktops = len(desktops) + + # 가상 데스크톱이 1개 이상일 때만 종료 + while number_of_desktops > 1: + self.close_whale_window_if_exists() # 웨일 브라우저 창이 있으면 종료 + pyautogui.hotkey('win', 'ctrl', 'f4') # 현재 가상 데스크톱 닫기 + time.sleep(1) # 각 데스크톱 닫기 사이에 짧은 대기시간 추가 + desktops = get_virtual_desktops() + number_of_desktops = len(desktops) + self.logger.debug(f"남은 가상 데스크톱 수: {number_of_desktops}") + + self.logger.debug("모든 가상 데스크톱이 종료되었습니다.") + + except Exception as e: + self.logger.debug(f"가상 데스크톱 종료 중 오류 발생: {e}") diff --git a/win.py b/win.py new file mode 100644 index 0000000..14af560 --- /dev/null +++ b/win.py @@ -0,0 +1,15 @@ +import win32gui + +def enum_window_titles(): + def callback(hwnd, results): + title = win32gui.GetWindowText(hwnd) + if title: # 제목이 있는 창만 결과에 추가 + results.append(title) + titles = [] + win32gui.EnumWindows(callback, titles) + return titles + +# 모든 창의 제목 출력 +window_titles = enum_window_titles() +for title in window_titles: + print(title)