This commit is contained in:
R5600U_PC 2024-10-15 13:01:36 +09:00
parent 87fd855ef6
commit a4963acd2c
30 changed files with 13034 additions and 138 deletions

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
Include/ Include/
include/
Lib/ Lib/
Scripts/ Scripts/
__pycache__/ __pycache__/
__pycache__/ build/
pyvenv.cfg pyvenv.cfg
*.log *.log

View File

@ -1,6 +1,7 @@
import ctypes import ctypes
import time import time
from ctypes import wintypes from ctypes import wintypes
wintypes.ULONG_PTR = wintypes.WPARAM wintypes.ULONG_PTR = wintypes.WPARAM
hllDll = ctypes.WinDLL("User32.dll", use_last_error=True) hllDll = ctypes.WinDLL("User32.dll", use_last_error=True)
VK_HANGUEL = 0x15 VK_HANGUEL = 0x15

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,6 @@ class BrowserController:
self.source_button_locator = self.locator_manager.get_locator('BrowserControl', 'source_button_locator') self.source_button_locator = self.locator_manager.get_locator('BrowserControl', 'source_button_locator')
self.ck_source_editing_area_locator = self.locator_manager.get_locator('BrowserControl', 'ck_source_editing_area_locator') self.ck_source_editing_area_locator = self.locator_manager.get_locator('BrowserControl', 'ck_source_editing_area_locator')
self.option_input_field_locator = self.locator_manager.get_locator('BrowserControl', 'option_input_field_locator') self.option_input_field_locator = self.locator_manager.get_locator('BrowserControl', 'option_input_field_locator')
self.text_templates = self.locator_manager.selectors.get('DetailPageTextTemplates', {})
self.title_tab_locator = self.locator_manager.get_locator('BrowserControl', 'title_tab_locator') self.title_tab_locator = self.locator_manager.get_locator('BrowserControl', 'title_tab_locator')
self.option_tab_locator = self.locator_manager.get_locator('BrowserControl', 'option_tab_locator') self.option_tab_locator = self.locator_manager.get_locator('BrowserControl', 'option_tab_locator')
self.price_tab_locator = self.locator_manager.get_locator('BrowserControl', 'price_tab_locator') self.price_tab_locator = self.locator_manager.get_locator('BrowserControl', 'price_tab_locator')
@ -53,6 +52,8 @@ class BrowserController:
self.upload_tab_locator = self.locator_manager.get_locator('BrowserControl', 'upload_tab_locator') self.upload_tab_locator = self.locator_manager.get_locator('BrowserControl', 'upload_tab_locator')
self.save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator') self.save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
self.text_templates = self.locator_manager.selectors.get('DetailPageTextTemplates', {})
def get_page(self): def get_page(self):
return self.page return self.page
@ -431,10 +432,12 @@ class BrowserController:
if is_option_data: if is_option_data:
self.logger.debug('옵션 데이터 입력 시작') self.logger.debug('옵션 데이터 입력 시작')
option_data = optionHandler.get_selected_translated_options() option_data = optionHandler.get_selected_translated_options()
self.logger.debug('가져온 옵션 데이터')
self.logger.debug(f'{option_data}')
# 옵션 입력 필드 선택 # 옵션 입력 필드 선택
input_field_locator = self.locator_manager.get_locator('BrowserControl', 'option_input_field_locator') input_field = await self.page.wait_for_selector(self.option_input_field_locator)
input_field = await self.page.wait_for_selector(input_field_locator) await input_field.press('Enter')
# 선두부 텍스트 입력 # 선두부 텍스트 입력
for key in sorted(self.text_templates.keys()): for key in sorted(self.text_templates.keys()):
@ -442,31 +445,37 @@ class BrowserController:
if 'leading_text' in key and leading_text: # leading_text 항목만 가져오기 if 'leading_text' in key and leading_text: # leading_text 항목만 가져오기
await input_field.type(leading_text) await input_field.type(leading_text)
await input_field.press('Enter') await input_field.press('Enter')
await input_field.press('Enter')
self.logger.debug(f"{key} 텍스트 입력 완료: {leading_text}") self.logger.debug(f"{key} 텍스트 입력 완료: {leading_text}")
# 각 옵션을 한 줄씩 입력 # 각 옵션을 한 줄씩 입력
for i, option in enumerate(option_data, start=1): await input_field.press('Enter')
if isinstance(option, tuple): await input_field.type("# > 옵션 목록")
option_text = option[0] # 튜플의 첫 번째 요소를 사용 await input_field.press('Enter')
else: await input_field.press('Enter')
option_text = option
# 옵션을 A. B. 등으로 표시하며 입력
# option_prefix = f"{chr(64 + i)}. " # A, B, C... # 첫 번째 옵션에만 - 기호를 붙여 목록 시작
option_prefix = f"- {chr(64 + i)}. " # 마크다운 목록 A, B, C... await input_field.type(f"- A. {option_data[0]}")
await input_field.press('Enter') # 첫 번째 옵션 이후 엔터로 줄바꿈
# 나머지 옵션들은 - 없이 입력하여 마크다운 목록으로 표시
for i, option in enumerate(option_data[1:], start=2):
option_text = option[0] if isinstance(option, tuple) else option
option_prefix = f"{chr(64 + i)}. "
await input_field.type(option_prefix + option_text) await input_field.type(option_prefix + option_text)
await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈 await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
# 목록 끝을 알리기 위해 엔터 두 번 입력
await input_field.press('Enter')
await input_field.press('Enter')
# 후두부 텍스트 입력 # 후두부 텍스트 입력
await input_field.type('---') await input_field.type('---')
await input_field.press('Enter') await input_field.type('### 나열된 옵션목록 이외의 옵션이 필요하실 경우 고객센터로 연락주세요.')
await input_field.type('나열된 옵션목록 이외의 옵션이 필요하실 경우 고객센터로 연락주세요.')
await input_field.press('Enter') await input_field.press('Enter')
await input_field.type('---') await input_field.type('---')
await input_field.press('Enter') await input_field.press('Enter')
await input_field.press('Enter')
await input_field.press('Enter')
self.logger.debug('옵션 데이터 입력 완료 후 엔터 입력') self.logger.debug('옵션 데이터 입력 완료 후 엔터 입력')
@ -477,6 +486,7 @@ class BrowserController:
async def paste_image_in_chrome(self, clipboardImageManager, url): async def paste_image_in_chrome(self, clipboardImageManager, url):
"""크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력""" """크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력"""
self.logger.debug("크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력")
try: try:
self.switch_to_chrome() # 크롬으로 포커스 이동 self.switch_to_chrome() # 크롬으로 포커스 이동
await clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리 await clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리

