This commit is contained in:
Envy_PC 2024-10-03 19:30:23 +09:00
commit 984c718300
15 changed files with 43492 additions and 627 deletions

1
.gitignore vendored
View File

@ -2,5 +2,6 @@ Include/
Lib/
Scripts/
__pycache__/
__pycache__/
pyvenv.cfg
*.log

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,7 @@ class BrowserController:
else:
self.logger.debug(f'크롬 창 핸들: {self.chrome_hwnd}')
await self.page.wait_for_load_state('networkidle')
# await self.page.wait_for_load_state('networkidle', timeout=10000)
async def login(self, admin_id, user_id, admin_password, user_password, is_admin=False):
"""로그인 처리"""
@ -78,7 +78,7 @@ class BrowserController:
self.logger.debug(f'로그인 완료: {"관리자" if is_admin else "직원"} 계정')
await self.page.wait_for_load_state('networkidle')
# await self.page.wait_for_load_state('networkidle', timeout=10000)
await self.close_ad_if_exists()
@ -159,59 +159,6 @@ class BrowserController:
self.logger.error(f"상품명 수집 중 오류: {e}")
return "수집 오류 발생"
async def extract_image_urls(self):
"""HTML에서 이미지 URL 추출 및 img 태그 삭제 후 소스 버튼 다시 클릭"""
self.logger.debug('이미지 URL을 추출 중...')
# 소스 버튼 클릭
await self.page.click("button[data-cke-tooltip-text='소스']")
self.logger.debug('소스 버튼 클릭 완료.')
# 'data-value' 속성 값을 추출 (textarea 요소)
textarea = await self.page.wait_for_selector('div.ck-source-editing-area')
data_value = await 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 태그를 삭제 중...')
await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
# data-value 속성을 가진 요소 선택
data_value_element = await 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)
await self.page.evaluate(f'() => document.querySelector("div.ck-source-editing-area").setAttribute("data-value", "{new_value}")')
# 데이터가 제대로 변경되었는지 확인
updated_value = await 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 태그 삭제 완료.')
# 소스 버튼 다시 클릭
await 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 콘텐츠에서 모든 <img> 태그의 URL을 순서대로 추출
@ -270,10 +217,11 @@ class BrowserController:
"""현재 페이지의 세부사항 수정 및 업로드 버튼을 찾기"""
try:
# 페이지 로딩을 기다림
await self.page.wait_for_load_state('networkidle') # 네트워크 요청이 모두 끝날 때까지 대기
# await self.page.wait_for_load_state('networkidle', timeout=10000) # 네트워크 요청이 끝난 후 대기
# 페이지 끝까지 스크롤하여 모든 동적 요소 로드
await self.scroll_page_to_bottom()
await self.scroll_page_to_top()
# # 스크롤하여 모든 버튼을 화면에 표시 (가장 하단까지 스크롤)
@ -321,7 +269,59 @@ class BrowserController:
except Exception as e:
self.logger.debug(f"옵션 탭 클릭 중 오류: {e}", exc_info=True)
async def extract_image_urls(self):
# async def extract_image_urls(self):
# """HTML에서 이미지 URL 추출 및 img 태그 삭제 후 소스 버튼 다시 클릭"""
# self.logger.debug('이미지 URL을 추출 중...')
# # 소스 버튼 클릭
# await self.page.click("button[data-cke-tooltip-text='소스']")
# self.logger.debug('소스 버튼 클릭 완료.')
# # 'data-value' 속성 값을 추출 (textarea 요소)
# textarea = await self.page.wait_for_selector('div.ck-source-editing-area')
# data_value = await 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 태그를 삭제 중...')
# await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
# # data-value 속성을 가진 요소 선택
# data_value_element = await 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)
# await self.page.evaluate(f'() => document.querySelector("div.ck-source-editing-area").setAttribute("data-value", "{new_value}")')
# # 데이터가 제대로 변경되었는지 확인
# updated_value = await 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 태그 삭제 완료.')
# # 소스 버튼 다시 클릭
# await self.page.click("button[data-cke-tooltip-text='소스']")
# self.logger.debug('소스 버튼 재 클릭 완료.')
# return image_urls
# else:
# self.logger.debug('data-value 속성에 데이터가 없습니다.')
# return []
async def extract_image_urls(self, optionHandler, is_option_data=False):
"""상세페이지에서 이미지 URL 추출"""
try:
# 소스 편집 모드로 전환
@ -338,7 +338,7 @@ class BrowserController:
# HTML 소스에서 이미지 URL 삭제
self.logger.debug('img 태그를 삭제 중...')
await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
# await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
# data-value 속성을 가진 요소 선택
data_value_element = await self.page.query_selector('div.ck-source-editing-area')
new_value = ""
@ -358,6 +358,51 @@ class BrowserController:
await self.page.click('button[data-cke-tooltip-text="소스"]')
self.logger.debug('소스 버튼 재 클릭 완료.')
if is_option_data:
self.logger.debug('옵션 데이터 입력 시작')
option_data = optionHandler.get_selected_translated_options()
# 옵션 입력 필드 선택
input_field_selector = '//*[@id="productMainContentContainerId"]/div/div/div[2]/div[2]/div[2]/div' # 여기에 적절한 입력 필드의 셀렉터를 입력
input_field = await self.page.wait_for_selector(input_field_selector)
# 선두부 텍스트 입력
await input_field.type('---')
await input_field.press('Enter')
pyautogui.typewrite('## > 안녕하세요 혜리수샵입니다.')
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.press('Enter')
# 각 옵션을 한 줄씩 입력
for i, option in enumerate(option_data, start=1):
if isinstance(option, tuple):
option_text = option[0] # 튜플의 첫 번째 요소를 사용
else:
option_text = option
# 옵션을 A. B. 등으로 표시하며 입력
option_prefix = f"{chr(64 + i)}. " # A, B, C...
await input_field.type(option_prefix + option_text)
await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
# 후두부 텍스트 입력
await input_field.type('---')
await input_field.press('Enter')
await input_field.type('나열된 옵션목록 이외의 옵션이 필요하실 경우 고객센터로 연락주세요.')
await input_field.press('Enter')
await input_field.type('---')
await input_field.press('Enter')
await input_field.press('Enter')
await input_field.press('Enter')
self.logger.debug('옵션 데이터 입력 완료 후 엔터 입력')
return image_urls
except Exception as e:
self.logger.debug(f"이미지 URL 추출 중 오류: {e}", exc_info=True)
@ -367,14 +412,14 @@ class BrowserController:
"""크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력"""
try:
self.switch_to_chrome() # 크롬으로 포커스 이동
clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리
await clipboardImageManager.process_clipboard(url) # 클립보드 내용을 처리
# clipboard_content = pyperclip.paste()
if clipboardImageManager.is_clipboard_image():
pyautogui.hotkey('ctrl', 'v') # 클립보드 이미지 붙여넣기
pyautogui.press('right') # 오른쪽 입력
self.logger.debug("이미지 붙여넣기 완료.")
self.logger.debug("이미지 붙여넣기 완료로 클립보드 비우기.")
clipboardImageManager.clear_clipboard()
await clipboardImageManager.clear_clipboard()
else:
self.logger.debug("클립보드가 비어있습니다.")
except Exception as e:
@ -416,7 +461,7 @@ class BrowserController:
if next_page_button:
await next_page_button.click() # 페이지 버튼 클릭
await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩이 완료될 때까지 대기
# await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩이 완료될 때까지 대기
self.logger.debug(f"페이지 {next_page_number}로 이동 완료.")
return True
else:
@ -456,32 +501,77 @@ class BrowserController:
- max_scrolls: 최대 스크롤 횟수.
"""
scroll_count = 0
self.logger.debug(f"스크롤 시작")
# 현재 페이지 높이 가져오기
last_height = await self.page.evaluate("document.body.scrollHeight")
self.logger.debug(f"현재 페이지 높이 가져오기 - {last_height}")
while scroll_count < max_scrolls:
if direction == "down":
# 아래로 스크롤
await self.page.mouse.wheel(0, 1000)
self.logger.debug(f"scroll_count[{scroll_count}]회 : 휠 아래로 1000px")
await self.page.evaluate("window.scrollBy(0, 1000);")
elif direction == "up":
# 위로 스크롤
await self.page.mouse.wheel(0, -1000)
self.logger.debug(f"scroll_count[{scroll_count}]회 : 휠 위로 1000px")
await self.page.evaluate("window.scrollBy(0, -1000);")
else:
raise ValueError("direction 인자는 'down' 또는 'up'만 허용됩니다.")
self.logger.debug(f"pause_time 슬립 : {pause_time}")
await asyncio.sleep(pause_time)
# 새로운 페이지 높이 가져오기
new_height = await self.page.evaluate("document.body.scrollHeight")
self.logger.debug(f"새로운 페이지 높이 가져오기 - {new_height}")
# 스크롤이 더 이상 필요 없는 경우(페이지 끝에 도달)
if direction == "down" and new_height == last_height:
self.logger.debug(f"페이지 끝에 도달했습니다. 스크롤 횟수: {scroll_count}")
break
elif direction == "up" and new_height == 0:
self.logger.debug(f"페이지 시작에 도달했습니다. 스크롤 횟수: {scroll_count}")
break
self.logger.debug(f"새로운 페이지 높이를 현재높이로 재설정 - {new_height}")
last_height = new_height
scroll_count += 1
self.logger.debug(f"스크롤 카운트 + 1")
if scroll_count == max_scrolls:
self.logger.debug("최대 스크롤 횟수에 도달했습니다.")
async def scroll_with_keyboard(self, direction="down", pause_time=0.5, max_scrolls=50):
"""
키보드를 사용하여 페이지를 위나 아래로 천천히 스크롤.
Parameters:
- direction: 스크롤 방향 ("down" 아래로, "up" 위로).
- pause_time: 스크롤 사이의 대기 시간 ().
- max_scrolls: 최대 스크롤 횟수.
"""
scroll_count = 0
while scroll_count < max_scrolls:
if direction == "down":
# 아래로 스크롤 (Page Down 키 사용)
await self.page.keyboard.press("PageDown")
elif direction == "up":
# 위로 스크롤 (Page Up 키 사용)
await self.page.keyboard.press("PageUp")
else:
raise ValueError("direction 인자는 'down' 또는 'up'만 허용됩니다.")
await asyncio.sleep(pause_time)
# 스크롤 후 높이 확인
new_height = await 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
if scroll_count == max_scrolls:
self.logger.debug("최대 스크롤 횟수에 도달했습니다.")
async def collect_product_info(self):
"""
상품 정보를 수집하는 메서드
@ -537,7 +627,7 @@ class BrowserController:
self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 중 오류: {str(e)}')
async def scroll_page_to_bottom(self, pause_time=1):
async def scroll_page_to_bottom(self, pause_time=0.2):
"""페이지의 맨 아래까지 스크롤하여 모든 동적 요소를 로드"""
self.logger.debug('페이지 스크롤 시작...')
previous_height = await self.page.evaluate("() => document.body.scrollHeight")
@ -551,4 +641,17 @@ class BrowserController:
previous_height = current_height
self.logger.debug('페이지 스크롤 완료.')
async def scroll_page_to_top(self, pause_time=0.2):
"""페이지의 맨 위까지 스크롤"""
self.logger.debug('페이지 위로 스크롤 시작...')
previous_height = await self.page.evaluate("() => window.pageYOffset")
while previous_height > 0:
await self.page.evaluate("window.scrollBy(0, -window.innerHeight);") # 한 화면씩 위로 스크롤
await asyncio.sleep(pause_time) # 페이지 로딩 대기
current_height = await self.page.evaluate("() => window.pageYOffset")
if current_height == previous_height:
break # 더 이상 스크롤할 내용이 없으면 종료
previous_height = current_height
self.logger.debug('페이지 위로 스크롤 완료.')

