ChangePercenty2/prompt.txt

311 lines
22 KiB
Plaintext

안녕? 한글로 얘기해.
나는 온라인 셀링을 하는 사업가야. 필요한 프로그램이 있어서 직접 코딩하는 중이야. 내가 원하는 프로그램을 완성해 줄래?
차근차근 진행해 보자.
전체코드의 구조를 검토해 보고 , 효율적으로 수정해 나가보자.
프로그램 아웃라인
1. 이 프로그램의 목적은 'percenty.co.kr'이라는 웹사이트에 접속 후 로그인 하고,
'마켓 설정'에 존재하는 각 마켓들의 'API key'를 바꾸는 프로그램이야.
2. 마켓은 현재 '쿠팡,' 스스', 'ESM', '11번가-국내', '11번가-해외', , '롯데온'이야. 추후에 더 추가될 수도 있어.
마켓마다 요구하는 api키의 종류는 다양하고 해당부분은 추가되어 있어.
3. 이 프로그램을 사용하는 사람은 사업자가 여러개 있을 수 있고, 사업자마다 마켓별 api키를 가지고 있지.
4. 프로그램 설정으로 저장할 수 있는 총 사업자 숫자를 제한할 수 있어야 해.
5. 각각의 설정은 암호화되어 config.ini 파일에 저장되고, 프로그램이 실행되면 다시 불러오게 해야해.
6. 디버깅을 위해 로그는 로그파일과 콘솔출력 모두 되어야 하고, 디버깅레벨은 디버그, 디버깅이 끝난 후 배포단계에서는 Info로 변경해서 배포할꺼야.
UI 구성
1. pyqt5를 이용한 GUI프로그램이야. 웹과 상호작용하는 부분은 playwright의 비동기 기능을 이용해야해.
2. 프로그램이 실행되면 라이센스 동의 화면이 표시되. 이 화면에는 아랫쪽에 체크버튼이 있고, 체크버튼을 클릭하면 Acept버튼이 활성화 되. 취소 버튼은 항상 활성화 되어있고, 이걸 누르면 프로그램이 종료되.
3. Accept버튼을 클릭하면 라이센스 동의화면이 없어지고 본 기능을 하는 메인윈도우가 표시되.
4. 메인 윈도우에는 '도움말', '퍼센티설정', '사업자설정'이라는 메뉴 item이 있어.
5. '도움말'을 누르면 QTextBrowser 가 실행되고 그 내부에 도움말을 표시할꺼야.
6. '퍼센티설정'을 누르면 사용자 아이디와 비밀번호를 입력받는 작은 위젯이 실행되고, 저장버튼과 취소버튼으로 구성되어있고, 저장 버튼을 누르면 설정이 저장되.
7. '사업자 설정'을 누르면 별도의 사업자설정 위젯이 생겨. 여기는 사업자마다 탭으로 구성되어있고, 탭 내부의 상단레이아웃에는 사업자 정보, 하단레이아웃은 다시 마켓별 탭이 있고, 탭마다 마켓의 api키들을 저장할 수 있는 qlabel과 qtextedit가 쌍을 이루면서 존재해.
8. 메인 윈도우는 mainLayout으로 qvlayout을 사용해.
9. mainLayout의 내부 구성레이아웃으로 3개의 layout들이 있어. 각각 20%, 60% 20%의 면적을 차지해.
10. 1번 qhlayout은 SettingButtonLayout으로 '현재설정 가져오기' 버튼과 '현재설정 저장하기' 버튼이 있어.
10. 현재설정 가져오기 버튼을 누르면 playwright가 비동기로 실행되어 웹사이트에 접속 후 로그인하고, 현재의 마켓설정들을 가져와.
11. 현재설정 저장하기 버튼을 누르면 '사업자 저장' 위젯이 떠.
12. '사업자 저장 위젯'은 사업자 별칭, 사업자등록번호, 상호명, 등록날짜, 응대전화번호를 입력하는 칸이 있어. 해당 칸들을 채운 후 취소버튼과 확인버튼 중 확인버튼을 누르면 해당사업자로 저장되.
13. 이때 현재 등록된 사업자 숫자가 총 등록된 사업자 숫자보다 많은 경우 에러 메세지를 띄워줘.
13. '사업자 저장 위젯'의 필수 입력정보는 사업자등록번호, 상호명이며 사업자별칭이 비어있으면 상호명이 이곳에 들어가면 되.
14. 2번 qhlayout은 currentStatusLayout으로 실제 웹사이트에 접속 후 가져온 데이터를 표시해 주는곳이야.
15. 2번 qhlayout은 3개의 박스로 구성 되어있어.
16. 2번 qhlayout 의 첫번째 박스는 현재 사업자 현황을 보여주는 박스가 표시되. 30%를 차지하는데, 가져온 데이터가 저장된 사업자데이터와 일치하는 경우 그 사업자의 사업자별칭, 사업자등록번호, 상호명, 등록날짜, 응대전화번호가 표시되.
17. 2번 qhlayout 의 두번째 박스는 60%를 차지하는데 내부에는 다시 qhlayout으로 만들어진 marketqhlayout 레이아웃은 마켓별로 구성되고, 여기에는 마켓이름을 표시하는 qpushbutton과 사업자별칭을 표시하는 qplaintext가 있어.
18. 가져온 데이터의 마켓별 api키가 저장된 사업자들 중 일치하는 사업자가 있으면 해당 마켓 라벨의 배경을 연두색으로 하고, 사업자의 별칭을 보여줘.
19. 만약 일치하는 사업자가 없다면 배경을 진한회색으로 변경하고 "NoMatch"로 표시하면 되.
20. 2번 qhlayout 의 세번째 박스 10%를 차지하고 내용은 일단 비워줘.
21. 3번 qvlayout은 selectSettingLayout으로 '사업자 드롭박스', '마켓 체크박스'와 '사업자 바꾸기' 버튼, '진행상황' 프로그레스바가 있어.
22. selectSettingLayout은 '사업자 드롭박스' 위젯1개, '마켓 체크박스'를 표시할 레이아웃 1개, '사업자 바꾸기' 위젯1개, '진행상황' 위젯 1개로 구성되.
22 '사업자 드롭박스'에는 '사업자설정'에서 마켓별 api가 1개라도 설정된 사업자만 표시되.
23. 마켓 체크박스 레이아웃은 마켓이름으로 구성된 체크박스들이 3열로 구성되어 있어. 추후 지원하는 마켓이 늘어나면 이곳에도 추가로 늘어날 예정이야.
24. 마켓 체크박스 레이아웃' 내부에는 '전체선택' 이라는 체크박스와 함께 모든 마켓들이 표시되. 그런데 '사업자 드롭박스'에서 선택된 사업자의 마켓 중 api값이 없는 마켓은 체크할 수 없게 비활성화 되어야 해.
25. '사업자바꾸기' 버튼을 누르면 선택된 사업자와 선택된 마켓을 기준으로 실제 바꾸는 동작을 해야해. 이 동작은 playwright의 비동기작업으로 실행되.
26. 이때 작업 진행상황은 '진행상황'프로그레스 바에 업데이트 시켜줘.
26. 사업자 api가 모두 바꿔지면 '현재설정 가져오기' 의 연결된 메서드 동작 중 현재마켓 설정을 가져오는 메서드만 동작하여 currentStatusLayout의 내용을 업데이트 시켜야 해.
27. 작업이 완료되면 작업완료 창을 띄워줘.
28. 작업완료창은 현황 레이아웃 ('사용자에게 바뀌기전 사업자 현황'과 '바뀐 사업자 현황'으로 구성), '브라우저 실행' 버튼과 '확인'버튼으로 구성되어 있어.
29. 작업완료창의 확인 버튼을 누르면 창이 사라지고, 브라우저 실행 버튼을 누르면 playwright의 chrome이 headless=false 모드로 실행되면서 작업완료 창은 사라져.
30. 사용자가 브라우저 실행 버튼을 누르면서 실행된 playwright는 이제부터 사용자가 웹사이트 작업을 할꺼야. 그래서 기존에 사업자 정보를 불러왔던 객체와는 별도의 객체여야 해.
사업자 설정 버튼을 누르면 나오는 ui를 정의해야 해.
이 파일은 클래스 파일이고 내부에는 기본적은 클래스변수를 포함해 initIU메서드와 함께 여러 기능을 하는 메서드들이 포함되어야 해.
모든 사용자에게 입력받는 모든 데이터는 클래스 변수로 설정되고 메서드들은 그 값들을 이용할 꺼야.
그리고 이 클래스변수에 접근하는 메서드들도 포함해줘,
동작 정의
기본적으로 다른 main 프로그램에 import되어 입력버튼을 누르는 등의 동작에 연결되어 해당하는 위젯이 표시되어야 해.
그래서 처음 main 프로그램이 시작할때 이 클래스의 객체를 생성하고, 필요한 동작이 호출되면 UI를 show 시키는 방식으로 동작하는게 좋겠어.
이 부분은 더 좋은 방법이 있으면 추천해줘
UI 정의
1. 메인 레이아웃은 QFRAME 안에 QVLAYOUT이 설정되어있어. 그 안애는 상단에 10%의 면적을 차지하는 QHLAYOUT이 1개 있고 여기에는 저장,삭제, 취소 버튼이 있고, 설정마켓 버튼이 있어.
저장,삭제,취소 버튼은 해당 메서드에 연결되어 있고, 이 부분은 추후에 구현할 예정이니 그냥 PASS와 함께 해당버튼을 눌렀다는 PRINT문만 추가해줘.
설정마켓 버튼은 클릭하면 작은 위젯이 하나 생성되는데, 여기서는 1행1열의 전체선택 체크박스와 3X3의 각 마켓의 체크박스가 함께 있어. 그리고 확인 버튼과 취소 버튼이 있지.
기본설정은 모든 마켓이 체크되어있어. 그리고 체크박스의 상태에 따라 마켓정보가 있는 QTABWIDGET 표시된 마켓의 탭을 보여줄지 말지의 여부를 결정할 꺼야. 이 버튼은 각각의 사업자 마다 동작해.
그 아래에는 QTABWIDGET이 있고 90%의 면적을 차지해.
2. QTABWIDGET의 탭의 갯수는 클래스 변수에 설정된 MAX_BUSINESS_COUNT 값이야. 이 값은 클래스 객체를 생성할때 인자로 받는 값이야.
각 탭의 제목은 기본값은 '1사업자', '2사업자', 등이 될꺼고, 만약 해당탭 내부의 '사업자 별칭'에 값이 있다면 그 값으로 설정되어야 해.
탭이 선택되면 해당 탭은 색이 연두색으로 변해.
QTABWIDGET은 테두리가 설정되어있고, 내부에는 QVLAYOUT이 설정되어 있어. 그리고 2개의 QFRAME으로 상단에는 사업자 정보 (40%면적차지), 하단에는 마켓정보(60%면적차지)가 들어갈 꺼야.
그 중 사업자 정보 QFRAME의 내부는 qgridlayout이 있고, 내부 구조는 다음과 같아
1행 에는 2개 열이 있고 1열에는 '사업자 별칭' 라벨, 2열에는 해당값을 입력할 수 있는 QLineedit가 있어.
2행 에는 4개 열이 있고 각각 사업자 등록번호, 상호명, 등록날짜, 전화번호 의 Qlabel이 가운데 정렬로 있어.
3행 에는 4개의 열이 있고 각각 2열에 해당하는 값을 입력할 수 있도록 Qlineedit가 있어.
4행 에는 4개 열이 있고 각각 기타 정보1, 기타정보2, 기타정보3, 기타정보4 의 Qpushbutton이 있어.
기타정보의 각 버튼을 누르면 간단한 텍스트를 입력할 수 있는 다이알로그가 떠. 창에는 레이아웃이 3개 있고, 첫번째 QHLAYOUT에는 '제목' 텍스트의 QLABEL과 사용자가 편집할 수 있는 QLINEEDIT 3:7의 비율로 있어. 두번째 QVLAYOUT에는 '내용' 텍스트의 QLABEL과 QTEXTBROWSER가 있어. 세번째 QHLAYOUT에는 확인버튼과 취소버튼이 있어.
확인 버튼을 누르면 기타정보 버튼의 텍스트가 제목 으로 설정된 텍스트로 바껴.
3. 마켓정보 QFRAME에도 역시 QTABWIDGET이 있고, 여기에는 탭마다 해당 사업자의 각 마켓 정보가 들어갈 꺼야.
마켓정보를 담은 QTABWIDGET은 선택된 탭은 연두색으로 변해.
탭 이름은 마켓이름이고(쿠팡,스마트스토어,ESM,11번가-국내,11번가-글로벌,롯데온,인터파크,위메프,옥션1.0)
탭 내부에는 QGRIDLAYOUT으로 설정되어있어.
기본적으로 4열로 구성되어있고, 1열, 3열에는 값 이름, 2열 4열에는 각각 1열,3열에 해당하는 값을 입력할 수 있는 QLINEEDIT가 있어.
- '쿠팡' 1행 : 쿠팡ID, 업체코드
- '쿠팡' 2행 : Access Key','Secret Key'
- '스마트스토어' 1행 :'업로드 할 스마트스토어 계정 ID','업로드 할 스마트스토어 계정 PW',
- '스마트스토어' 2행 :'애플리케이션 ID','애플리케이션 시크릿'
- 'ESM' 1행 :'옥션ID','G마켓 ID'
- '11번가-국내' 1행 :'API KEY'
- '11번가-글로벌' 1행 :'API KEY'
- '롯데온' 1행 :'API KEY'
- '인터파크' 1행:'상품상태재고수정 인증키','상품상태재고수정 비밀키'
- '인터파크' 2행'상품재고조회 인증키','상품재고조회 비밀키'
- '인터파크' 3행'상품정보조회 인증','상품정보조회 비밀키'
- '인터파크' 4행'상품수정 인증키','상품수정 비밀키'
- '인터파크' 5행'상품등록 인증키','상품등록 비밀키'
- '인터파크' 6행'반품배송지조회 인증키','반품배송지조회 비밀키'
- '인터파크' 7행'반품배송지등록 인증키','반품배송지등록 비밀키'
- '인터파크' 8행'상품QnA등록 인증키','상품QnA등록 비밀키'
- '인터파크' 9행'상품QnA조회 인증키','상품QnA조회 비밀키'
- '인터파크' 10행'인터파크 업체번호','공급계약 일련번호'
- '위메프' 1행:'API KEY'
- '옥션1.0' 1행:'멤버 ID'
기타 정보 버튼 아래에 4개의 버튼을 추가로 만들어줘.
이 버튼들은 '사업자등록증', '통신판매업신고증', '사업자통장', '기타이미지' 의 텍스트를 가지고 있어.
이 버튼을 클릭하면 작은 위젯이 생성되고, 위젯에는 4:3 비율의 이미지를 표시할꺼야.
사업자등록증이나 통신판매업 신고증 같은 A4용지를 스캔한 이미지를 표시할 예정이야.
UI의 구성은 아래와 같아.
qvlayout을 메인레이아웃으로 하고 그 내부에 2개의 qhlayout이 있어. 첫번째는 10%의 면적을 차지하고 확인 버튼, 저장 버튼, 삭제버튼이 있어.
두번째 qhlayout은 나머지 90%의 면적을 차지하고 내부에는 이미지를 표시하는 위젯이 있어.
이 모든 정보를 저장하기 위해 실행폴더에 My_BUSINESS_Info 폴더를 만들고 해당폴더 아래에 각 사업자 별로 정보를 저장해야 해.
각 사업자 정보는 BUSINESS_INFO_1,BUSINESS_INFO_2,BUSINESS_INFO_3,BUSINESS_INFO_4,BUSINESS_INFO_5 폴더가 있고,
해당 사업자 폴더 아래에는 business_info.ini 파일, etc_info.ini파일, image폴더, pdf폴더, etc폴더가 있어.
business_info.ini파일에는 사업자 정보(사업자등록번호 등)과 각 마켓의 api정보를 담고 있어.
etc_info.ini파일에는 기타 정보에서 설정한 내용들이 들어있어.
image폴더에는 사업자등록증이나 통신판매업 신고증 같은 이미지파일들이 있고, pdf폴더에는 교육 수료증이나 판매허가증 같은 pdf 파일들이 있어.
etc폴더에는 위의 내용을 제외한 나머지 사업폴더들이 존재할꺼야.
이렇게 폴더구조를 만든 후 이 내용들을 구글드라이브와 동기화 시키는 옵션을 만들고, My_BUSINESS_Info 폴더가 업데이트 될때 마다 동기화를 시키면 좋겠어.
아래의 코드를 처리하는 중 예상치 못한 상황이 발생했어.
id와 pw를 입력 후 로그인버튼을 누르면 일반적으로 로그인이 성공하는데, 그 중간에 2단계 인증을 하는 경우가 가끔 있어.
이 상황을 해결하는 코드가 필요해
2단계 인증에 관한 css는 아래와 같아.
2단계 인증이 활성화 된 경우 사용자에게 메세지를 표시하고, headless모드를 풀어서 팝업 윈도우를 사용자에게 보여주어야 해.
그래서 사용자가 선호하는 방법으로 2단계인증을 완료하고 팝업창을 종료할때까지 대기해야해.
[코드]
# 스마트스토어의 경우 로그인 작업 수행
if market == '스마트스토어' and key == '업로드할스마트스토어계정명':
print(f"스마트스토어 작업 시작")
# 새 창이 열리는 이벤트를 기다림
async with self.page.context.expect_page() as new_page_info: # 새 팝업이 아닌 새창
await self.page.click(self.smartstore_elements['account_button'])
print(f"page.click - account_button")
popup_page = await new_page_info.value
print(f"새 창 열림: {popup_page.url}")
await popup_page.wait_for_load_state()
print("새 창 로드 완료")
await popup_page.evaluate('document.querySelector(arguments[0]).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_type_button'])
await popup_page.click(self.smartstore_elements['popup_login_type_button'])
print(f"page.click - popup_login_type_button")
await popup_page.evaluate('document.querySelector(arguments[0]).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_id_input'])
await popup_page.fill(self.smartstore_elements['popup_login_id_input'], market_api_keys.get('업로드할스마트스토어계정id', ''))
print(f"page.fill - 업로드할스마트스토어계정id")
await popup_page.fill(self.smartstore_elements['popup_login_pw_input'], market_api_keys.get('업로드할스마트스토어계정pw', ''))
print(f"page.fill - 업로드할스마트스토어계정pw")
await popup_page.evaluate('document.querySelector(arguments[0]).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_button'])
await popup_page.click(self.smartstore_elements['popup_login_button'])
print(f"page.click - popup_login_button")
try:
# 팝업 페이지가 닫혀 있는지 확인
if popup_page.is_closed():
print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.")
else:
# 로그인 성공 확인
login_success = await popup_page.inner_text(self.smartstore_elements['login_success_check'])
if login_success != market_api_keys.get('업로드할스마트스토어계정id', ''):
print("스마트스토어 로그인 실패")
return
# 팝업 닫기
await popup_page.close()
except Exception as e:
print(f"로그인 팝업윈도우 처리 중 에러발생 : {e}")
traceback.print_exc()
[2단계인증 관련]
"div#root h2"의 요소의 값이 "2단계 인증" 일 경우 2단계 인증 발생으로 간주
아래의 2개 중 1개로 해결해야 함.
2개중 1개는 활성화 되어 있고, 활성화된 버튼은 아래처럼 표시됨.
활성화된 버튼의 css : "div#root li.TwoStepCertify_choice_item__2qian.TwoStepCertify_on__2Y_8N > label"
1. '휴대전화 인증'이 활성화 된 경우 '이메일 인증' 활성화 버튼 : "div#root li:nth-child(1) > label"
2. 이메일 인증 보내기 버튼 : "div#root div.TextField_text_field__x1Wtz.TextField_field_email__2BzY5.TextField_disabled__2mxn3 > div > div > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
3. '이메일 인증'이 활성화 된 경우 '휴대전화 번호로 인증' 활성화 버튼 : "div#root li:nth-child(2) > label"
4. 휴대전화 번호로 인증 보내기 버튼 : "div#root div.TextField_text_field__x1Wtz.TextField_field_phone__3MV-T.TextField_disabled__2mxn3 > div > div.TextField_ipt_area__3lD1U > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
아래의 요소들을 다시 반영해줘.
[2단계인증 관련]
"div#root h2"의 요소의 값이 "2단계 인증" 일 경우 2단계 인증 발생으로 간주
아래의 2개 중 1개로 해결해야 함.
2개중 1개는 활성화 되어 있고, 관련 CSS 요소는 아래와 같아.
2개의 인증방법 중 1개를 선택하고 인증 버튼을 누르면 창 내부에 '인증전송 확인' 팝업이 뜨고, 해당 팝업의 확인 버튼을 누르면 인증번호 입력칸이 생겨.
제한시간이 3분가량 있고, 역으로 카운트 되. 해당시간 내에 정확안 인증번호를 넣으면 확인버튼이 활성화 되고 해당 버튼을 누르면 로그인 절차가 완료되.
[CSS 요소]
활성화된 버튼의 css : "div#root li.TwoStepCertify_choice_item__2qian.TwoStepCertify_on__2Y_8N > label"
1. '휴대전화 인증'이 활성화 된 경우 '이메일 인증' 활성화 버튼 : "div#root li:nth-child(1) > label"
2. 이메일 인증 보내기 버튼 : "div#root div.TextField_text_field__x1Wtz.TextField_field_email__2BzY5.TextField_disabled__2mxn3 > div > div > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
3. '이메일 인증'이 활성화 된 경우 '휴대전화 번호로 인증' 활성화 버튼 : "div#root li:nth-child(2) > label"
4. 휴대전화 번호로 인증 보내기 버튼 : "div#root div.TextField_text_field__x1Wtz.TextField_field_phone__3MV-T.TextField_disabled__2mxn3 > div > div.TextField_ipt_area__3lD1U > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
이메일 인증이 활성화 되었을 때 보이는 '인증용 이메일 주소' : input#auth_id.TextField_ipt__33BFT[placeholder='2단계 인증 이메일']
휴대전화 인증이 활성화 되었을 때 보이는 인증용 휴대전화 번호 : input#phone[placeholder='내용을 입력해주세요']
이메일 인증번호를 전송했을때 전송되었다는 확인팝업의 확인버튼 : div#root button.PopupCommon_btn__33Of5[type='button']
이메일 주소 인증의 인증번호 입력칸 : div#root input.TextField_ipt__33BFT[inputmode='numeric'][placeholder='인증번호 숫자 6자리']
이메일 주소 인증의 인증번호 입력 취소버튼 : "div#root div.TextField_text_field__x1Wtz.TextField_field_email__2BzY5.TextField_disabled__2mxn3 > div > div > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
이메일 주소 인증의 인증 유효시간 :"div#root div.TextField_time__1AWa7 > span"
휴대전화 인증번호를 전송했을때 전송되었다는 확인팝업의 확인버튼 : div#root button.PopupCommon_btn__33Of5[type='button']
휴대전화 인증의 인증번호 입력칸 : div#root input.TextField_ipt__33BFT[inputmode='numeric'][placeholder='인증번호 숫자 6자리']
휴대전화 인증의 인증번호 입력 취소버튼 : "div#root div.TwoStepCertify_certify_num__1m4OX > div > div.TextField_ipt_item__1AOpe > div > div.TextField_btn_box__2TdIe > button[type=\"button\"]"
휴대전화 인증의 인증 유효시간 : "div#root div.TextField_time__1AWa7 > span"
인증번호 입력 후 확인 버튼 : div#root .TwoStepCertify_btn_box__3TSSP .Button_btn_plain__1j7dG[type='button']