View File

@ -2,7 +2,7 @@ import base64
import pyperclip import pyperclip
import win32clipboard import win32clipboard
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image, ImageGrab
import requests import requests
import numpy as np import numpy as np
import cv2 import cv2
@ -22,12 +22,27 @@ class ClipboardImageManager:
self.debug = True self.debug = True
async def get_clipboard_data(self): def get_clipboard_data(self):
"""클립보드의 텍스트 데이터를 가져옵니다.""" """클립보드의 텍스트 또는 이미지 데이터를 가져옵니다."""
self.logger.debug("클립보드의 텍스트 또는 이미지 데이터를 가져옵니다")
try: try:
return pyperclip.paste() # 클립보드의 텍스트 데이터를 가져옴 # 1. 텍스트 데이터 우선 시도
clipboard_text = pyperclip.paste()
if clipboard_text:
return clipboard_text
# 2. 텍스트가 없으면 이미지 확인
self.logger.debug("텍스트 데이터가 없어 이미지 데이터 확인 시도")
image = ImageGrab.grabclipboard()
if isinstance(image, Image.Image): # 이미지 데이터가 있는 경우
self.logger.debug("클립보드에 이미지 데이터가 확인되었습니다.")
return image # PIL 이미지 객체 반환
else:
self.logger.debug("클립보드에 텍스트 또는 이미지 데이터가 없습니다.")
return None
except Exception as e: except Exception as e:
self.logger.debug(f"클립보드 데이터를 가져오는 중 오류 발생: {e}", exc_info=True) self.logger.error(f"클립보드 데이터를 가져오는 중 오류 발생: {e}", exc_info=True)
return None return None
def set_image_to_clipboard(self, image): def set_image_to_clipboard(self, image):
@ -45,7 +60,15 @@ class ClipboardImageManager:
win32clipboard.EmptyClipboard() win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
win32clipboard.CloseClipboard() win32clipboard.CloseClipboard()
self.logger.debug(f"클립보드 데이터 저장 성공")
# 클립보드가 제대로 설정되었는지 확인하는 로그
time.sleep(0.1) # 아주 짧은 대기 시간
win32clipboard.OpenClipboard()
if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_DIB):
self.logger.debug("클립보드 데이터 저장 성공")
else:
self.logger.error("클립보드 데이터 저장 실패")
win32clipboard.CloseClipboard()
def save_image_to_path(self, image, path): def save_image_to_path(self, image, path):
try: try:
@ -110,7 +133,7 @@ class ClipboardImageManager:
# self.logger.debug(f"{crop_percentage*100}% 크롭된 이미지가 클립보드에 저장되었습니다.") # self.logger.debug(f"{crop_percentage*100}% 크롭된 이미지가 클립보드에 저장되었습니다.")
async def base64_to_image(self, base64_data): def base64_to_image(self, base64_data):
"""Base64 데이터를 이미지로 변환하는 함수""" """Base64 데이터를 이미지로 변환하는 함수"""
if base64_data.startswith('data:image'): if base64_data.startswith('data:image'):
header, encoded = base64_data.split(',', 1) header, encoded = base64_data.split(',', 1)
@ -167,69 +190,79 @@ class ClipboardImageManager:
async def process_clipboard(self, original_url, path=None): async def process_clipboard(self, original_url, path=None):
"""클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기""" """클립보드의 내용을 처리하고, 필요한 경우 이미지 변환, 크롭 또는 클립보드 비우기"""
clipboard_data = await self.get_clipboard_data()
# 1. 클립보드의 데이터가 Base64 이미지일 경우 try:
if clipboard_data.startswith('data:image'): clipboard_data = self.get_clipboard_data()
self.logger.info("data:image 감지 : 이미지 데이터로 변환")
image = await self.base64_to_image(clipboard_data)
if image:
width, _ = image.size
self.logger.debug(f"Base64 이미지 크기: {width}px")
# 가로 크기가 200픽셀 이상이면 크롭
if width >= 200:
self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
cropped_image = self.crop_image(image) # 크롭 메서드 사용
await self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
self.logger.debug("clipboard_data")
self.logger.debug(f"{clipboard_data}")
self.logger.debug(f"============================")
# 1. 클립보드의 데이터가 Base64 이미지일 경우
if isinstance(clipboard_data, str) and clipboard_data.startswith('data:image'):
self.logger.info("[process_clipboard] data:image 감지 : 이미지 데이터로 변환")
image = self.base64_to_image(clipboard_data)
if image:
width, _ = image.size
self.logger.debug(f"Base64 이미지 크기: {width}px")
# 가로 크기가 200픽셀 이상이면 크롭
if width >= 200:
self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
cropped_image = self.crop_image(image) # 크롭 메서드 사용
self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
else:
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
self.clear_clipboard()
else: else:
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.") self.logger.debug("Base64 이미지 변환 실패.")
await self.clear_clipboard()
else:
self.logger.debug("Base64 이미지 변환 실패.")
# 2. 클립보드에 이미지가 있을 경우 # 2. 클립보드에 이미지가 있을 경우
elif self.is_clipboard_image(): elif isinstance(clipboard_data, Image.Image):
self.logger.info("클립보드 이미지 확인") self.logger.info("[process_clipboard] 클립보드 이미지 확인")
image = self.get_image_from_clipboard()
if image: image = clipboard_data
width, _ = image.size width, _ = image.size
self.logger.debug(f"클립보드에 있는 이미지 크기: {width}px") self.logger.debug(f"클립보드에 있는 이미지 크기: {width}px")
if width >= 200: if width >= 200:
self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...") self.logger.debug("이미지 가로 크기 200픽셀 이상: 크롭 진행 중...")
cropped_image = self.crop_image(image) # 크롭 메서드 사용 cropped_image = self.crop_image(image) # 크롭 메서드 사용
await self.set_image_to_clipboard(cropped_image) # 클립보드에 저장 self.set_image_to_clipboard(cropped_image) # 클립보드에 저장
if path: if path:
self.logger.debug("이미지 저장 시도...") self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path) self.save_image_to_path(path)
else: else:
self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.") self.logger.debug("이미지 가로 크기 200픽셀 이하: 클립보드 비움.")
await self.clear_clipboard() self.clear_clipboard()
# 3. html > whale-ocr 처리 # 3. 클립보드에 데이터가 없거나 html > whale-ocr 처리
elif clipboard_data.strip() == "html > whale-ocr": elif clipboard_data == "html > whale-ocr" or clipboard_data is None:
self.logger.info("html > whale-ocr 감지 : 이미지 번역 실패 확인") if clipboard_data == "html > whale-ocr":
if original_url: self.logger.info("[process_clipboard] html > whale-ocr 감지 : 이미지 번역 실패 확인")
image = await self.download_image_from_url(original_url) elif clipboard_data is None:
if image: self.logger.info("[process_clipboard] 클립보드에 이미지 없음")
self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 저장 중...")
await self.set_image_to_clipboard(image) # 크롭 없이 저장 if original_url:
if path: image = await self.download_image_from_url(original_url)
self.logger.debug("이미지 저장 시도...") if image:
self.save_image_to_path(path) self.logger.debug("원본 이미지 다운로드 성공, 클립보드에 저장 중...")
self.set_image_to_clipboard(image) # 크롭 없이 저장
if path:
self.logger.debug("이미지 저장 시도...")
self.save_image_to_path(path)
else:
self.logger.debug("원본 이미지 다운로드 실패.")
else: else:
self.logger.debug("원본 이미지 다운로드 실패.") self.logger.debug("원본 이미지 URL을 찾을 수 없습니다.")
else:
self.logger.debug("원본 이미지 URL을 찾을 수 없습니다.") except Exception as e:
self.logger.error(f"클립보드에서 이미지를 처리하는 중 오류 발생: {e}", exc_info=True)
else:
self.logger.debug("클립보드에 처리할 수 있는 데이터가 없습니다.")
def is_clipboard_image(self): def is_clipboard_image(self):
"""클립보드에 이미지가 있는지 확인하는 함수""" """클립보드에 이미지가 있는지 확인하는 함수"""
@ -252,7 +285,7 @@ class ClipboardImageManager:
return None return None
async def clear_clipboard(self): def clear_clipboard(self):
"""클립보드를 비우는 함수""" """클립보드를 비우는 함수"""
try: try:
win32clipboard.OpenClipboard() win32clipboard.OpenClipboard()