14
gui.py
View File

@ -105,7 +105,7 @@ class TranslationApp(QWidget):
def initUI(self):
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setGeometry(QRect(940, 500, 180, 400))
self.setGeometry(QRect(1240, 750, 280, 600))
self.setWindowTitle('이미지 번역 도구')
# 로그
@ -375,7 +375,7 @@ class TranslationApp(QWidget):
self.logger.debug(f'현재 페이지: {page_number}')
if not page_number == 1:
self.browser_controller.scroll_with_wheel("up")
self.browser_controller.scroll_page_to_top()
self.logger.debug(f'1페이지가 아니므로 동적로딩을 위해 휠 스크롤 업')
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
@ -532,21 +532,21 @@ class TranslationApp(QWidget):
self.logger.debug('프로그램을 종료합니다...')
self.save_settings()
await self.browser_controller.close_browser() # 브라우저 종료
await self.whale_translator.close_all_virtual_desktops()
self.whale_translator.close_all_virtual_desktops()
super().close()
async def detail_trans(self):
# 상세페이지 탭 클릭
await self.browser_controller.click_detail_tab()
await self.browser_controller.page.wait_for_load_state('domcontentloaded')
# await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000)
self.detail_progress_bar.setValue(0)
self.detail_progress_bar.setVisible(True)
# 이미지 URL 추출
# image_urls = self.browser_controller.extract_image_urls()
image_urls = await self.browser_controller.extract_image_urls() # 코루틴 실행
image_urls = await self.browser_controller.extract_image_urls(self.optionHandler, is_option_data=True) # 코루틴 실행
total_images = len(image_urls)
self.logger.debug(f"현재 상품의 총 이미지 수 : {total_images}")
@ -560,7 +560,7 @@ class TranslationApp(QWidget):
self.logger.debug('번역 작업이 중단되었습니다.')
break
await self.whale_translator.translate_image(url)
self.whale_translator.translate_image(url)
await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
self.update_detail_progress(i,total_images)
@ -576,7 +576,7 @@ class TranslationApp(QWidget):
async def edit_option(self, product_name):
# 상세페이지 탭 클릭
await self.browser_controller.click_option_tab()
await self.browser_controller.page.wait_for_load_state('domcontentloaded')
# await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000)
self.detail_progress_bar.setVisible(True)
# 옵션 최대선택갯수