View File

@ -8,6 +8,7 @@ option_count_text_locator = 'div#productMainContentContainerId th:nth-child(2) >
product_cost_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[3]/div/div/div/div[2]/input' product_cost_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[3]/div/div/div/div[2]/input'
standard_selling_price_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[4]/div/div/div[1]/div/div[2]/input' standard_selling_price_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{index}]/td[4]/div/div/div[1]/div/div[2]/input'
[OptionLocators] [OptionLocators]
# 옵션 관련 선택자 # 옵션 관련 선택자
option_excluded_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{index}]/div/div[1]/div/div[2]/div/div[3]' option_excluded_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{index}]/div/div[1]/div/div[2]/div/div[3]'
@ -15,7 +16,9 @@ option_input_selector_template = '//*[@id="productMainContentContainerId"]/div[1
single_option_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]' single_option_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]'
option_product_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]' option_product_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]'
total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper' total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
is_all_option_checked_selector = '#productMainContentContainerId .ant-checkbox-indeterminate' ; is_all_option_checked_selector = '#productMainContentContainerId .ant-checkbox-indeterminate'
is_all_option_checked_selector = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]/input'
ai_option_btn_selector = 'div#productMainContentContainerId div:nth-child(2) > div > div > div.ant-row.ant-row-middle.css-1li46mu > div:nth-child(4) > button[type=\"button\"]'
original_name_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span' original_name_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span'
edit_field_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input' edit_field_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input'
checkbox_selector_template = '#productMainContentContainerId li:nth-child({index}) input[type="checkbox"]' checkbox_selector_template = '#productMainContentContainerId li:nth-child({index}) input[type="checkbox"]'
@ -34,12 +37,10 @@ product_image_locator = '//*[@id='detailMainContainerId']/div/div/div[{i}]/img'
[DetailPageTextTemplates] [DetailPageTextTemplates]
leading_text_1 = '---' leading_text_1 = '---'
leading_text_2 = '# > 안녕하세요 혜리수샵입니다.' leading_text_2 = '# 안녕하세요 혜리수샵입니다.'
leading_text_3 = ' ' leading_text_3 = '### 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다.'
leading_text_4 = ' ' leading_text_4 = '### 반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.'
leading_text_5 = '### 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다.' leading_text_5 = '---'
leading_text_6 = '**반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.**'
leading_text_7 = '---'
# 필요한 만큼 추가 가능 # 필요한 만큼 추가 가능
[TitleLocators] [TitleLocators]

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

15
gui.py
View File

@ -22,7 +22,7 @@ class TranslationApp(QWidget):
self.initUI() self.initUI()
self.logger = logger self.logger = logger
self.debug = False self.debug = False
key_path = 'leensoo1nt.json' # key_path = 'leensoo1nt.json'
self.settings = QSettings("WhenRideMycar", "TranslationApp") # QSettings 초기화 self.settings = QSettings("WhenRideMycar", "TranslationApp") # QSettings 초기화
self.locator_manager = LocatorManager() self.locator_manager = LocatorManager()
self.browser_controller = BrowserController(self, self.logger, self.locator_manager) self.browser_controller = BrowserController(self, self.logger, self.locator_manager)
@ -31,7 +31,7 @@ class TranslationApp(QWidget):
self.whale_translator = None self.whale_translator = None
self.app = app self.app = app
self.vertexAI = VertexAITranslator(self.logger, key_path) self.vertexAI = VertexAITranslator(self.logger)
self.optionHandler = None self.optionHandler = None
# DB 파일 경로 설정 # DB 파일 경로 설정
@ -542,11 +542,12 @@ class TranslationApp(QWidget):
self.logger.debug('크롬 실행 버튼 클릭됨') self.logger.debug('크롬 실행 버튼 클릭됨')
self.logger.debug(f'self.browser_controller.page : {self.browser_controller.page}') self.logger.debug(f'self.browser_controller.page : {self.browser_controller.page}')
# 비동기 함수 실행을 위해 asyncio.create_task 사용 # 비동기 함수 실행을 위해 asyncio.create_task 사용
optionIMGTrans_status = self.toggle_states['vd_mode'] optionIMGTrans_status = self.toggle_states['optionIMGTrans']
detail_IMGTrans_status = self.toggle_states['vd_mode'] detail_IMGTrans_status = self.toggle_states['detail_IMGTrans']
vd_mode_status = self.toggle_states['vd_mode'] vd_mode_status = self.toggle_states['vd_mode']
if optionIMGTrans_status or detail_IMGTrans_status: if optionIMGTrans_status or detail_IMGTrans_status:
self.logger.debug(f"optionIMGTrans_status : {optionIMGTrans_status}, detail_IMGTrans_status : {detail_IMGTrans_status}")
self.whale_translator = WhaleTranslator(self.app, self.logger, secret_mode=True, vd_mode=vd_mode_status) # 모드 켜기 self.whale_translator = WhaleTranslator(self.app, self.logger, secret_mode=True, vd_mode=vd_mode_status) # 모드 켜기
self.whale_translator.start_whale_browser() self.whale_translator.start_whale_browser()
@ -779,7 +780,8 @@ class TranslationApp(QWidget):
self.logger.debug('프로그램을 종료합니다...') self.logger.debug('프로그램을 종료합니다...')
self.save_settings() self.save_settings()
await self.browser_controller.close_browser() # 브라우저 종료 await self.browser_controller.close_browser() # 브라우저 종료
self.whale_translator.close_all_virtual_desktops() if self.toggle_states['vd_mode']:
self.whale_translator.close_all_virtual_desktops()
super().close() super().close()
async def detail_trans(self): async def detail_trans(self):
@ -807,8 +809,11 @@ class TranslationApp(QWidget):
self.logger.debug('번역 작업이 중단되었습니다.') self.logger.debug('번역 작업이 중단되었습니다.')
break break
self.logger.debug(f"이미지 번역 프로세스")
self.whale_translator.translate_image(url) self.whale_translator.translate_image(url)
self.logger.debug(f"이미지 붙여넣기")
await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url) await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
self.logger.debug(f"Progress Update")
self.update_detail_progress(i,total_images) self.update_detail_progress(i,total_images)
current_image_count += 1 current_image_count += 1

View File