View File

@ -48,7 +48,7 @@ async def main():
logger.error(f"DPI 인식 설정 실패: {e}")
# 기존 TranslationApp UI 사용
whale_translator = WhaleTranslator(app, logger, secret_mode=True,vd_mode=True) # 디버그 모드 켜기
whale_translator = WhaleTranslator(app, logger, secret_mode=True, vd_mode=True) # 디버그 모드 켜기
await whale_translator.start_whale_browser()
window = TranslationApp(logger, whale_translator) # PySide6 UI
window.show()

View File

@ -134,14 +134,18 @@ class OptionHandler:
"""
try:
self.logger.debug(f"상품명: {product_name}에 대한 옵션을 처리 중...")
self.logger.debug(f"휠스크롤 Down")
await self.browser_controller.scroll_with_wheel('down')
self.logger.debug(f"휠스크롤 Up")
await self.browser_controller.scroll_with_wheel('up')
await self.page.wait_for_load_state('domcontentloaded')
self.logger.debug(f"동적요소 로딩완료")
# self.logger.debug(f"포커스를 위해 클릭1회")
# self.low_order_click()
# self.logger.debug(f"휠스크롤 Down")
# await self.browser_controller.scroll_with_wheel('down')
# self.logger.debug(f"휠스크롤 Up")
# await self.browser_controller.scroll_with_wheel('up')
# await self.page.wait_for_load_state('domcontentloaded')
# self.logger.debug(f"동적요소 로딩완료")
await asyncio.sleep(2)
# 1. 단일 옵션인지 판단
if await self.is_single_option():
@ -210,6 +214,10 @@ class OptionHandler:
# # 8. 정리된 옵션을 다시한번 더 가격 낮은 순으로 정렬 클릭
# await self.low_order_click()
# 9. A-Z 버튼 클릭
self.logger.debug("A-Z 버튼을 클릭합니다.")
self.AtoZ_button_click()
# 9. 저장 버튼 클릭
self.logger.debug("저장 버튼을 클릭합니다.")
@ -554,16 +562,14 @@ class OptionHandler:
self.logger.error(f"옵션 필터링 및 조정 중 오류 발생: {e}", exc_info=True)
async def adjust_options(self, filtered_options, max_option_count):
async def adjust_options(self, filtered_option_names, max_option_count):
"""
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
:param filtered_options: 필터링된 옵션 리스트
:param max_option_count: 최대 선택 가능한 옵션
"""
try:
# 필터링된 옵션들만 체크박스 상태를 조정
filtered_option_names = {option['name'] for option in filtered_options}
# 옵션 체크 상태를 수집한 정보에서 필터링된 옵션들만 체크 상태로 유지
for i, name in enumerate(self.option_info['original_names'].values()):
checkbox = self.option_info['checkboxes'][i]
@ -581,27 +587,32 @@ class OptionHandler:
await checkbox.click()
self.option_info['checked_states'][name] = False # 체크 상태 업데이트
# 필터링된 옵션 중 max_option_count가 넘지 않도록 체크 상태 유지
if len(filtered_option_names) > max_option_count:
self.logger.debug(f"필터링된 옵션이 {max_option_count}개 이상이므로 초과된 옵션을 체크 해제합니다.")
filtered_options_sorted = sorted(filtered_option_names) # 정렬된 옵션 리스트
for i, name in enumerate(filtered_options_sorted[max_option_count:], start=max_option_count):
checkbox = self.option_info['checkboxes'][i]
if self.option_info['checked_states'][name]: # 이미 체크된 경우만 해제
self.logger.debug(f"{name} 옵션 체크 해제 (초과된 옵션)")
await checkbox.click()
self.option_info['checked_states'][name] = False # 체크 상태 업데이트
# # 필터링된 옵션 중 max_option_count가 넘지 않도록 체크 상태 유지
# if len(filtered_option_names) > max_option_count:
# self.logger.debug(f"필터링된 옵션이 {max_option_count}개 이상이므로 초과된 옵션을 체크 해제합니다.")
# filtered_options_sorted = sorted(filtered_option_names) # 정렬된 옵션 리스트
# for i, name in enumerate(filtered_options_sorted[max_option_count:], start=max_option_count):
# checkbox = self.option_info['checkboxes'][i]
# if self.option_info['checked_states'][name]: # 이미 체크된 경우만 해제
# self.logger.debug(f"{name} 옵션 체크 해제 (초과된 옵션)")
# await checkbox.click()
# self.option_info['checked_states'][name] = False # 체크 상태 업데이트
self.logger.debug(f"옵션 체크 상태 조정 완료.")
except Exception as e:
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
async def AtoZ_button_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click('button:has-text("A-Z")')
# atoz_button_xpath = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[4]/div[1]/div[1]/div/div[1]/button'
async def low_order_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click('button:has-text("가격 낮은 순")')
await self.page.wait_for_load_state('domcontentloaded')
# await self.page.wait_for_load_state('domcontentloaded')
async def save_option(self):
"""옵션 수정 후 저장 버튼 클릭"""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

View File

@ -31,29 +31,66 @@ class WhaleTranslator:
# Whale 브라우저 실행
whale_path = r"C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
process = subprocess.Popen([whale_path, '--incognito'])
time.sleep(2)
self.whale_pid = process.pid
self.logger.debug(f"Whale 브라우저 실행, PID: {self.whale_pid}")
# 창을 찾기 전 대기 (최대 10초 동안 반복 시도)
wait_time = 0
self.whale_hwnd = None
while self.whale_hwnd is None and wait_time < 10:
self.whale_hwnd = win32gui.FindWindow(None, self.whale_window_name)
if self.whale_hwnd:
self.logger.debug(f"Whale 창을 찾았습니다: {self.whale_hwnd}")
break
time.sleep(1)
wait_time += 1
self.logger.debug(f"Whale 창을 찾지 못했습니다. 재시도 중... ({wait_time}초 경과)")
# await self.enter_url(self.newtab, change=True)
await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기
hwnd = win32gui.FindWindow(None, self.whale_window_name)
if hwnd:
win32gui.ShowWindow(hwnd, win32con.SW_NORMAL)
win32gui.SetWindowPos(hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER)
else:
# 창을 찾지 못한 경우 처리
if not self.whale_hwnd:
self.logger.debug("Whale 창을 찾을 수 없습니다.")
return
# 창 크기 조절 및 포커스 이동
win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL)
win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER)
self.logger.debug("Whale 창 크기 조절 완료")
# 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l')
time.sleep(0.4)
# pyautogui.typewrite('https://daum.net')
self.change_lang()
self.enter_url("about:newtab", change=True)
self.logger.debug("URL 입력 완료")
# 가상 데스크탑 복귀 처리
if self.vd_mode:
self.return_to_virtual_desktop_1() # 가상 데스크탑 1로 복귀
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 find_whale_window_pid(self):
"""프로세스 ID를 기반으로 웨일 창 핸들을 찾는 메서드"""
if not self.whale_hwnd:
self.whale_hwnd = self.find_window_by_pid(self.whale_pid)
return self.whale_hwnd
def find_window_by_pid(self, pid):
"""PID와 창 제목을 통해 유일한 웨일 창을 찾는 메서드"""
def enum_windows_callback(hwnd, pid_list):
@ -134,7 +171,7 @@ class WhaleTranslator:
except Exception as e:
self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}", exc_info=True)
async def translate_image(self, url, path=None):
def translate_image(self, url, path=None):
if self.vd_mode:
self.switch_to_virtual_desktop_2()
@ -143,15 +180,14 @@ class WhaleTranslator:
if not self.whale_hwnd:
# 웨일 창을 찾지 못했을 경우 사용자에게 입력 받기
self.logger.debug("웨일 창을 찾지 못했습니다. 계속하려면 'y'를 입력하세요.")
user_input = input("계속하려면 'y'를 입력하세요: ").lower()
# user_input = input("계속하려면 'y'를 입력하세요: ").lower()
if user_input != 'y':
self.logger.debug("사용자에 의해 작업이 중단되었습니다.")
raise SystemExit("프로그램이 사용자 요청으로 종료되었습니다.") # 프로그램 종료
else:
self.logger.debug("새 웨일 창을 생성합니다.")
await self.create_and_update_whale_window()
# if user_input != 'y':
# self.logger.debug("사용자에 의해 작업이 중단되었습니다.")
# raise SystemExit("프로그램이 사용자 요청으로 종료되었습니다.") # 프로그램 종료
# else:
# self.logger.debug("새 웨일 창을 생성합니다.")
self.create_and_update_whale_window()
if self.whale_hwnd:
@ -161,20 +197,27 @@ class WhaleTranslator:
pyautogui.moveTo(960,580) # 마우스 센터로 이동
# pyautogui.hotkey('ctrl', 'l') # 웨일 브라우저의 주소창으로 이동
await self.enter_url(url)
await asyncio.sleep(1) # 페이지 로딩 대기
self.enter_url(url)
pyautogui.press('enter')
# await asyncio.sleep(1) # 페이지 로딩 대기
time.sleep(2)
pyautogui.rightClick()
await asyncio.sleep(0.2) # 페이지 로딩 대기
# await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(2)
pyautogui.press('r') # 번역 클릭
await asyncio.sleep(7) # 페이지 로딩 대기
# await asyncio.sleep(7) # 페이지 로딩 대기
time.sleep(7)
pyautogui.rightClick()
await asyncio.sleep(0.2) # 페이지 로딩 대기
# await asyncio.sleep(0.2) # 페이지 로딩 대기
time.sleep(0.2)
pyautogui.press('c') # 번역된 이미지 클립보드에 복사
# pyautogui.hotkey('ctrl', 'l') # 새 탭으로 이동
# pyautogui.typewrite(self.newtab) # URL을 입력
await self.enter_url(self.newtab)
self.enter_url(self.newtab)
self.logger.debug(f'번역 완료: {url}')
if self.vd_mode:
@ -187,7 +230,7 @@ class WhaleTranslator:
else:
self.logger.debug('웨일 창을 찾을 수 없습니다.')
async def create_and_update_whale_window(self):
def create_and_update_whale_window(self):
"""
웨일 창을 찾지 못했을 경우 웨일 창을 생성하고 핸들을 업데이트하는 메서드.
"""
@ -199,7 +242,8 @@ class WhaleTranslator:
self.whale_pid = process.pid
self.logger.debug(f"새 웨일 브라우저 실행, PID: {self.whale_pid}")
await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기
# await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기
time.sleep(2)
# 창 핸들 업데이트
self.whale_hwnd = self.find_whale_window()
@ -240,26 +284,31 @@ class WhaleTranslator:
# # 한글로 변경
# user32.LoadKeyboardLayoutW('00000412', 1)
async def enter_url(self, url, change=False):
if change:
# 언어 전환이 완료되었는지 확인하는 함수
async def wait_for_language_switch():
# 최대 3초까지 언어 전환 확인 시도 (0.1초 간격)
for _ in range(30):
if KO_EN.get_hanguel_state() == 0: # 영어로 전환된 상태가 0
return True
await asyncio.sleep(0.1)
return False
def change_lang(self):
# 언어 전환이 완료되었는지 확인하는 함수
def wait_for_language_switch():
# 최대 3초까지 언어 전환 확인 시도 (0.1초 간격)
for _ in range(30):
if KO_EN.get_hanguel_state() == 0: # 영어로 전환된 상태가 0
return True
time.sleep(0.1)
# await asyncio.sleep(0.1)
return False
# 주소창에 URL 입력하기 전에 영어로 변경
KO_EN.set_keyboard_language('eng')
# 주소창에 URL 입력하기 전에 영어로 변경
KO_EN.set_keyboard_language('eng')
# 언어 전환 완료 대기
is_switched = await wait_for_language_switch()
# 언어 전환 완료 대기
is_switched = wait_for_language_switch()
if not is_switched:
print("영어로 전환하는데 실패했습니다.")
return
if not is_switched:
self.logger.debug("영어로 전환하는데 실패했습니다.")
return
else:
self.logger.debug("전환 성공")
def enter_url(self, url, change=False):
# 언어 전환이 완료되면 주소창으로 이동 후 URL 입력
pyautogui.hotkey('ctrl', 'l') # 주소창으로 이동
@ -296,7 +345,7 @@ class WhaleTranslator:
except Exception as e:
self.logger.error(f"웨일 창을 종료하는 중 오류 발생: {e}", exc_info=True)
async def close_all_virtual_desktops(self):
def close_all_virtual_desktops(self):
"""모든 가상 데스크톱을 종료"""
try:
desktops = get_virtual_desktops()