@ -46,6 +46,7 @@ class LocatorManager:
'next_page_button_template': self.config.get('BrowserControl', 'next_page_button_template').strip("'"), 'next_page_button_template': self.config.get('BrowserControl', 'next_page_button_template').strip("'"),
'source_button_locator': self.config.get('BrowserControl', 'source_button_locator').strip("'"), 'source_button_locator': self.config.get('BrowserControl', 'source_button_locator').strip("'"),
'ck_source_editing_area_locator': self.config.get('BrowserControl', 'ck_source_editing_area_locator').strip("'"), 'ck_source_editing_area_locator': self.config.get('BrowserControl', 'ck_source_editing_area_locator').strip("'"),
'option_input_field_locator': self.config.get('BrowserControl', 'option_input_field_locator').strip("'"),
'title_tab_locator': self.config.get('BrowserControl', 'title_tab_locator').strip("'"), 'title_tab_locator': self.config.get('BrowserControl', 'title_tab_locator').strip("'"),
'option_tab_locator': self.config.get('BrowserControl', 'option_tab_locator').strip("'"), 'option_tab_locator': self.config.get('BrowserControl', 'option_tab_locator').strip("'"),
'price_tab_locator': self.config.get('BrowserControl', 'price_tab_locator').strip("'"), 'price_tab_locator': self.config.get('BrowserControl', 'price_tab_locator').strip("'"),
@ -56,6 +57,12 @@ class LocatorManager:
'save_button_locator': self.config.get('BrowserControl', 'save_button_locator').strip("'"), 'save_button_locator': self.config.get('BrowserControl', 'save_button_locator').strip("'"),
} }
# DetailPageTextTemplates 섹션
self.selectors['DetailPageTextTemplates'] = {
key: value.strip("'") for key, value in self.config.items('DetailPageTextTemplates')
}
# OptionLocators 섹션 # OptionLocators 섹션
self.selectors['OptionLocators'] = { self.selectors['OptionLocators'] = {
'option_excluded_selector_template': self.config.get('OptionLocators', 'option_excluded_selector_template').strip("'"), 'option_excluded_selector_template': self.config.get('OptionLocators', 'option_excluded_selector_template').strip("'"),
@ -64,6 +71,7 @@ class LocatorManager:
'option_product_locator': self.config.get('OptionLocators', 'option_product_locator').strip("'"), 'option_product_locator': self.config.get('OptionLocators', 'option_product_locator').strip("'"),
'total_options_selector': self.config.get('OptionLocators', 'total_options_selector').strip("'"), 'total_options_selector': self.config.get('OptionLocators', 'total_options_selector').strip("'"),
'is_all_option_checked_selector': self.config.get('OptionLocators', 'is_all_option_checked_selector').strip("'"), 'is_all_option_checked_selector': self.config.get('OptionLocators', 'is_all_option_checked_selector').strip("'"),
'ai_option_btn_selector': self.config.get('OptionLocators', 'ai_option_btn_selector').strip("'"),
'original_name_selector_template': self.config.get('OptionLocators', 'original_name_selector_template').strip("'"), 'original_name_selector_template': self.config.get('OptionLocators', 'original_name_selector_template').strip("'"),
'edit_field_selector_template': self.config.get('OptionLocators', 'edit_field_selector_template').strip("'"), 'edit_field_selector_template': self.config.get('OptionLocators', 'edit_field_selector_template').strip("'"),
'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template').strip("'"), 'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template').strip("'"),

View File

@ -1,6 +1,7 @@
import logging import logging
import os import os
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
# from PySide6.QtCore import Signal, QObject
from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtCore import pyqtSignal, QObject
def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, backup_count=5): def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, backup_count=5):
@ -24,15 +25,15 @@ def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, ba
return logger return logger
class QTextEditLogger(logging.Handler, QObject): class QTextEditLogger(QObject, logging.Handler):
appendHtml = pyqtSignal(str) # HTML 메시지를 전달할 시그널 정의 appendHtml = pyqtSignal(str) # HTML 메시지를 전달할 시그널 정의
scrollToBottom = pyqtSignal() # 스크롤을 최하단으로 이동시키는 시그널 scrollToBottom = pyqtSignal() # 스크롤을 최하단으로 이동시키는 시그널
def __init__(self): def __init__(self):
logging.Handler.__init__(self)
QObject.__init__(self) QObject.__init__(self)
logging.Handler.__init__(self)
def emit(self, record): def log_message(self, record):
msg = self.format(record) # 로그 레코드를 문자열로 포매팅 msg = self.format(record) # 로그 레코드를 문자열로 포매팅
color = { color = {
@ -47,10 +48,14 @@ class QTextEditLogger(logging.Handler, QObject):
message = f"<span style=\"color:{color};\">{msg}</span><br/>" message = f"<span style=\"color:{color};\">{msg}</span><br/>"
self.appendHtml.emit(message) # HTML 메시지로 변경 self.appendHtml.emit(message) # HTML 메시지로 변경
self.scrollToBottom.emit() # 스크롤 시그널 발생 self.scrollToBottom.emit() # 스크롤 시그널 발생
# emit 대신 log_message를 호출하도록 수정
def emit(self, record):
self.log_message(record)
def close(self): def close(self):
self.flush() self.flush()
logging.Handler.close(self) logging.Handler.close(self)
def flush(self): def flush(self):
pass # 필요 시 정리 작업 수행 pass

44
main.py
View File

@ -21,9 +21,13 @@ def allow_sleep():
async def process_qt_events(app, stop_event): async def process_qt_events(app, stop_event):
"""PySide6의 이벤트를 처리하는 비동기 함수""" """PySide6의 이벤트를 처리하는 비동기 함수"""
while not stop_event.done(): try:
app.processEvents() while not stop_event.done():
await asyncio.sleep(0.01) # 10ms마다 Qt 이벤트 처리 app.processEvents()
await asyncio.sleep(0.01) # 10ms마다 Qt 이벤트 처리
except asyncio.CancelledError:
# 취소 시 안전하게 종료
pass
async def main(): async def main():
# 로깅 설정 # 로깅 설정
@ -38,38 +42,38 @@ async def main():
stop_event = asyncio.Future() # 종료 이벤트 생성 stop_event = asyncio.Future() # 종료 이벤트 생성
try: try:
# PySide6 앱 실행
app = QApplication([]) app = QApplication([])
# DPI 설정
try: try:
# DPI 인식 설정을 위한 환경 변수
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
os.environ["QT_SCALE_FACTOR"] = "1" os.environ["QT_SCALE_FACTOR"] = "1"
ctypes.windll.shcore.SetProcessDpiAwareness(1)
# DPI 인식 설정
ctypes.windll.shcore.SetProcessDpiAwareness(1) # 시스템 DPI 인식 대신 개별 모니터 인식으로 변경
except Exception as e: except Exception as e:
logger.error(f"DPI 인식 설정 실패: {e}") logger.error(f"DPI 인식 설정 실패: {e}")
# 기존 TranslationApp UI 사용 window = TranslationApp(logger, app)
# whale_translator = WhaleTranslator(app, logger, secret_mode=True, vd_mode=True) # 모드 켜기
# await whale_translator.start_whale_browser()
window = TranslationApp(logger, app) # PySide6 UI
window.show() window.show()
# asyncio와 PySide6 이벤트 루프를 통합 # QApplication.exec_()을 사용하여 Qt 이벤트 루프 시작
await asyncio.gather( await asyncio.gather(
process_qt_events(app, stop_event), # PySide6 이벤트 처리, stop_event 추가 process_qt_events(app, stop_event),
window.run_async_tasks(), # 비동기 작업 window.run_async_tasks(),
return_exceptions=True
) )
# 이 부분은 exec_()를 호출했기 때문에 도달하지 않습니다.
# app.exec_()
except Exception as e:
logger.exception(f"Main function error: {e}")
finally: finally:
# 앱 종료 시 절전모드 방지 해제
allow_sleep() allow_sleep()
stop_event.set_result(True) # 종료 이벤트 설정 (process_qt_events 종료) if not stop_event.done():
if window: # window가 생성되었을 경우에만 close() 호출 stop_event.set_result(True)
await window.close() # window.close()를 finally 블록으로 이동 if window:
await window.close() # await 추가
if app:
app.quit() #QApplication을 명시적으로 종료
if __name__ == '__main__': if __name__ == '__main__':
asyncio.run(main()) # 비동기 함수는 asyncio.run()으로 실행 asyncio.run(main()) # 비동기 함수는 asyncio.run()으로 실행

View File

@ -22,6 +22,7 @@ class OptionHandler:
self.option_product_locator = self.locator_manager.get_locator('OptionLocators', 'option_product_locator') self.option_product_locator = self.locator_manager.get_locator('OptionLocators', 'option_product_locator')
self.total_options_selector = self.locator_manager.get_locator('OptionLocators', 'total_options_selector') self.total_options_selector = self.locator_manager.get_locator('OptionLocators', 'total_options_selector')
self.is_all_option_checked_selector = self.locator_manager.get_locator('OptionLocators', 'is_all_option_checked_selector') self.is_all_option_checked_selector = self.locator_manager.get_locator('OptionLocators', 'is_all_option_checked_selector')
self.ai_option_btn_selector = self.locator_manager.get_locator('OptionLocators', 'ai_option_btn_selector')
self.original_name_selector_template = self.locator_manager.get_locator('OptionLocators', 'original_name_selector_template') self.original_name_selector_template = self.locator_manager.get_locator('OptionLocators', 'original_name_selector_template')
self.edit_field_selector_template = self.locator_manager.get_locator('OptionLocators', 'edit_field_selector_template') self.edit_field_selector_template = self.locator_manager.get_locator('OptionLocators', 'edit_field_selector_template')
self.checkbox_selector_template = self.locator_manager.get_locator('OptionLocators', 'checkbox_selector_template') self.checkbox_selector_template = self.locator_manager.get_locator('OptionLocators', 'checkbox_selector_template')
@ -131,7 +132,8 @@ class OptionHandler:
if option_input_element: if option_input_element:
option_name_value = (await option_input_element.get_attribute('value')).strip() option_name_value = (await option_input_element.get_attribute('value')).strip()
selected_translated_options.append( selected_translated_options.append(
(option_name_value, self.option_info['prices'].get(option_name_value, {}).get('low_price', 0)) # (option_name_value, self.option_info['prices'].get(option_name_value, {}).get('low_price', 0))
(option_name_value)
) )
self.option_info['selected_translated_options'] = selected_translated_options self.option_info['selected_translated_options'] = selected_translated_options
@ -180,32 +182,56 @@ class OptionHandler:
await self.low_order_click() await self.low_order_click()
# 4. 옵션 정보 수집 및 번역 # 4. 옵션 정보 수집 및 번역
if toggle_states['optionTrnas']: try:
self.logger.debug(f"옵션 AI번역 : {toggle_states['optionTrnas']}") if toggle_states['optionTrnas']:
self.option_info = await self.collect_options_info() self.logger.debug(f"옵션 AI번역 : {toggle_states['optionTrnas']}")
self.option_info = await self.collect_options_info()
translation_success = False # 성공/실패 플래그 translation_success = False # 성공/실패 플래그
try: try:
# Vertex AI를 통한 번역 시도 # Vertex AI를 통한 번역 시도
translated_options = await self.vertexAItranslator.translate_options(self.option_info['original_names'], product_name) translated_options = await self.vertexAItranslator.translate_options(self.option_info['original_names'], product_name)
self.logger.debug(f"번역된 옵션 입력") self.logger.debug(f"번역된 옵션 입력")
await self.apply_translated_options(translated_options, self.option_info['edit_fields']) await self.apply_translated_options(translated_options, self.option_info['edit_fields'])
translation_success = True # 번역 성공 translation_success = True # 번역 성공
except Exception as ve: except ValueError as ve:
if "SAFETY" in str(ve): # 안전 필터 예외 처리
self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}") if "SAFETY" in str(ve):
self.logger.debug(f"퍼센티 자체 AI번역 사용 시도") self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}")
pyautogui.hotkey('alt', 'q') self.logger.debug("퍼센티 자체 AI번역 사용 시도")
self.logger.debug(f"번역을 위한 5초간 대기") await self.page.click(self.ai_option_btn_selector)
# await asyncio.sleep(5) self.logger.debug("번역을 위한 5초간 대기")
time.sleep(5) await asyncio.sleep(5)
translation_success = False # 번역 실패
# except google.api_core.exceptions.ResourceExhausted as re:
# # 할당량 초과 예외 처리
# self.logger.error(f"Vertex AI 할당량 초과: {re}")
# self.logger.debug("퍼센티 자체 AI번역 사용 시도")
# pyautogui.hotkey('alt', 'q')
# self.logger.debug("번역을 위한 5초간 대기")
# time.sleep(5)
# translation_success = False # 번역 실패
except Exception as e:
# 기타 예외 처리
self.logger.error(f"번역 처리 중 알 수 없는 오류 발생: {e}", exc_info=True)
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
translation_success = False # 번역 실패 translation_success = False # 번역 실패
self.logger.debug(f"[{'VertexAI' if translation_success else '퍼센티AI'}] 를 이용한 옵션번역 성공") # 번역 성공 여부에 따른 로그
self.logger.debug(f"[{'VertexAI' if translation_success else '퍼센티AI'}] 를 이용한 옵션번역 성공")
except Exception as e:
# 옵션 처리 중 오류 발생 시 전체 로그 출력
self.logger.error(f"옵션 처리 중 오류 발생: {e}", exc_info=True)
# 5. 옵션 필터링 및 조정 # 5. 옵션 필터링 및 조정
if toggle_states['optionAutoSelect']: if toggle_states['optionAutoSelect']:
self.logger.debug(f"옵션 필터링 및 조정 : {toggle_states['optionAutoSelect']}") self.logger.debug(f"옵션 필터링 및 조정 : {toggle_states['optionAutoSelect']}")

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

53
setup.py Normal file
View File

@ -0,0 +1,53 @@
# setup.py
import sys
from cx_Freeze import setup, Executable
# 필요한 파일 정의
include_files = [
'config.ini',
'leensoo1nt.json',
'prompt.json',
'userDB.db',
('src/initialDB.db', 'src/initialDB.db'),
('src/Percenty_SS_Code.json', 'src/Percenty_SS_Code.json')
]
# 사용된 패키지 정의
build_exe_options = {
'packages': [
'ctypes', 'asyncio', 'os', 're', 'time', 'math', 'json', 'logging', 'shutil', 'random', 'base64',
'subprocess', 'configparser', 'pyperclip', 'numpy', 'cv2', 'requests', 'win32clipboard', 'win32gui',
'win32con', 'win32process', 'PIL', 'bs4', 'pyautogui', 'pyvda', 'sqlalchemy', 'sqlalchemy.orm',
'sqlalchemy.exc', 'collections', 'pandas'
],
'includes': [
'PySide6.QtWidgets', 'PySide6.QtCore', 'PySide6.QtGui',
'whale_translator', 'gui', 'logger_module', 'toggleSwitch',
'browser_control', 'clipboardImageManager', 'vertexAI', 'option',
'price', 'title', 'locatorManager', 'src.cmb_diag', 'src.DatabaseManager',
'vertexai.generative_models'
],
'include_files': include_files,
'excludes': [
'tkinter', 'PyQt4', 'PyQt6', 'AppKit', 'Foundation', 'IPython', 'OpenSSL', 'asyncpg',
'curses', 'pydantic', 'ssl', 'test', 'unittest', 'matplotlib', 'tensorflow', 'torch', 'scipy'
]
}
# 애플리케이션 메인 파일 및 설정
base = None
if sys.platform == 'win32':
base = 'Win32GUI'
executables = [
Executable('main.py', base=base, target_name='AutoPercenty2.exe')
]
# Setup 설정
setup(
name='AutoPercenty2',
version='1.1',
description='자동화도구',
options={'build_exe': build_exe_options},
executables=executables
)

View File

@ -1,18 +1,18 @@
import os import os
import json import json
import asyncio import sys
from vertexai.generative_models import GenerativeModel from vertexai.generative_models import GenerativeModel
class VertexAITranslator: class VertexAITranslator:
def __init__(self, logger, key_path): def __init__(self, logger):
""" """
VertexAITranslator 클래스 초기화 메서드. VertexAITranslator 클래스 초기화 메서드.
:param logger: 로깅을 위한 로거 객체. :param logger: 로깅을 위한 로거 객체.
:param key_path: Google Application Credentials의 파일 경로.
""" """
self.logger = logger self.logger = logger
key_path = self.get_key_path('leensoo1nt.json')
# GOOGLE_APPLICATION_CREDENTIALS 환경 변수 설정 # GOOGLE_APPLICATION_CREDENTIALS 환경 변수 설정
self.logger.debug(f"GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정: {key_path}") self.logger.debug(f"GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정: {key_path}")
@ -36,12 +36,20 @@ class VertexAITranslator:
:return: 파싱된 JSON 데이터. :return: 파싱된 JSON 데이터.
""" """
try: try:
prompt_path = os.path.join(os.path.dirname(__file__), 'prompt.json') # cx_Freeze로 패키징된 경우 실행 파일의 경로로 설정
if getattr(sys, 'frozen', False):
prompt_path = os.path.join(os.path.dirname(sys.executable), 'prompt.json')
else:
# 일반 Python 실행 환경일 경우
prompt_path = os.path.join(os.path.dirname(__file__), 'prompt.json')
self.logger.debug(f"프롬프트 파일 경로: {prompt_path}") self.logger.debug(f"프롬프트 파일 경로: {prompt_path}")
with open(prompt_path, 'r', encoding='utf-8') as file: with open(prompt_path, 'r', encoding='utf-8') as file:
prompt_data = json.load(file) prompt_data = json.load(file)
self.logger.debug("prompt.json 파일이 성공적으로 로드되었습니다.") self.logger.debug("prompt.json 파일이 성공적으로 로드되었습니다.")
return prompt_data return prompt_data
except FileNotFoundError as e: except FileNotFoundError as e:
self.logger.error(f"prompt.json 파일을 찾을 수 없습니다: {e}", exc_info=True) self.logger.error(f"prompt.json 파일을 찾을 수 없습니다: {e}", exc_info=True)
raise e raise e
@ -49,6 +57,14 @@ class VertexAITranslator:
self.logger.error(f"prompt.json 파일 파싱 중 오류 발생: {e}", exc_info=True) self.logger.error(f"prompt.json 파일 파싱 중 오류 발생: {e}", exc_info=True)
raise e raise e
def get_key_path(self, key_path):
# cx_Freeze로 패키징된 경우 실행 파일 경로로 설정
if getattr(sys, 'frozen', False):
return os.path.join(os.path.dirname(sys.executable), key_path)
else:
# 일반 Python 실행 환경일 경우
return os.path.join(os.path.dirname(__file__), key_path)
def clean_special_chars(self, text): def clean_special_chars(self, text):
""" """
텍스트에서 허용되지 않는 특수 문자를 제거하고, 텍스트에서 허용되지 않는 특수 문자를 제거하고,

View File

@ -7,6 +7,7 @@ import subprocess
import asyncio import asyncio
import KO_EN import KO_EN
import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러리 import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러리
from PIL import ImageGrab
class WhaleTranslator: class WhaleTranslator:
def __init__(self, app, logger, secret_mode=True, vd_mode=False, max_failures=5): def __init__(self, app, logger, secret_mode=True, vd_mode=False, max_failures=5):
@ -65,8 +66,8 @@ class WhaleTranslator:
# 주소창으로 이동 후 URL 입력 # 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l') pyautogui.hotkey('ctrl', 'l')
time.sleep(0.4) time.sleep(0.4)
# pyautogui.typewrite('https://daum.net') pyautogui.typewrite('https://daum.net')
# self.change_lang() self.change_lang()
self.enter_url("about:newtab", change=True) self.enter_url("about:newtab", change=True)
self.logger.debug("URL 입력 완료") self.logger.debug("URL 입력 완료")
@ -91,7 +92,7 @@ class WhaleTranslator:
asyncio.run(self.start_whale_browser()) # 브라우저 재시작 asyncio.run(self.start_whale_browser()) # 브라우저 재시작
self.reset_failures() # 실패 횟수 초기화 self.reset_failures() # 실패 횟수 초기화
def is_image_in_clipboard(self): def is_image_in_clipboard_with_text(self):
"""클립보드에 이미지 데이터 또는 base64로 인코딩된 이미지 데이터가 있는지 확인""" """클립보드에 이미지 데이터 또는 base64로 인코딩된 이미지 데이터가 있는지 확인"""
clipboard_content = pyperclip.paste() clipboard_content = pyperclip.paste()
if clipboard_content.startswith("data:image") or isinstance(clipboard_content, bytes): if clipboard_content.startswith("data:image") or isinstance(clipboard_content, bytes):
@ -100,6 +101,21 @@ class WhaleTranslator:
else: else:
self.logger.debug("클립보드에 이미지 데이터가 없습니다.") self.logger.debug("클립보드에 이미지 데이터가 없습니다.")
return False return False
def is_image_in_clipboard(self):
"""클립보드에 이미지 데이터가 있는지 확인"""
try:
# 클립보드에서 이미지를 가져오고 None이 아니면 이미지가 있는 것으로 간주
image = ImageGrab.grabclipboard()
if isinstance(image, bytes) or image is not None:
self.logger.debug("클립보드에 이미지 데이터가 확인되었습니다.")
return True
else:
self.logger.debug("클립보드에 이미지 데이터가 없습니다.")
return False
except Exception as e:
self.logger.error(f"클립보드에서 이미지 확인 중 오류 발생: {e}", exc_info=True)
return False
def find_whale_window(self): def find_whale_window(self):
"""웨일 창 핸들을 찾는 메서드""" """웨일 창 핸들을 찾는 메서드"""
@ -223,19 +239,19 @@ class WhaleTranslator:
self.enter_url(url) self.enter_url(url)
pyautogui.press('enter') pyautogui.press('enter')
# await asyncio.sleep(1) # 페이지 로딩 대기 # await asyncio.sleep(1) # 페이지 로딩 대기
time.sleep(2) time.sleep(1)
pyautogui.rightClick() pyautogui.rightClick()
# await asyncio.sleep(0.2) # 페이지 로딩 대기 # await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(2) time.sleep(1)
pyautogui.press('r') # 번역 클릭 pyautogui.press('r') # 번역 클릭
# await asyncio.sleep(7) # 페이지 로딩 대기 # await asyncio.sleep(7) # 페이지 로딩 대기
time.sleep(7) time.sleep(5)
pyautogui.rightClick() pyautogui.rightClick()
# await asyncio.sleep(0.2) # 페이지 로딩 대기 # await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(0.2) time.sleep(1)
pyautogui.press('c') # 번역된 이미지 클립보드에 복사 pyautogui.press('c') # 번역된 이미지 클립보드에 복사
@ -344,7 +360,7 @@ class WhaleTranslator:
self.logger.debug("전환 성공") self.logger.debug("전환 성공")
def enter_url_for_typing(self, url, change=False): def enter_url(self, url, change=False):
# 언어 전환이 완료되면 주소창으로 이동 후 URL 입력 # 언어 전환이 완료되면 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동 pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동
@ -353,7 +369,7 @@ class WhaleTranslator:
pyautogui.press('enter') # Enter 키 입력 pyautogui.press('enter') # Enter 키 입력
time.sleep(1) # 페이지 로딩 대기 time.sleep(1) # 페이지 로딩 대기
def enter_url(self, url, change=False): def enter_url_for_clipboard(self, url, change=False):
# URL을 클립보드에 복사 # URL을 클립보드에 복사
pyperclip.copy(url) pyperclip.copy(url)