Compare commits

...

169 commits

Author SHA1 Message Date
gyuu04
20578d7f7b
Merge pull request #39 from j93es/feat/some
Browser Use Proxy 미사용 문제 해결
2025-07-13 15:52:27 +09:00
2d3677fe4a ignore_default_args에서 주석 처리된 옵션 정리 2025-07-13 14:35:44 +09:00
ba1e81177b 코드 정리 및 개선: 파일 생성 로직 및 Playwright 설치 과정에서의 예외 처리 개선, 사용자 데이터 디렉토리 복사 시 로그 추가 2025-07-13 14:33:55 +09:00
9858d7acd2
Merge pull request #38 from j93es/feat/some
feat: Browser use v0.5.3 업데이트 및 프로세스 안정성 개선
2025-07-11 23:51:51 +09:00
d6803ad20e 브라우저 리소스 정리 및 종료 처리 개선, 진행 상황 저장 기능 추가 2025-07-11 18:37:31 +09:00
0f5ab6dea1 browser use 버전 업데이트 및 프롬프트 개선 및 임시 파일 삭제 구조 개선 2025-07-11 15:51:29 +09:00
gyuu04
657d5370b9
Merge pull request #35 from j93es/feat/github
Github 프롬프트 테스트 완료
2025-07-10 13:55:26 +09:00
gyuu04
628c994f22
Merge pull request #34 from j93es/feat/microsoft
microsoft prompt 정교화
2025-07-10 13:55:14 +09:00
sultanofdisco
a8165e9cdc
Merge pull request #37 from j93es/fix-goolgle
프롬프트 수정
2025-07-09 12:51:48 +00:00
sultanofdisco
90f0487dd7
Merge pull request #36 from j93es/facebook-prompt
페이스북 프롬프트 테스트 완료
2025-07-09 12:51:25 +00:00
sultanofdisco
0904f282ff 프롬프트 수정 2025-07-09 19:06:07 +09:00
gyuu04
d01a78d442 B
페북 프롬프트 정교화

- agents.py에서 응답만 있으면 성공으로 처리하던 문제를 수정 -> status가 "success"인 경우만 성공으로 반환
- 페북 로그인 프롬프트 정교화
2025-07-09 17:04:45 +09:00
seungyeoncherry
52b10446f7 프롬프트 수정 2025-07-09 16:33:57 +09:00
tk
ff54e8d4e3 프롬프트 수정(테스트 O) 2025-07-08 18:37:21 +09:00
08e7e34b9f
Merge pull request #33 from j93es/feat/chromium
크로미움 사용, user data 복사 수정
2025-07-05 15:33:21 +09:00
1742afcb5e
Merge pull request #32 from j93es/0705
[Update] naver id
2025-07-05 15:29:37 +09:00
68eedc3fa4 Chromium 설치 메시지 수정 및 사용자 데이터 복사 로직 개선 2025-07-05 15:28:57 +09:00
2b805df001 env error 2025-07-05 15:14:17 +09:00
tv0924@icloud.com
264f29ffb9 [Update] naver id 2025-07-05 15:01:12 +09:00
김민곤
8d1aa3df1a
Merge pull request #31 from j93es/imnyang-patch-2
Update .env.example
2025-07-04 21:25:43 +09:00
952db8d2b9
Update .env.example 2025-07-04 21:24:47 +09:00
2fdd187f9a
Merge pull request #30 from j93es/feat/user-data
User Data로 세션 제어, 여러 문제 수정
2025-07-03 15:22:05 +09:00
a9f3bc7233 Browser Use Downgrade 2025-07-02 23:29:53 +09:00
735e90739c Merge branch 'main' into feat/user-data 2025-07-02 20:52:46 +09:00
72f784dec5 docs: 레거시 설치 및 설정 섹션 삭제 2025-07-02 20:34:06 +09:00
b3b5f05697
Update README.md 2025-07-02 20:30:13 +09:00
c5e0a8c2f1
Delete .legacy directory 2025-07-02 20:27:10 +09:00
c3a0132489
Delete docs directory 2025-07-02 20:26:40 +09:00
b45daebc81
Update README.md 2025-07-02 20:26:29 +09:00
f5ee676468 feat: 리팩토링, User Data 2025-07-02 20:18:01 +09:00
8cfb6488d1 feat: .env.example에 HEADLESS 옵션 추가 2025-07-02 20:17:44 +09:00
3199a53a44 refect: 코드 가독성 해결 2025-07-02 19:10:58 +09:00
146c187b05 refect: OAuth 프롬프트 및 모델 구조 개선 2025-07-02 19:02:43 +09:00
92eea9c0c9
Merge pull request #29 from j93es/temp/mingon
[FEAT]: env로 로그인 구현
2025-07-01 18:34:03 +09:00
KMINGON
b2aedf53db [FEAT]: env로 로그인 구현 2025-07-01 01:05:07 +09:00
65c865b620
Merge pull request #26 from j93es/feat/microsoft
Feat/microsoft
2025-06-30 22:18:34 +09:00
54e923ae95
Merge pull request #25 from j93es/feat/facebook
facebook prompt 추가
2025-06-30 22:18:28 +09:00
13a2798fa4
Merge pull request #24 from j93es/feat/github
github prompt 추가
2025-06-30 22:18:21 +09:00
3e2b598298
Merge pull request #28 from j93es/chore/hotfix
HotFix
2025-06-30 21:08:33 +09:00
18a575a8af [ADD] 재시도 큐 시스템 추가 및 관련 함수 구현 2025-06-30 21:01:01 +09:00
seungyeoncherry
1c43e63cca Merge branch 'main' of https://github.com/j93es/browser-use-oauth into feat/facebook 2025-06-30 17:54:00 +09:00
seungyeoncherry
d1737f27a3 Merge branch 'main' of https://github.com/j93es/browser-use-oauth into feat/github 2025-06-30 17:49:23 +09:00
tk
ba0bb61ef3 마소 프젝 계정 업데이트 2025-06-30 17:48:11 +09:00
tk
fe003013f4 파일 정리 2025-06-30 17:38:13 +09:00
seungyeoncherry
91c18e12c7 facebook prompt 추가 2025-06-30 17:20:50 +09:00
sultanofdisco
686333e75c
Merge pull request #23 from j93es/feat/apple
Feat/apple
2025-06-30 16:51:55 +09:00
seungyeoncherry
79c9dffa08 github prompt 추가 2025-06-30 16:39:24 +09:00
sultanofdisco
1d7d9c8ad5 Update prompt.py 2025-06-30 02:43:35 +09:00
sultanofdisco
812a26a103 Update prompt.py 2025-06-30 02:35:53 +09:00
sultanofdisco
b7df8cffcd Update .sensitive.example.json 2025-06-29 23:56:39 +09:00
sultanofdisco
bcca364021 [ADD]apple 프롬프트 추가 2025-06-29 22:37:36 +09:00
tk
e1c07c4a1e microsoft 제작 2025-06-29 21:05:07 +09:00
tk
b26c47d1ad Merge branch 'main' of https://github.com/j93es/browser-use-oauth into tk 2025-06-29 14:29:36 +09:00
2202a1a2d8
Merge pull request #22 from j93es/feat/refect
Breaking Changes: 코드 전체 리팩토링
2025-06-29 11:53:48 +09:00
bc9b598993 feat: Browser Use Update 2025-06-29 11:53:16 +09:00
353e98e28c feat: OAuth 제공자 추출 및 로그인 테스트 개선 2025-06-28 11:51:19 +09:00
tk
54682cdb72 feat/refect 가져오기 2025-06-27 23:06:35 +09:00
tk
ce70191d49 microsoft 추가 2025-06-27 22:01:59 +09:00
20601cec76 feat: 구글 로그인 프롬프트 및 모델 추가
Co-authored-by: James <j93es.jung@gmail.com>
2025-06-27 20:31:19 +09:00
70e8bdbbde feat: Prompt 구조 개선 2025-06-27 20:28:12 +09:00
seungyeoncherry
d8ec21c61b
Merge pull request #20 from j93es/feat/split-agent
Agent 모듈화 (feat. 코드베이스 전체 변경)
2025-06-26 22:25:55 +09:00
20ac0ccc06 chore: README.md 2025-06-26 22:13:06 +09:00
069dbf446d feat: 코드베이스 리팩터링
* `run.py`에서 `main.py` 경로를 명시적으로 지정하고, 명령줄 인자를 보다 사용하기 쉽게 조정했습니다.
* 에이전트, 리소스 정리, 공통 함수, 모델 등을 포함하는 브라우저 유틸리티용 신규 모듈 구조를 만들었습니다.
* `agents.py`에 비동기 에이전트 실행 및 재시도 로직을 구현했습니다.
* `scanner.py`에 OAuth URL 추출 및 로그인 테스트 기능을 추가했습니다.
* 전반적인 코드베이스에 걸쳐 에러 핸들링 및 로깅을 강화했습니다.
* 백엔드 URL과 Google API 키 등의 관리를 위한 환경변수 기반 설정 시스템을 도입했습니다.
* 스캐닝 중 진행 상태 추적 및 시그널 핸들링을 통한 정상 종료 처리를 개선했습니다.
* 텍스트 파일 읽기 및 HTML 콘텐츠 여부 확인을 위한 유틸리티 함수를 추가했습니다.
* LLM과의 상호작용을 위한 구조화된 프롬프트 시스템을 구축했습니다.
2025-06-26 21:44:31 +09:00
1ddc3c41bc
Update README.md 2025-06-25 22:58:22 +09:00
4261bdc7d6
Update README.md 2025-06-25 22:57:34 +09:00
5c816baf67 chore: 윈도우 인코딩 이슈 해결 2025-06-25 13:43:15 +09:00
3d55b6275e Update docs and remove unused prompt files
The commit updates documentation with a guide image and removes unused
prompt files for Google and Meta logins.
2025-06-24 23:57:38 +09:00
5535515dbd Merge branch 'feat/split-agent' of https://github.com/j93es/browser-use-oauth into feat/split-agent 2025-06-24 22:44:55 +09:00
c2e610ec54 temp_commit: 프롬프트 확장 가이드 추가 및 Google, Meta 프로바이더에 대한 SSO 로그인 리디렉션 URL 수집 로직 구현 2025-06-24 22:44:51 +09:00
32f2ce486e temp_commit: 프롬프트 확장 가이드 추가 및 Google, Meta 프로바이더에 대한 SSO 로그인 리디렉션 URL 수집 로직 구현 2025-06-24 22:44:09 +09:00
27192dab3a feat: LLM 및 유틸리티 모듈 구조 개선 및 불필요한 코드 제거 2025-06-24 22:25:28 +09:00
b0a4727e13 fix: get_prompt 함수에서 type 비교 시 대소문자 구분 없애기 2025-06-23 20:44:54 +09:00
a5d8d674b4 Merge branch 'main' of https://github.com/j93es/browser-use-oauth into feat/split-agent 2025-06-23 20:42:38 +09:00
9af67fbd49 fix: 로그인 차단 시 반환 형식 수정 및 즉시 종료로 변경 2025-06-23 20:39:28 +09:00
e85c128ed8 fix: SSO 로그인 절차 간소화 및 불필요한 JSON 반환 형식 제거 2025-06-23 20:39:00 +09:00
1767cd5861 feat: OAuth 리스트 추출 및 모델 수정, 파일 생성 및 설정 과정 개선
- Windows에서 storage_state 이슈가 일어납니다.
2025-06-23 20:38:02 +09:00
6ddeed2173 fix: 온도 설정 및 OAuth 제공자 식별 프롬프트 개선 2025-06-23 19:59:51 +09:00
e1319a108d chore: storage_state.json 관련해서 수정함
- Windows 싫어요
2025-06-23 19:19:56 +09:00
4b3637b762 feat: OAuth 리스트 추출 및 로그인 기능 개선
- README.md: uv 실행 명령어 수정
- lib/llm/prompt: OAuth 리스트 추출 및 fallback 프롬프트 추가
- lib/utils/browser_use: 프로필 생성 시 스토리지 상태 파일 처리 개선
- lib/utils/browser_use/func: 안전한 JSON 읽기 및 쓰기 함수 추가
- main.py: OAuth 리스트 추출 및 개별 로그인 시도 통합
- model.py: OAuth 모델 수정
2025-06-23 00:15:03 +09:00
c1ade99b8a
Merge pull request #19 from j93es/feat/setup
chore: 환경 설정 및 크로스 플랫폼 실행 파일
2025-06-22 23:45:27 +09:00
46a169f1d0
HotFix #1 2025-06-22 23:44:57 +09:00
4f90285bdd modified: README.md 2025-06-22 23:13:39 +09:00
3dd86982d7 chore: 환경 설정 및 크로스 플랫폼 실행 파일 2025-06-22 23:13:26 +09:00
95d56259e7
Merge pull request #17 from j93es/chore/vibe-coding
바이브 코딩
2025-06-22 18:40:37 +09:00
638a3d47ce Add comprehensive documentation for Browser Use features
- Introduced custom output format instructions with example code.
- Detailed connection methods for launching and connecting to browsers, including local and remote options.
- Provided guidelines for handling sensitive data securely, including best practices and examples.
- Documented supported LangChain chat models with setup instructions and environment variable requirements.
- Added instructions for customizing the system prompt to control agent behavior.
2025-06-21 16:18:16 +09:00
34ee66b4e8
Merge pull request #16 from j93es/feat/fix-prompt
[Update] prompt
2025-06-21 16:08:12 +09:00
tv0924@icloud.com
1ca98bc067 [Update] prompt 2025-06-21 16:06:35 +09:00
James
03acba17f9
Merge pull request #15 from j93es/feat/login
feat: implement OAuth login functionality
2025-06-21 15:58:29 +09:00
d47ebe0960 aa 2025-06-21 15:57:37 +09:00
3f801ab4f7 fix: 쿠키 삭제 방법 추가 및 planner_llm 주석 처리 2025-06-21 15:57:22 +09:00
f157039351 fix: 쿠키 삭제 및 페이지 새로 고침 관련 문서 수정, 의존성 버전 하향 조정 2025-06-21 15:49:48 +09:00
8ad30460ce feat: 개선된 쿠키 삭제 및 페이지 새로 고침 기능 추가
- 'clear_cookies' 액션을 'Clear all cookies and reload page'로 변경
- 현재 페이지가 없거나 잘못된 타입일 경우 오류 처리 추가
- 쿠키 삭제 후 페이지 새로 고침 성공 메시지 및 오류 메시지 추가
- extend_planner_system_message에 OAuth 관련 조건 추가
2025-06-21 15:32:17 +09:00
c9f146baef feat: implement OAuth login functionality
- Update environment configuration and gitignore
- Replace sensitive example with actual config file
- Refactor LLM prompt structure by removing login/session modules
- Update main application entry point for OAuth integration
2025-06-21 15:23:47 +09:00
김민곤
c20bfda3af
Merge pull request #14 from j93es/chore/browser-use-update
Browser Use v0.3.1
2025-06-21 12:10:40 +09:00
c43535c20e [Update] pyproject.toml 및 uv.lock 파일에서 browser-use 및 authlib 버전 업데이트 2025-06-21 12:06:56 +09:00
4f0097049c
Merge pull request #13 from j93es/feat/sensitive-data
Docs 수정 및 Sensitive Data 기능 추가
2025-06-20 22:03:49 +09:00
753afa6656
Update README.md 2025-06-18 22:27:01 +09:00
871659cc00 [Enhancement] Browser Use에게 직접 로그인 요청 섹션에 선택 사항 추가 2025-06-18 21:50:14 +09:00
3521125b5a Merge branch 'feat/sensitive-data' of https://github.com/j93es/browser-use-oauth into feat/sensitive-data 2025-06-18 21:49:03 +09:00
34a98773ab [Fix] GetSensitiveData 함수에서 파일이 없을 경우 예외 대신 None 반환 2025-06-18 21:45:14 +09:00
8444abc242 [Fix] GetSensitiveData 함수에서 파일이 없을 경우 예외 대신 None 반환 2025-06-18 21:43:35 +09:00
1b65693ba5 Add function to read sensitive data from .sensitive.json file
- Implemented GetSensitiveData function to load sensitive data from a JSON file.
- Added error handling for missing file scenario.
2025-06-18 21:42:06 +09:00
29d91bac56 [Enhancement] 도메인 파일 경로를 './data/domains.txt'로 변경 2025-06-18 21:18:01 +09:00
9f6d172c23 [Enhancement] .vscode/settings.json 파일 추가: rust-analyzer 설정 포함 2025-06-18 21:15:14 +09:00
c5ff066c65
Merge pull request #12 from j93es/feat/storage-state
Browser Use Latest && 쿠키 로컬스토리지 추가 등
2025-06-16 08:22:14 +09:00
imnyang
7ae5834d82 [Enhancement] setup_storage_state 및 GetProfile 함수를 비동기 처리로 변경 2025-06-15 22:37:35 +09:00
imnyang
adec1f5e49 [Enhancement] GetProfile 함수 추가 및 브라우저 프로필 생성 로직 통합 2025-06-15 22:36:20 +09:00
imnyang
b49fe8b49e Merge branch 'feat/storage-state' of https://github.com/j93es/browser-use-oauth into feat/storage-state 2025-06-15 22:09:00 +09:00
imnyang
1f3b2f5c8a [Enhancement] 스토리지 상태 경로 설정 로직 개선: 스크립트 디렉토리 기반으로 수정 2025-06-15 22:08:57 +09:00
c64c33516e [Enhancement] setup_storage_state 함수 호출 추가: URL 스캔 시작 시 저장 상태 설정 2025-06-15 15:21:16 +09:00
imnyang
1d5e470f4e [Enhancement] 진행 상황 저장 및 로드 기능 추가, 스캔 중단 시 처리 상태 출력 2025-06-15 13:15:41 +09:00
imnyang
b68425f523 Refactor authentication and session management
- Removed old llm_login and session scripts, replacing them with a new structure for handling SSO login and session management.
- Introduced a new prompt system for collecting SSO redirect URLs, ensuring compliance with security protocols.
- Implemented a robust backend notification system for tracking scan initiation.
- Enhanced browser profile configuration and resource management for improved session handling.
- Added utility functions for environment variable checks and logging.
- Updated the overall architecture to improve maintainability and readability.
2025-06-15 12:24:01 +09:00
imnyang
2d8a7d5cfb [Enhancement] LMNR 초기화 로직 간소화: BASEURL 및 포트 설정 제거 2025-06-15 03:14:51 +09:00
imnyang
cbc621aa52 [Enhancement] LMNR 관련 환경변수 정리 및 기본 URL 수정 2025-06-15 03:11:14 +09:00
imnyang
05bd499e86 [Enhancement] .env.example 파일에 LLM 및 Monitoring 섹션 추가 2025-06-15 03:07:53 +09:00
imnyang
6e74377028 [Enhancement] LMNR 초기화 로직 개선: HTTP 및 gRPC 포트 설정 추가 2025-06-15 03:04:36 +09:00
imnyang
546d9c061a [Enhancement] LMNR 초기화 시 BASEURL 환경변수 추가 2025-06-15 02:58:57 +09:00
imnyang
ed120f7a69 [Enhancement] BrowserProfile 설정 수정 및 창 크기 조정 2025-06-15 02:53:48 +09:00
imnyang
2d3d3dfe88 Refactor code structure for improved readability and maintainability 2025-06-15 01:53:52 +09:00
imnyang
25be609dae [Enhancement] Browser Use 패키지 버전 업데이트 및 subprocess를 통한 패키지 정보 출력 추가 2025-06-14 22:11:41 +09:00
imnyang
6dc388421c v0.2.7 2025-06-14 21:44:14 +09:00
f6368d12a3 [Enhancement] README 및 코드에서 Playwright 의존성 및 SSO 로그인 흐름 개선 2025-06-14 18:08:39 +09:00
9b797a8d21 [Enhancement] async_playwright 추가 및 OAuth 로그인 흐름 개선, Planner 제외 (오히려 문제가 일어남) 2025-06-14 18:08:33 +09:00
imnyang
1474399f5c [Enhancement] SSO 로그인 리디렉션 URL 수집을 위한 프롬프트 개선 및 명확한 지침 추가 2025-06-13 22:40:07 +09:00
imnyang
a52bf2907c [Enhancement] 환경 변수 추가 및 SSO 로그인 리디렉션 URL 수집을 위한 확장된 프롬프트 구현 2025-06-11 23:16:19 +09:00
imnyang
ad4354e126 [Enhancement] 초기 URL 내에서 확인되지 않은 URL로의 이동 금지 및 로그인 시도 우선화 로직 추가 2025-06-11 21:33:45 +09:00
imnyang
45b64a9bc9 [Enhancement] SSO 버튼 클릭 및 로그인 시도 로직 추가 및 리디렉션 URL 캡처 방식 개선 2025-06-11 21:09:12 +09:00
imnyang
863828f802 [Enhancement] 임시 스토리지 상태 파일 삭제 및 복사 로직 추가 2025-06-11 20:45:33 +09:00
imnyang
8df6811cd0 [Refactor] 환경 설정 및 API 재시도 로직 개선, 불필요한 파일 삭제 2025-06-11 20:39:17 +09:00
imnyang
00558603a7 세션 복구 삭제 2025-06-11 19:51:02 +09:00
imnyang
2ee917408f Browser Use 최신버전 업데이트 및 프로필 기능 유지 2025-06-11 19:50:10 +09:00
imnyang
e1ae2dc94f 지금 일단 업데이트만 해뒀어요 2025-06-10 22:38:44 +09:00
James
09a91f9c7c
Merge pull request #9 from j93es/j93es2
에러 발생 관련 로직 개선
2025-06-09 13:10:50 +09:00
tv0924@icloud.com
94ca383b82 [Fix] 에러 발생 시, 재시도 후 return 되지 않는 문제 해결 2025-06-09 10:33:08 +09:00
tv0924@icloud.com
cb7dbccb04 [Update] sleep time 2025-06-08 22:05:23 +09:00
tv0924@icloud.com
267fb40fa4 [Update] logger 2025-06-08 22:04:54 +09:00
tv0924@icloud.com
a655697b0f [Refactor] 프롬프트를 다른 파일로 분리 2025-06-08 21:09:05 +09:00
tv0924@icloud.com
03d48880d5 gitignore 2025-06-08 14:31:48 +09:00
tv0924@icloud.com
61b6b8b356 [Update] 레이트 리밋 등의 에러 발생 시 1분 간 멈추고 실행 2025-06-08 14:18:39 +09:00
seungyeoncherry
1b0a0deeb4
Merge pull request #7 from j93es/domains-gitignore
domains.txt를 제외하였습니다.
2025-06-08 14:02:25 +09:00
김민곤
f4796d8bbe
Merge pull request #6 from j93es/imnyang
LLM 레이트리밋 줄이기, .env.example에 있는 BACKEND_URL가 누락되었습니다.
2025-06-08 14:01:38 +09:00
81f76259a0 domains.txt 삭제 2025-06-08 13:50:14 +09:00
51b9df078c 가이드 수정 2025-06-08 13:49:15 +09:00
7ee8d96b88 Merge branch 'main' of https://github.com/j93es/browser-use-oauth into imnyang 2025-06-08 13:44:22 +09:00
83cefc2c04 부서진 레이트리밋과 전해지지 않는 LLM의 응답 2025-06-08 13:43:58 +09:00
2df17aad34
Update run.sh 2025-06-08 03:00:49 +09:00
imnyang
6436f3cf4f Hotfix #2 2025-06-07 22:07:06 +09:00
imnyang
1642676c95 Hotfix #1 2025-06-07 22:06:14 +09:00
James
512aadfc7f
Merge pull request #5 from j93es/imnyang
README.md를 수정
2025-06-07 19:20:09 +09:00
a186bb10a8
Update README.md 2025-06-07 17:26:53 +09:00
imnyang
bf41ccdb92 가이드라인 배포 2025-06-07 17:19:27 +09:00
James
714c0ebea7
Merge pull request #4 from j93es/imnyang
프록시와 통신하게 구현
2025-06-07 15:16:19 +09:00
imnyang
d228fcc620 Merge branch 'main' of https://github.com/j93es/browser-use-oauth into imnyang 2025-06-07 15:15:05 +09:00
imnyang
02c2f6a5de 도메인 누락 2025-06-07 15:13:27 +09:00
imnyang
97184c03d7 이거도 대충했어요 2025-06-07 15:12:25 +09:00
James
ea5158ca7d
Merge pull request #3 from j93es/gemini-new-model
Gemini new model
2025-06-07 12:58:30 +09:00
eeeb8b4223
Update .env.example 2025-06-07 12:48:52 +09:00
imnyang
c0499819be 브라우저 헤더 설정을 개선하고 사용자 에이전트를 업데이트했습니다. 2025-06-07 00:05:46 +09:00
imnyang
b879cafe1b HTML 도메인 카운터 추가 및 최종 결과 출력 개선 2025-06-07 00:03:15 +09:00
imnyang
905a1bee6d user agent Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0 2025-06-07 00:01:52 +09:00
imnyang
603531a0c0 너무 빠르니까 레이트리밋 걸리는거 같아요요 2025-06-06 23:57:19 +09:00
imnyang
e6fb9d65d6 temp 2025-06-06 23:51:13 +09:00
imnyang
351af7ba78 Implement HTML domain checker in Rust and add PowerShell script for chunked execution
- Added a Rust program that reads a list of domains from "domains.txt", checks if they return HTML content, and writes valid domains to "domains-filtered.txt".
- Introduced a PowerShell script to execute a Python script in chunks, allowing for processing of specified line ranges from the domain list.
2025-06-06 23:47:59 +09:00
James
c6ccc514b1
Merge pull request #1 from j93es/j93es-workspace
[Update] 메모리 관리 및 프롬프트 수정
2025-06-06 22:35:25 +09:00
tv0924@icloud.com
197fa6a033 [Update] 메모리 관리 및 프롬프트 수정 2025-06-05 22:19:26 +09:00
tv0924@icloud.com
9818dafce4 [Update] prompt 2025-06-05 16:10:30 +09:00
tv0924@icloud.com
7973559e62 [Add] 2025-06-04 22:14:38 +09:00
tv0924@icloud.com
802b8311a7 [Update] 여러 url을 연속으로 돌릴 수 있음 2025-06-02 20:24:00 +09:00
tv0924@icloud.com
a2c88daa74 여러 url을 연속적으로 실행 2025-06-02 15:09:43 +09:00
61 changed files with 5600 additions and 998 deletions

View file

@ -1,9 +1,38 @@
# 권장 (다른 모델로 교체 가능) [다른 모델로 교체시 성능 보장 불가]
ANONYMIZED_TELEMETRY=false ANONYMIZED_TELEMETRY=false
# ========== LLM ==========
GOOGLE_API_KEY= GOOGLE_API_KEY=
GOOGLE_MODEL=gemini-2.5-flash-preview-04-17 # 권장 (다른 모델로 교체 가능) [다른 모델로 교체시 성능 보장 불가] # 권장 (다른 모델로 교체 가능) [다른 모델로 교체시 성능 보장 불가]
GOOGLE_PLANNER_MODEL=gemini-2.0-flash-lite # 권장 (다른 모델로 교체 가능) [다른 모델로 교체시 성능 보장 불가] GOOGLE_MODEL=gemini-2.5-flash
#GOOGLE_PLANNER_MODEL=gemini-2.5-flash # 왜 비활성화 되었나요? // Planner 모델이 오히려 문제를 일으키는 경우가 있어 비활성화했습니다. 필요시 활성화하세요.
# min(INITIAL_BACKOFF * (2 ** try_cnt), MAX_BACKOFF)만큼 API가 실패시 대기합니다.
INITIAL_BACKOFF=60
MAX_BACKOFF=600
#ENABLE_PLANNER_MODEL_OAUTH_LOGIN=true # OAuth 로그인 시 Planner 모델을 활성화합니다.
#ENABLE_PLANNER_MODEL_OAUTH_LIST=true # OAuth List를 찾을 때 Planner 모델을 활성화합니다.
# ========== Monitoring ==========
# 선택 # 선택
PROXY_HOST=127.0.0.1 PROXY_HOST=127.0.0.1
PROXY_PORT=8080 PROXY_PORT=11080
BACKEND_URL=http://localhost:11081
# https://docs.browser-use.com/development/observability - 선택
# Lmnr 계정이 필요합니다.
# https://lmnr.ai/
LMNR_PROJECT_API_KEY=
# 브라우저 언어 설정
LANG=en_US
HEADLESS=False # 브라우저를 헤드리스 모드로 실행할지 여부. True로 설정하면 브라우저가 보이지 않습니다.
# ========= Account ==========
# 필수 뒤에 있는 이메일 주소는 Google 계정의 로그인 힌트로 사용됩니다.
# 이메일의 전체를 입력해주세요
GOOGLE_ID=whs.imnya.ng@gmail.com

75
.gitignore vendored
View file

@ -6,8 +6,83 @@ dist/
wheels/ wheels/
*.egg-info *.egg-info
oauth_providers.csv
# Virtual environments # Virtual environments
.venv .venv
.env .env
.sensitive.json
log_*.log log_*.log
domains.txt
# Created by https://www.toptal.com/developers/gitignore/api/macos,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
my.sh
log.txt
data/
!src/lib/utils/data
# End of https://www.toptal.com/developers/gitignore/api/macos,windows

42
.sensitive.example.json Normal file
View file

@ -0,0 +1,42 @@
{
"google.com": {
"x_username": "whs.imnya.ng@gmail.com",
"x_password": "Vb1Mz9pgjY8JVs"
},
"accounts.google.com": {
"x_username": "whs.imnya.ng@gmail.com",
"x_password": "Vb1Mz9pgjY8JVs"
},
"naver.com": {
"x_username": "oauth-test-test",
"x_password": "gx^AKz-289d3/7B"
},
"nid.naver.com": {
"x_username": "oauth-test-test",
"x_password": "gx^AKz-289d3/7B"
},
"github.com": {
"x_username": "imnyang-bot",
"x_password": "6PuVXCH9tpQLNm"
},
"apple.com": {
"x_username": "",
"x_password": ""
},
"appleid.apple.com": {
"x_username": "",
"x_password": ""
},
"microsoft.com": {
"x_username": "whs.imnya.ng@gmail.com",
"x_password": "WHS123987"
},
"login.microsoftonline.com": {
"x_username": "whs.imnya.ng@gmail.com",
"x_password": "WHS123987"
},
"facebook.com": {
"x_username": "01047183675",
"x_password": "whs3oauth@"
}
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"rust-analyzer.initializeStopped": true
}

116
README.md
View file

@ -1,48 +1,106 @@
# 테스트한 영상
https://f.imnya.ng/.whs/teamproject/
# 참고하면 좋을만한 것
- [ ] 일부 웹사이트는 사용자의 언어에 따라 OAuth 옵션을 바꾸기도 합니다.
- [ ] https://docs.browser-use.com/customize/custom-functions
# 환경 설정 # 환경 설정
이 프로젝트는 [uv](https://docs.astral.sh/uv/getting-started/installation/)라는 Python 패키지 관리자를 사용하여 설정해야합니다. 요구 사항
- [uv](https://docs.astral.sh/uv/getting-started/installation/) - Python Package Manager Written by Rust
- [oauth-backend](https://github.com/j93es/oauth-backend)
- [Google Chrome](https://www.google.com/intl/ko_kr/chrome/)
---
> [oauth-backend](https://github.com/j93es/oauth-backend) 프록시를 사용한다면 이 가이드에 따라 인증서 또한 설정되어야만 합니다.
>
> 그렇지 않으면 실행되지 않습니다.
>
> 윈도우 환경에서는 `sudo certutil -addstore root mitmproxy-ca-cert.cer`로 인증합니다.
>
> Sudo가 활성화되어있지 않은 환경에서는 관리자로 상향된 쉘에서 실행합니다.
>
> MacOS 환경에서는 `sudo security add-trusted-cert -d -p ssl -p basic -k /Library/Keychains/System.keychain ~/.mitmproxy/mitmproxy-ca-cert.pem`으로 인증합니다.
>
> 다른 플렛폼은 수동으로 설정되어야만 합니다.
> https://docs.mitmproxy.org/stable/concepts/certificates/
현재 아래와 같은 환경에서 개발되며 테스트되고 있습니다.
- ✅ MacOS 26 Tahoe Developer Beta 2 (25A5295e) en-US aarch64
- ✅ Windows 11 Pro for Workstations 24H2 (26100.4351) en-US x86_64
- ✅ NixOS 25.05.804570.c7ab75210cb8 KDE 6 / Linux 6.15 x86_64
---
다음과 같은 명령어로 환경을 설정합니다.
설명하는 가이드를 잘 따라가면 설정할 수 있습니다.
```sh
uv run setup.py
```
uv 설치 후 다음과 같은 명령어를 입력합니다. uv 설치 후 다음과 같은 명령어를 입력합니다.
``` ```sh
uv sync uv sync
``` ```
venv와 패키지가 설치가 됩니다. venv와 패키지가 설치가 됩니다.
browser_use가 Playwright에 대한 의존성이 있어 브라우저 설치가 필요합니다 ---
``` `uv run setup.py`로 환경을 설정합니다.
playwright install chromium --with-deps --no-shell
---
# 윈도우 인코딩 이슈 해결
이거 해결 방법
![image](https://github.com/user-attachments/assets/01ca45c2-fda9-44fb-83fc-39daa7e52092)
![image](https://github.com/user-attachments/assets/55c502f5-0bf7-44f8-bbb4-1dc1e0c8c3c3)
이것도 setup.py 사용하면 반자동으로 할 수 있습니다.
못찾겠으면 intl.cpl 열어주세요.
# 실행
domains.txt는 실행시 자동으로 다운로드 됩니다.
```sh
curl "https://f.imnya.ng/.whs/tp-domains/data/domains/latest.txt" -o domains.txt
``` ```
다음과 같은 명령어로 실행합니다. ```sh
# uv run run.py {domains.txt 시작 줄} {domains.txt 끝 줄} {--skh} {--no-download}
``` uv run run.py 1 100 --skh
uv run main.py
``` ```
Environment에는 다음과 같은 값이 들어갑니다. # Prompt 확장 가이드
```
ANONYMIZED_TELEMETRY=false
OPENAI_API_KEY=your_openai_api_key_here ## 1. 파일 생성
OPENAI_BASE_URL=https://models.github.ai/inference # 선택
OPENAI_MODEL=openai/gpt-4o-mini # Github Models가 아닐시 gpt-4.1
# 선택 `lib/llm/prompt` 폴더에서 fallback 폴더를 복사하여
PROXY_HOST=127.0.0.1
PROXY_PORT=8080 원하는 프로바이더를 추가해줍니다. `ex) lib/llm/prompt/Google/`
## 2. prompt.py 수정
Prompt에서 추가한 파일을 prompt.py에서 수정합니다.
만약 로그인 정보를 넣고 싶다면 Sensitive
`Log into example.com as user x_username with password x_password`
## 3. model.py
응답할 때 원하는 리턴 값을 `dict`로 받습니다.
## 4. \_\_init\_\_.py 수정
![image](https://github.com/user-attachments/assets/3aed2243-92d5-4359-8515-6d2f9bfa100b)
추가한 prompt에 따라 import합니다.
## 5. 사용 방법
```py
from lib.llm.prompt.fallback import prompt, model
``` ```
`OPENAI_BASE_URL`은 GitHub Models가 아닐시 비워둡니다. # 참고하면 좋을만한 것
`OPENAI_MODEL`은 GitHub Models가 아닐시 `openai/`를 제거합니다. - [ ] 일부 웹사이트는 사용자의 언어에 따라 OAuth 옵션을 바꾸기도 합니다.
- [ ] https://docs.browser-use.com/customize/custom-functions
`PROXY_HOST``PROXY_PORT`는 만약 Caido를 사용 중일 시 환경에 맞게 설정 후 설정합니다.

71
is-html-fast/.gitignore vendored Normal file
View file

@ -0,0 +1,71 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# General
.DS_Store
.AppleDouble
.LSOverride
Icon[]
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk

1581
is-html-fast/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

8
is-html-fast/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "is-html-fast"
version = "0.1.0"
edition = "2024"
[dependencies]
rayon = "1.10.0"
reqwest = { version = "0.12.19", features = ["blocking", "json"]}

2
is-html-fast/README.md Normal file
View file

@ -0,0 +1,2 @@
실제로 사용되진 않습니다.
일회용 코드입니다.

92
is-html-fast/src/main.rs Normal file
View file

@ -0,0 +1,92 @@
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::sync::atomic::{AtomicUsize, Ordering};
use rayon::prelude::*;
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE, USER_AGENT, ACCEPT, ACCEPT_LANGUAGE, ACCEPT_ENCODING, CONNECTION, UPGRADE_INSECURE_REQUESTS};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input_file = File::open("domains.txt")?;
let reader = BufReader::new(input_file);
let domains: Vec<String> = reader.lines().filter_map(Result::ok).collect();
let total_count = domains.len();
let counter = Arc::new(AtomicUsize::new(0));
let html_count = Arc::new(AtomicUsize::new(0));
let failed_count = Arc::new(AtomicUsize::new(0));
let non_html_count = Arc::new(AtomicUsize::new(0));
let output_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open("domains-filtered.txt")?;
let output = Arc::new(Mutex::new(output_file));
// 브라우저 헤더 세팅
let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0"));
headers.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"));
headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("ko,en-US;q=0.7,en;q=0.3"));
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
headers.insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1"));
let client = Arc::new(
Client::builder()
.timeout(Duration::from_secs(5))
.default_headers(headers)
.build()?,
);
domains.par_iter().for_each(|domain| {
let current = counter.fetch_add(1, Ordering::SeqCst) + 1;
let url = format!("https://{}", domain);
let response = client.get(&url).send();
match response {
Ok(resp) => {
if let Some(content_type) = resp.headers().get(CONTENT_TYPE) {
if let Ok(content_type_str) = content_type.to_str() {
if content_type_str.starts_with("text/html") {
if let Ok(mut file) = output.lock() {
writeln!(file, "{}", domain).ok();
}
html_count.fetch_add(1, Ordering::SeqCst);
println!("[{}/{}] ✅ HTML: {}", current, total_count, domain);
} else {
non_html_count.fetch_add(1, Ordering::SeqCst);
println!("[{}/{}] ❌ Not HTML: {} ({})", current, total_count, domain, content_type_str);
}
}
} else {
non_html_count.fetch_add(1, Ordering::SeqCst);
println!("[{}/{}] ❌ No Content-Type: {}", current, total_count, domain);
}
}
Err(_) => {
failed_count.fetch_add(1, Ordering::SeqCst);
println!("[{}/{}] ⚠️ Failed to connect: {}", current, total_count, domain);
}
}
});
// Final results
let html_final = html_count.load(Ordering::SeqCst);
let failed_final = failed_count.load(Ordering::SeqCst);
let non_html_final = non_html_count.load(Ordering::SeqCst);
println!("\n=== Final Results ===");
println!("📊 Total domains: {}", total_count);
println!("✅ HTML domains: {} ({:.1}%)", html_final, (html_final as f64 / total_count as f64) * 100.0);
println!("❌ Non-HTML domains: {} ({:.1}%)", non_html_final, (non_html_final as f64 / total_count as f64) * 100.0);
println!("⚠️ Failed connections: {} ({:.1}%)", failed_final, (failed_final as f64 / total_count as f64) * 100.0);
println!("💾 HTML domains saved to: domains-filtered.txt");
Ok(())
}

View file

@ -1,26 +0,0 @@
from browser_use.browser.context import BrowserContextConfig
from pathlib import Path
import os
from typing import Any
def browser_config_kwargs(lang: str = "en_US") -> dict[str, Any]:
browser_config_kwargs: dict[str, Any] = {
"keep_alive": True,
"browser_type": "chromium",
"headless": False,
"disable_security": True,
"extra_browser_args": [
"--disable-web-security",
"--disable-features=IsolateOrigins,site-per-process",
"--disable-popup-blocking",
f"--lang={lang}",
],
}
proxy_host = os.getenv("PROXY_HOST")
proxy_port = os.getenv("PROXY_PORT")
if proxy_host and proxy_port:
browser_config_kwargs["proxy"] = {"server": f"http://{proxy_host}:{proxy_port}"}
return browser_config_kwargs

152
main.py
View file

@ -1,152 +0,0 @@
import asyncio
import json
import os
from typing import List
from dotenv import load_dotenv
from pydantic import BaseModel
from langchain_google_genai import ChatGoogleGenerativeAI
from browser_use import Agent, Browser, BrowserConfig, Controller
from browser_use.browser.context import BrowserContext, BrowserContextConfig
from lib.browser_config import browser_config_kwargs
import csv
load_dotenv()
# Check environment variables
if os.getenv("GOOGLE_API_KEY") is None:
raise ValueError("OPENAI_API_KEY environment variable not set.")
if os.getenv("GOOGLE_MODEL") is None:
raise ValueError("OPENAI_MODEL environment variable not set.")
if os.getenv("GOOGLE_PLANNER_MODEL") is None:
raise ValueError("OPENAI_PLANNER_MODEL environment variable not set.")
# Configure browser
browser = Browser(
config=BrowserConfig(**browser_config_kwargs())
)
# Set browser context
context = BrowserContext(
browser=browser,
config=BrowserContextConfig(
wait_for_network_idle_page_load_time=3.0,
window_width=1600,
window_height=900,
locale='en-US',
highlight_elements=True,
viewport_expansion=500,
keep_alive=True
)
)
# Output model: each result is one OAuth entry with metadata
class OAuth(BaseModel):
provider: str
oauth_uri: str
class OAuthList(BaseModel):
oauth_providers: List[OAuth]
controller = Controller(output_model=OAuthList)
# Extended planner prompt
extend_planner_system_message = """
🎯 Your mission is to collect the real OAuth login URLs from the website.
1. First, go to the websites **login page**.
2. On the login page, look for OAuth login buttons. These usually say things like **"Continue with Google"**, **"Sign in with GitHub"**, etc.
3. **DO NOT collect or include "Passkey"** it is NOT an OAuth provider.
---
For EACH OAuth button you find:
- **Try opening it in a new tab**. If it redirects to an OAuth URL (e.g. `https://accounts.google.com/...`, `https://github.com/login/oauth/...`), copy that **exact final URL**.
- If it **doesnt open in a new tab**, **click the button** and wait for the redirect to happen.
- As soon as you see the redirected URL with **client_id**, **redirect_uri**, etc., copy that **entire URL without changing or hiding anything**.
- Then come back to the original tab (if needed) and continue with the next provider.
---
💡 **Do not guess** the OAuth URLs only collect them by actually interacting with the buttons.
🚫 **Do not redact or mask any part** of the URL, including `client_id`, `redirect_uri`, `state`, or any other parameters. Record them exactly as they appear.
Return a list of all OAuth providers and their **full raw redirect URLs** in this exact format:
```json
[
{
"provider": "Google",
"oauth_uri": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&...",
},
{
"provider": "GitHub",
"oauth_uri": "https://github.com/login/oauth/authorize?client_id=...&redirect_uri=...",
}
]
```
"""
# Main async runner
async def main():
url = "https://git.imnya.ng"
agent = Agent(
browser_context=context,
browser=browser,
task=f"Go to {url}, navigate to the login page, and collect the OAuth provider buttons and their login URLs. Ignore Passkey.",
llm=ChatGoogleGenerativeAI(model=os.getenv("GOOGLE_MODEL")),
planner_llm=ChatGoogleGenerativeAI(model=os.getenv("GOOGLE_PLANNER_MODEL")),
controller=controller,
extend_planner_system_message=extend_planner_system_message,
)
# Run the agent
response = await agent.run()
final_result = response.final_result()
if final_result is None:
raise ValueError("final_result() returned None")
data = json.loads(final_result)
try:
oauth_entries: List[OAuth] = [OAuth(**entry) for entry in data["oauth_providers"]]
except Exception as e:
raise ValueError(f"Failed to parse result: {e}\nRaw result: {final_result}")
# Clear terminal
#print("\033c", end="")
print("-" * 20)
print(f"Raw result: {final_result}")
print(f"🔗 Scanned URL: {url}\n")
print("🔐 Detected OAuth Providers and URLs:")
for entry in oauth_entries:
if "<" in entry.oauth_uri or "..." in entry.oauth_uri:
print(f"⚠️ WARNING: {entry.provider} URL may be masked or incomplete:\n{entry.oauth_uri}\n")
else:
print(f"- {entry.provider}: {entry.oauth_uri}")
# Save the result to CSV (append mode, so you can continue later)
# 이거 좀 이상한데 나중에 고쳐야 할듯 파일이 수정이 안됨
csv_file = "oauth_providers.csv"
file_exists = os.path.isfile(csv_file)
with open(csv_file, "a", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(["issuer", "provider", "oauth_uri"])
for entry in oauth_entries:
writer.writerow([url, entry.provider, entry.oauth_uri])
print(f"\n✅ OAuth providers saved to {csv_file}")
# Save the result to JSON
with open(f"oauth_providers_{url}.json", "w") as f:
json.dump(data, f, indent=2)
print(f"✅ OAuth providers saved to oauth_providers_{url}.json")
# Run it
asyncio.run(main())

View file

@ -5,5 +5,10 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"browser-use[memory]>=0.1.48", "black>=25.1.0",
"browser-use[memory]==0.5.3",
"chardet>=5.2.0",
"isort>=6.0.1",
"lmnr[all]>=0.6.10",
"patchright>=1.52.5",
] ]

View file

@ -1 +0,0 @@
uv run ./main.py

165
run.py Normal file
View file

@ -0,0 +1,165 @@
import argparse
import os
import signal
import subprocess
import sys
from datetime import datetime
import requests
#!/usr/bin/env python3
# ── 설정 부분 ──
PYTHON_SCRIPT = "./src/main.py"
DOMAIN_FILE = "./data/domains.txt"
# ─────────────
def download_domains():
"""도메인 파일 다운로드"""
try:
print("도메인 파일 다운로드 중...")
response = requests.get(
"https://f.imnya.ng/.whs/tp-domains/data/domains/latest.txt"
)
response.raise_for_status()
# 디렉토리가 없으면 생성
os.makedirs(os.path.dirname("./data"), exist_ok=True)
with open(DOMAIN_FILE, "w", encoding="utf-8") as f:
f.write(response.text)
print("도메인 파일 다운로드 완료")
except requests.RequestException as e:
print(f"도메인 파일 다운로드 실패: {e}")
sys.exit(1)
def run_script(start_line, end_line, skh_option):
"""Python 스크립트 실행"""
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{current_time}] Processing lines {start_line} to {end_line}...")
process = None
signal_handled = False
def signal_handler(sig, frame):
nonlocal signal_handled
if signal_handled:
return
signal_handled = True
print("\n🛑 종료 신호를 받았습니다. 정리 작업을 진행합니다...")
if process:
try:
# 자식 프로세스에 SIGTERM 전송
print("📤 서브프로세스에 종료 신호를 전달합니다...")
process.terminate()
# 5초간 대기
process.wait(timeout=5)
print("✅ 서브프로세스가 정상적으로 종료되었습니다.")
except subprocess.TimeoutExpired:
print("⚠️ 서브프로세스가 응답하지 않아 강제 종료합니다...")
process.kill()
try:
process.wait(timeout=3)
print("✅ 서브프로세스가 강제 종료되었습니다.")
except subprocess.TimeoutExpired:
print("❌ 서브프로세스 강제 종료 실패")
except Exception as e:
print(f"❌ 프로세스 종료 중 오류: {e}")
print("✅ 런처 종료 완료.")
sys.exit(0)
# 원래 시그널 핸들러 저장
original_sigint = signal.signal(signal.SIGINT, signal_handler)
original_sigterm = signal.signal(signal.SIGTERM, signal_handler)
try:
command = [
"uv",
"run",
PYTHON_SCRIPT,
"-f",
DOMAIN_FILE,
"-s",
str(start_line),
"-e",
str(end_line),
]
if skh_option:
command.append("--skip-html-check")
process = subprocess.Popen(command)
returncode = process.wait()
if returncode != 0:
print(f"❌ Python 스크립트가 오류 코드 {returncode}로 종료되었습니다.")
sys.exit(returncode)
except KeyboardInterrupt:
signal_handler(signal.SIGINT, None)
except Exception as e:
print(f"❌ 스크립트 실행 중 오류: {e}")
if process:
try:
process.terminate()
process.wait(timeout=3)
except subprocess.TimeoutExpired:
process.kill()
sys.exit(1)
finally:
# 시그널 핸들러 복원
signal.signal(signal.SIGINT, original_sigint)
signal.signal(signal.SIGTERM, original_sigterm)
def main():
parser = argparse.ArgumentParser(
description="도메인 처리 스크립트 실행기",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
사용 예시:
uv run run.py 10000 11000 # 10000~11000 라인 처리
uv run run.py 10000 11000 --skh # SKH 옵션 활성화
uv run run.py 10000 11000 --no-download # 다운로드 생략
""",
)
parser.add_argument("start_line", type=int, help="시작 라인 번호")
parser.add_argument("end_line", type=int, help="종료 라인 번호")
parser.add_argument("--skh", action="store_true", help="SKH 옵션 활성화")
parser.add_argument(
"--no-download", action="store_true", help="도메인 파일 다운로드 생략"
)
args = parser.parse_args()
# 라인 범위 검증
if args.start_line < 0 or args.end_line < 0:
print("라인 번호는 0 이상이어야 합니다.")
sys.exit(1)
if args.start_line > args.end_line:
print("시작 라인은 종료 라인보다 크거나 같아야 합니다.")
sys.exit(1)
# 도메인 파일 다운로드
if not args.no_download:
download_domains()
elif not os.path.exists(DOMAIN_FILE):
print(
f"도메인 파일({DOMAIN_FILE})이 존재하지 않습니다. --no-download 옵션을 제거하거나 파일을 준비해주세요."
)
sys.exit(1)
# 스크립트 실행
run_script(args.start_line, args.end_line, args.skh)
print("처리 완료.")
if __name__ == "__main__":
main()

263
setup.py Normal file
View file

@ -0,0 +1,263 @@
import os
import subprocess
import webbrowser
import asyncio
from browser_use import BrowserProfile, Agent
from browser_use.llm import ChatGoogle
from dotenv import load_dotenv
import threading
load_dotenv(verbose=True, override=True)
os.makedirs(os.path.dirname("./data"), exist_ok=True)
def create_file_from_example(target: str, example: str) -> bool:
if not os.path.exists(target):
if os.path.exists(example):
with (
open(example, "r", encoding="utf-8") as example_file,
open(target, "w", encoding="utf-8") as target_file,
):
target_file.write(example_file.read())
# os.startfile(target)
print(f"{target} 파일이 {example}에서 생성되었습니다.")
return True
else:
print(
f"⚠️ {example} 파일이 존재하지 않습니다. {target} 생성에 실패했습니다."
)
else:
print(f" {target} 파일이 이미 존재합니다.")
return False
def install_playwright_chrome():
print("\n🛠️ Playwright의 Chromium을 설치 중입니다...")
print("👉 이 작업은 시간이 걸릴 수 있습니다. 잠시 기다려주세요.")
try:
subprocess.run(["uv", "run", "playwright", "install", "chromium"], check=True)
print("✅ Playwright Chrome 설치 완료.")
except subprocess.CalledProcessError as e:
if "already" in e.stdout.decode():
print(" Chrome이 이미 설치되어 있습니다.")
else:
print(f"❌ Playwright 설치 실패: {e}")
print("\n")
def prompt_yes_no(message: str) -> bool:
print(message, end="")
return input().strip().lower() in ["y", "yes"]
def i_dont_like_windows():
# Windows인지 확인
if os.name != "nt":
return
else:
# run (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage").ACP
try:
result = subprocess.run(
[
"powershell",
"-Command",
'(Get-ItemProperty "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage").ACP',
],
capture_output=True,
text=True,
check=True,
)
acp = result.stdout.strip()
if acp == "65001":
print("현재 Active Code Page가 UTF-8로 설정되어 있습니다.")
return
else:
print("현재 Active Code Page가 UTF-8로 설정되어 있지 않습니다.")
except subprocess.CalledProcessError as e:
print(f"코드 페이지 확인 실패: {e}")
print("=======================================================")
print("\n⚠️ Windows에서는 인코딩 문제가 발생합니다.")
print("👉 엔터를 누르면 자동으로 intl.cpl이 열립니다.")
print('👉 자세한 내용은 README.md에서 "윈도우 인코딩 해결"을 참조해주세요.\n')
print(
"⚠️ 경고 : 이 작업은 윈도우에서 킹갓 대한민국의 프로그램들의 한글이 정상적으로 표시되지 않을 수 있습니다."
)
# Pause
input("계속하려면 Enter 키를 누르세요...")
webbrowser.open("intl.cpl")
print("👉 intl.cpl가 열렸습니다.\n")
print("👉 관리자 옵션 -> 시스템 로켈 변경")
print("👀 Beta: 세계 언어 지원을 위해 Unicode UTF-8 사용")
print("👉 이 설정을 변경한 후, 시스템을 재시작하세요.\n")
print("⚠️ 이 작업은 시스템 언어 설정을 변경하므로 주의가 필요합니다.\n")
print("=======================================================")
input("계속하려면 Enter 키를 누르세요...")
async def setup_user_data():
print("\n📂 사용자 데이터 디렉토리를 설정하시겠습니까?")
print("⚠️ 사용자 데이터 디렉토리는 브라우저의 프로필 데이터를 저장하는 곳입니다.")
print("✅ 이 작업은 Google API Key를 설정하고 나서 진행해야만합니다.")
if prompt_yes_no("\033[1m\033[33m선택하시려면 y를 입력하세요 (y/n):\033[0m "):
if os.getenv("GOOGLE_API_KEY") is None:
print(
"⚠️ Google API Key가 설정되어 있지 않습니다. 먼저 Google API Key를 설정해주세요."
)
return
print("======================================================")
llm = ChatGoogle(
model="gemini-2.0-flash",
)
initial_actions = [
{"go_to_url": {"url": "https://www.google.com", "new_tab": False}},
{"wait": {"seconds": 2147483647}},
]
agent = Agent(
task="Just Wait",
llm=llm,
use_vision=False,
initial_actions=initial_actions,
browser_profile=BrowserProfile(
disable_security=True,
# stealth=True,
headless=False,
device_scale_factor=1,
window_size={"width": 1600, "height": 900},
viewport={"width": 1600, "height": 900},
user_data_dir="./data/user_data",
args=[
# "--disable-features=Translate,PasswordManagerDefaultEnabled",
],
ignore_default_args=[
"--disable-datasaver-prompt",
"--disable-component-extensions-with-background-pages",
"--disable-prompt-on-repost",
"--safeBrowse-disable-auto-update",
"--install-autogenerated-theme=0,0,0",
"--disable-speech-synthesis-api",
"--ash-no-nudges",
"--test-type=gpu",
"--noerrdialogs",
"--disable-external-intent-requests",
"--disable-breakpad",
"--disable-backgrounding-occluded-windows",
"--export-tagged-pdf",
"--disable-focus-on-load",
"--suppress-message-center-popups",
"--disable-renderer-backgrounding",
"--hide-crash-restore-bubble",
"--disable-back-forward-cache",
"--allow-legacy-extension-manifests",
# "--disable-field-trial-config", # 왜 이걸 끄면 웹사이트가 압축된 형태로 보이는 진 모르곘음
"--disable-popup-blocking",
"--disable-background-networking",
"--no-first-run",
"--disable-blink-features=AutomationControlled",
"--password-store=basic",
"--enable-network-information-downlink-max",
"--allow-pre-commit-input",
"--enable-features=NetworkService,NetworkServiceInProcess",
"--metrics-recording-only",
"--silent-debugger-extension-api",
"--disable-features=AcceptCHFrame,AutoExpandDetailsElement,AvoidUnnecessaryBeforeUnloadCheckSync,CertificateTransparencyComponentUpdater,DestroyProfileOnBrowserClose,DialMediaRouteProvider,ExtensionManifestV2Disabled,GlobalMediaControls,HttpsUpgrades,ImprovedCookieControls,LazyFrameLoading,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutomationControlled,BackForwardCache,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,InterestFeedContentSuggestions,CalculateNativeWinOcclusion,HeavyAdPrivacyMitigations,PrivacySandboxSettings4,AutofillServerCommunication,CrashReporting,OverscrollHistoryNavigation,InfiniteSessionRestore,ExtensionDisableUnsupportedDeveloper",
"--disable-ipc-flooding-protection",
"--disable-hang-monitor",
"--disable-dev-shm-usage",
"--disable-client-side-phishing-detection",
"--log-level=2",
"--generate-pdf-document-outline",
"--disable-speech-api",
"--disable-search-engine-choice-screen",
"--no-service-autorun",
"--no-pings",
"--disable-component-update",
'--simulate-outdated-no-au="Tue, 31 Dec 2099 23:59:59 GMT"',
"--disable-background-timer-throttling",
"--use-mock-keychain",
"--disable-features=IsolateOrigins,site-per-process",
# 아래는 기존 예시에 있던 인자들입니다. 필요에 따라 유지하거나 제거하세요.
"--enable-automation",
"--disable-extensions",
"--hide-scrollbars",
],
),
)
print("======================================================\n")
print(
"👉 브라우저가 열립니다. 필요한 로그인을 완료한 후 엔터키를 눌러 다음 단계로 진행하세요."
)
input("계속하려면 Enter 키를 누르세요...\n")
print("======================================================")
# 브라우저를 백그라운드에서 시작
def run_agent():
asyncio.run(agent.run())
agent_thread = threading.Thread(target=run_agent)
agent_thread.daemon = True
agent_thread.start()
# 사용자가 'n'을 입력할 때까지 대기
while True:
user_input = input("").strip().lower()
if user_input == "":
agent.stop()
break
print("======================================================")
print("✅ 설정이 완료되었습니다.")
else:
print("🚫 설정이 취소되었습니다.")
print("======================================================")
print(
"⚠️ 이후에 USER_DATA_DIR을 설정하려면, .env 파일을 참고하여 USER_DATA_DIR을 설정하세요.\n"
)
def setup_sensitive():
print("\n🔐 Sensitive Data을 설정하시겠습니까?")
print("👉 이미 세션을 설정했다면, 이 작업은 **선택사항**입니다.")
print(
"⚠️ 민감 정보 파일은 오류를 유발하거나 문제가 될 수 있으므로 가급적 세션 사용을 권장합니다."
)
if prompt_yes_no("\033[1m\033[33m선택하시려면 y를 입력하세요 (y/n):\033[0m "):
print("======================================================")
print("👀 .sensitive.json 파일을 생성합니다.")
print("💾 Browser Use의 문서를 참조하여 수정을 수정해주세요.")
print("https://docs.browser-use.com/customize/sensitive-data")
create_file_from_example(".sensitive.json", ".sensitive.example.json")
print("======================================================")
print("✅ .sensitive.json 파일이 생성되었습니다.")
else:
print("🚫 .sensitive.json 생성이 취소되었습니다.")
print("======================================================")
print(
"⚠️ 이후에 민감 정보 파일을 설정하려면, .sensitive.example.json 파일을 참고하여 .sensitive.json 파일을 생성하세요.\n"
)
if __name__ == "__main__":
# 1. .env 생성
create_file_from_example(".env", ".env.example")
print("=====================================================")
# 2. Playwright용 Chrome 설치
install_playwright_chrome()
print("=====================================================")
# 3. Windows 인코딩 문제 해결
# i_dont_like_windows()
# print("=====================================================")
# 4. Setup User Data
asyncio.run(setup_user_data())
print("=====================================================")
# 5. .sensitive.json 생성
# setup_sensitive()
print("=====================================================")
print("🎉 초기 설정이 완료되었습니다! 이제 스크립트를 실행할 준비가 되었습니다.")

View file

@ -0,0 +1,6 @@
from lib.browser_use.agents import *
from lib.browser_use.func import *
from lib.browser_use.init_profile import *
from lib.browser_use.model import *
from lib.browser_use.scanner import *
from lib.browser_use.sensitive_data import *

View file

@ -0,0 +1,417 @@
import asyncio
import json
import os
import shutil
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
from browser_use import Agent, BrowserSession, Controller
from patchright.async_api import async_playwright as async_patchright
from lib.browser_use.init_profile import GetProfile
from lib.browser_use.sensitive_data import GetSensitiveData
from lib.llm import CreateChatGoogle, get_prompt
from lib.utils import config, logger
# Exponential backoff settings
INITIAL_BACKOFF = int(os.getenv("INITIAL_BACKOFF", "60")) # seconds
MAX_BACKOFF = int(os.getenv("MAX_BACKOFF", "600")) # seconds
@dataclass
class RetryTask:
"""재시도할 작업을 나타내는 클래스"""
task_type: str # "oauth_list" or "oauth_login"
url: str
oauth_provider: Optional[str] = None
retry_count: int = 0
next_retry_time: Optional[datetime] = None
max_retries: int = 5
# 전역 재시도 큐
retry_queue: list[RetryTask] = []
retry_queue_lock = asyncio.Lock()
async def add_to_retry_queue(task: RetryTask):
"""작업을 재시도 큐에 추가"""
async with retry_queue_lock:
# 중복 작업 확인
existing_task = None
for existing in retry_queue:
if (
existing.task_type == task.task_type
and existing.url == task.url
and existing.oauth_provider == task.oauth_provider
):
existing_task = existing
break
if existing_task:
# 기존 작업이 있으면 재시도 횟수 업데이트
existing_task.retry_count = task.retry_count
existing_task.next_retry_time = task.next_retry_time
print(
f"📝 기존 작업 업데이트: {task.task_type} - {task.url} (재시도: {task.retry_count})"
)
else:
# 새 작업 추가
retry_queue.append(task)
print(
f" 재시도 큐에 작업 추가: {task.task_type} - {task.url} (재시도: {task.retry_count})"
)
async def process_retry_queue():
"""재시도 큐 처리"""
async with retry_queue_lock:
now = datetime.now()
ready_tasks = []
for task in retry_queue[:]: # 복사본에서 반복
if task.next_retry_time and task.next_retry_time <= now:
ready_tasks.append(task)
retry_queue.remove(task)
if ready_tasks:
print(f"🔄 {len(ready_tasks)}개의 재시도 작업 처리 중...")
for task in ready_tasks:
try:
if task.task_type == "oauth_list":
result = await _extract_oauth_list_internal(task.url)
if result:
print(f"✅ 재시도 성공: OAuth 리스트 추출 - {task.url}")
else:
await _handle_retry_failure(task)
elif task.task_type == "oauth_login":
result = await _test_oauth_login_internal(
task.url, task.oauth_provider
)
if result:
print(
f"✅ 재시도 성공: {task.oauth_provider} 로그인 - {task.url}"
)
else:
await _handle_retry_failure(task)
except Exception as e:
print(f"❌ 재시도 중 에러: {e}")
await _handle_retry_failure(task)
async def _handle_retry_failure(task: RetryTask):
"""재시도 실패 처리"""
if task.retry_count < task.max_retries:
task.retry_count += 1
wait_time = min(INITIAL_BACKOFF * (2**task.retry_count), MAX_BACKOFF)
task.next_retry_time = datetime.now() + timedelta(seconds=wait_time)
await add_to_retry_queue(task)
print(f"{wait_time}초 후 재시도 예정: {task.task_type} - {task.url}")
else:
print(f"❌ 최대 재시도 횟수 초과: {task.task_type} - {task.url}")
logger(f"❌ 최대 재시도 횟수 초과: {task.task_type} - {task.url}")
async def get_retry_queue_status():
"""재시도 큐 상태 조회"""
async with retry_queue_lock:
return {
"queue_length": len(retry_queue),
"tasks": [
{
"task_type": task.task_type,
"url": task.url,
"oauth_provider": task.oauth_provider,
"retry_count": task.retry_count,
"next_retry_time": (
task.next_retry_time.isoformat()
if task.next_retry_time
else None
),
}
for task in retry_queue
],
}
async def _run_agent_with_retry(agent_config):
"""Agent 실행을 위한 내부 헬퍼 함수 (재시도 로직 포함)"""
agent = None
session = None
try_cnt = 0
url = agent_config["url"]
headless = os.getenv("HEADLESS", "False").lower() == "true"
while try_cnt < 3:
try:
Profile = await GetProfile(headless=headless)
session = BrowserSession(
playwright=(await async_patchright().start()),
browser_profile=Profile[0],
)
agent = Agent(browser_session=session, **agent_config["agent_params"])
response = await agent.run()
if any(
keyword in str(response)
for keyword in [
"429",
"resource_exhausted",
"resourceexhausted",
"quota",
"rate limit",
"too many requests",
"exceeded",
"limit reached",
]
):
print(f"⚠️ API 쿼터 에러 발생, 재시도 큐에 추가: {url}")
task = RetryTask(
task_type=agent_config.get("task_type", "unknown"),
url=url,
retry_count=try_cnt + 1,
next_retry_time=datetime.now() + timedelta(seconds=INITIAL_BACKOFF),
)
await add_to_retry_queue(task)
return None
# remove profile
print(Profile)
if Profile[1] and isinstance(Profile[1], str):
print(1)
shutil.rmtree(Profile[1], ignore_errors=True)
print(f"🗑️ 임시 프로필 디렉토리 삭제 완료: {Profile[1]}")
return response
except Exception as e:
# 일반 에러 처리
try_cnt += 1
if try_cnt >= 3:
error_msg = f"최대 재시도 횟수 초과."
logger(
f"{url} - {agent_config['log_context']} 실패: {error_msg}: {e}"
)
print(f"{url} - {agent_config['log_context']} 실패: {error_msg}")
return None
print(f"⚠️ 에러 발생: {e}. {try_cnt}번째 재시도 중...")
await asyncio.sleep(30)
continue
return None
async def _extract_oauth_list_internal(url: str):
"""OAuth 리스트 추출 내부 함수 (재시도 큐에서 사용)"""
target_url = url if url.startswith("http") else f"https://{url}"
print(f"🔎 OAuth 리스트 추출 시작: {target_url}")
prompt, model = get_prompt("auth")
agent_config = {
"url": target_url,
"log_context": "OAuth 리스트 추출",
"agent_params": {
"initial_actions": [{"go_to_url": {"url": target_url, 'new_tab': False}}],
"sensitive_data": GetSensitiveData(),
"task": (
"Navigate to the login page and identify all OAuth provider buttons (excluding Passkey). "
"DO NOT click any OAuth buttons or attempt to login. "
"Just find and list all available OAuth providers with their button texts or provider names. "
"Return a list of OAuth providers found on the login page."
),
"llm": CreateChatGoogle(config.GOOGLE_MODEL),
"planner_llm": (
CreateChatGoogle(config.GOOGLE_PLANNER_MODEL)
if config.GOOGLE_PLANNER_MODEL
and os.getenv("ENABLE_PLANNER_MODEL_OAUTH_LIST")
else None
),
"controller": Controller(
output_model=model if not isinstance(model, str) else None,
exclude_actions=["search_google", "unknown_action", "unkown"],
),
"extend_system_message": prompt,
"extend_planner_system_message": prompt,
},
}
response = await _run_agent_with_retry(agent_config)
if not response:
return []
final_result = response.final_result()
if not final_result:
print("OAuth 리스트 추출 결과가 없습니다.")
return []
try:
data = json.loads(final_result)
print(final_result)
oauth_providers = data.get("sso_list", [])
if not oauth_providers:
print("❌ OAuth 제공자가 없습니다.")
logger(f"{url} - OAuth 제공자 없음: {final_result}")
return []
print(f"✅ OAuth 제공자 추출 완료: {oauth_providers}")
return oauth_providers
except (json.JSONDecodeError, KeyError) as e:
print(f"❌ 결과 파싱 실패: {e}")
logger(f"{url} 결과 파싱 실패: {final_result}")
return []
async def extract_oauth_list(url: str):
"""첫 번째 Agent: 로그인 페이지를 찾고 OAuth 리스트만 추출"""
try:
return await _extract_oauth_list_internal(url)
except Exception as e:
error_str = str(e).lower()
if any(
keyword in error_str
for keyword in [
"429",
"resource_exhausted",
"resourceexhausted",
"quota",
"rate limit",
"too many requests",
"exceeded",
"limit reached",
]
):
print(f"⚠️ API 쿼터 에러 발생, 재시도 큐에 추가: {url}")
task = RetryTask(
task_type="oauth_list",
url=url,
retry_count=1,
next_retry_time=datetime.now() + timedelta(seconds=INITIAL_BACKOFF),
)
await add_to_retry_queue(task)
return []
else:
raise e
async def _test_oauth_login_internal(url: str, oauth_provider: str):
"""OAuth 로그인 테스트 내부 함수 (재시도 큐에서 사용)"""
target_url = url if url.startswith("http") else f"https://{url}"
print(f"🔐 {oauth_provider} 로그인 시작: {target_url}")
prompt, model = get_prompt(oauth_provider)
agent_config = {
"url": target_url,
"log_context": f"{oauth_provider} 로그인",
"agent_params": {
"initial_actions": [{"go_to_url": {"url": target_url, 'new_tab': False}}],
"sensitive_data": GetSensitiveData(),
"task": (
f"Navigate to the login page, find and click the {oauth_provider} OAuth button, "
f"then follow the complete OAuth login flow as far as possible with a real user account. "
f"Capture the final redirect URL after login completion. "
f"If login fails or encounters errors, report the issue. "
f"Focus only on {oauth_provider} - ignore other OAuth providers."
),
"llm": CreateChatGoogle(config.GOOGLE_MODEL),
"planner_llm": (
CreateChatGoogle(config.GOOGLE_PLANNER_MODEL)
if config.GOOGLE_PLANNER_MODEL
and os.getenv("ENABLE_PLANNER_MODEL_OAUTH_LOGIN")
else None
),
"controller": Controller(
output_model=model if not isinstance(model, str) else None,
exclude_actions=["search_google", "unknown_action", "unkown"],
),
"extend_system_message": prompt,
"extend_planner_system_message": prompt,
},
}
response = await _run_agent_with_retry(agent_config)
if response and response.final_result():
final_result = response.final_result()
try:
import json
result_data = json.loads(final_result)
status = result_data.get("status", "")
if status == "success":
print(f"{oauth_provider} 로그인 완료")
logger(f"{url} - {oauth_provider} 로그인 결과: {final_result}")
return True
else:
print(f"{oauth_provider} 로그인 실패: {status}")
logger(f"{url} - {oauth_provider} 로그인 실패: {final_result}")
return False
except (json.JSONDecodeError, KeyError):
print(f"{oauth_provider} 결과 파싱 실패")
return False
print(f"{oauth_provider} 로그인 실패")
return False
async def test_oauth_login(url: str, oauth_provider: str):
"""두 번째 Agent: 특정 OAuth 제공자로 로그인 시도"""
try:
return await _test_oauth_login_internal(url, oauth_provider)
except Exception as e:
error_str = str(e).lower()
if any(
keyword in error_str
for keyword in [
"429",
"resource_exhausted",
"resourceexhausted",
"quota",
"rate limit",
"too many requests",
"exceeded",
"limit reached",
]
):
print(f"⚠️ API 쿼터 에러 발생, 재시도 큐에 추가: {oauth_provider} - {url}")
task = RetryTask(
task_type="oauth_login",
url=url,
oauth_provider=oauth_provider,
retry_count=1,
next_retry_time=datetime.now() + timedelta(seconds=INITIAL_BACKOFF),
)
await add_to_retry_queue(task)
return False
else:
raise e
async def start_retry_queue_processor():
"""재시도 큐 처리기를 백그라운드에서 시작"""
async def queue_processor():
while True:
try:
await process_retry_queue()
await asyncio.sleep(30) # 30초마다 큐 확인
except Exception as e:
print(f"❌ 재시도 큐 처리 중 에러: {e}")
await asyncio.sleep(60) # 에러 발생 시 1분 대기
# 백그라운드 태스크로 실행
asyncio.create_task(queue_processor())
print("🔄 재시도 큐 처리기 시작됨")
# 모듈 로딩 시 자동으로 백그라운드 처리기 시작
# (실제 애플리케이션에서는 main 함수에서 호출하는 것이 좋음)
def init_retry_system():
"""재시도 시스템 초기화"""
print("🔧 재시도 시스템 초기화 중...")
# 이 함수는 메인 애플리케이션에서 호출해야 함

View file

@ -0,0 +1,110 @@
"""
브라우저 리소스 정리를 위한 모듈
"""
import os
import shutil
import asyncio
from pathlib import Path
async def cleanup_browser_resources(agent=None, session=None, user_data_dir=None):
"""브라우저 관련 리소스를 정리하는 함수"""
print("🔄 브라우저 리소스 정리를 시작합니다...")
# 에이전트 리소스 정리
if agent:
try:
print("<EFBFBD> 에이전트 리소스 정리 중...")
# 브라우저 종료 대기 시간 설정
await asyncio.wait_for(agent.close(), timeout=10.0)
print("✅ 에이전트 리소스 정리 완료.")
except asyncio.TimeoutError:
print("⚠️ 에이전트 종료 시간 초과. 강제 종료합니다.")
except Exception as e:
print(f"⚠️ 에이전트 리소스 정리 실패: {e}")
# 세션 리소스 정리
if session:
try:
print("🔄 세션 리소스 정리 중...")
await asyncio.wait_for(session.close(), timeout=5.0)
print("✅ 세션 리소스 정리 완료.")
except asyncio.TimeoutError:
print("⚠️ 세션 종료 시간 초과.")
except Exception as e:
print(f"⚠️ 세션 리소스 정리 실패: {e}")
# 임시 스토리지 상태 파일 삭제
storage_state_temp_path = Path("./data/storage_state_temp.json").resolve()
if storage_state_temp_path.exists():
try:
print(f"<EFBFBD> 임시 스토리지 상태 파일 삭제 중: {storage_state_temp_path}")
storage_state_temp_path.unlink()
print("✅ 임시 스토리지 상태 파일 삭제 완료.")
except Exception as e:
print(f"⚠️ 임시 스토리지 상태 파일 삭제 실패: {e}")
# 임시 사용자 데이터 디렉토리 정리
if user_data_dir and os.path.exists(user_data_dir):
try:
print(f"🗑️ 임시 사용자 데이터 디렉토리 삭제 중: {user_data_dir}")
await asyncio.sleep(0.5) # 브라우저가 완전히 종료될 시간 제공
shutil.rmtree(user_data_dir)
print("✅ 임시 사용자 데이터 디렉토리 삭제 완료.")
except Exception as e:
print(f"⚠️ 임시 사용자 데이터 디렉토리 삭제 실패: {e}")
# userdata.dump 파일에서 기록된 디렉토리 정리
log_file = "./data/userdata.dump"
if os.path.exists(log_file):
try:
with open(log_file, "r") as f:
tmp_user_data_dir = f.read().strip()
if tmp_user_data_dir and os.path.exists(tmp_user_data_dir):
print(f"🗑️ 기록된 임시 사용자 데이터 디렉토리 삭제 중: {tmp_user_data_dir}")
await asyncio.sleep(0.5) # 브라우저가 완전히 종료될 시간 제공
shutil.rmtree(tmp_user_data_dir)
print("✅ 기록된 임시 사용자 데이터 디렉토리 삭제 완료.")
os.remove(log_file)
print("✅ userdata.dump 파일 삭제 완료.")
except Exception as e:
print(f"⚠️ userdata.dump 관련 정리 실패: {e}")
print("✅ 브라우저 리소스 정리가 완료되었습니다.")
def cleanup_all_running_tasks():
"""실행 중인 모든 asyncio 태스크를 정리"""
try:
loop = asyncio.get_running_loop()
tasks = [task for task in asyncio.all_tasks(loop) if not task.done()]
if tasks:
print(f"🔄 {len(tasks)}개의 실행 중인 태스크를 정리합니다...")
for task in tasks:
task.cancel()
# 태스크들이 정리될 때까지 잠시 대기
async def wait_for_tasks():
await asyncio.gather(*tasks, return_exceptions=True)
asyncio.create_task(wait_for_tasks())
print("✅ 모든 태스크 정리 완료.")
except RuntimeError:
# 이벤트 루프가 실행 중이 아닌 경우
pass
except Exception as e:
print(f"⚠️ 태스크 정리 중 오류: {e}")
async def emergency_cleanup():
"""긴급 종료 시 최소한의 리소스 정리"""
print("🚨 긴급 리소스 정리 실행 중...")
# 모든 태스크 취소
cleanup_all_running_tasks()
# 기본 리소스 정리
await cleanup_browser_resources()
print("✅ 긴급 리소스 정리 완료.")

View file

@ -0,0 +1,54 @@
import json
import os
from pathlib import Path
from browser_use import BrowserProfile
from dotenv import load_dotenv
# Load environment variables
load_dotenv(override=True)
def setup_proxy():
"""Configure proxy settings from environment variables."""
proxy_host = os.getenv("PROXY_HOST")
proxy_port = os.getenv("PROXY_PORT")
if proxy_host and proxy_port:
proxy_url = f"http://{proxy_host}:{proxy_port}"
print(f"🔗 Using proxy: {proxy_host}:{proxy_port}")
return proxy_url
else:
print("🔗 No proxy configured, using direct connection.")
return None
def get_browser_args():
"""Get browser arguments for enhanced compatibility and security."""
return [
# Security and isolation
"--disable-web-security",
"--disable-site-isolation-trials",
"--disable-features=IsolateOrigins,site-per-process",
"--ignore-certificate-errors",
"--ignore-ssl-errors",
"--allow-running-insecure-content",
# Performance and rendering
"--disable-features=VizDisplayCompositor",
"--disable-dev-shm-usage",
# Popup and automation
"--disable-popup-blocking",
"--disable-blink-features=AutomationControlled",
# Browser behavior
"--no-first-run",
"--no-service-autorun",
"--no-default-browser-check",
"--password-store=basic",
"--use-mock-keychain",
# Extensions
"--disable-extensions-file-access-check",
"--disable-extensions-http-throttling",
"--disable-component-extensions-with-background-pages",
# Language
f"--lang={os.getenv('LANG', 'en_US')}",
]

View file

@ -0,0 +1,134 @@
import os
import shutil
import tempfile
from lib.browser_use.func import *
from lib.utils.config import USER_DATA_DIR
# Initialize configuration
proxy_url = setup_proxy()
async def GetProfile(headless=False):
"""브라우저 프로필을 생성하고 임시 사용자 데이터 디렉토리를 관리합니다."""
user_data_dir = None
tmp_user_data_dir = None
if USER_DATA_DIR and os.path.isdir(USER_DATA_DIR):
try:
tmp_user_data_dir = tempfile.mkdtemp(prefix="browser_use_")
print(f"🔧 기본 사용자 데이터 디렉토리: {USER_DATA_DIR}")
print(f"🔧 임시 사용자 데이터 디렉토리: {tmp_user_data_dir}")
log_file = os.path.join("./data", "userdata.dump")
if not os.path.exists("./data"):
os.makedirs("./data")
# 기존 로그 파일이 있다면 해당 디렉토리 정리
if os.path.exists(log_file):
try:
with open(log_file, "r") as f:
old_tmp_dir = f.read().strip()
if old_tmp_dir and os.path.exists(old_tmp_dir):
shutil.rmtree(old_tmp_dir)
print(f"🗑️ 이전 임시 디렉토리 정리: {old_tmp_dir}")
except Exception as e:
print(f"⚠️ 이전 임시 디렉토리 정리 실패: {e}")
os.remove(log_file)
# 새 임시 디렉토리 경로 로깅
with open(log_file, "w") as f:
f.write(tmp_user_data_dir)
# 사용자 데이터 디렉토리 복사
if os.path.exists(tmp_user_data_dir):
shutil.rmtree(tmp_user_data_dir)
shutil.copytree(
USER_DATA_DIR,
tmp_user_data_dir,
dirs_exist_ok=False,
ignore_dangling_symlinks=True,
)
user_data_dir = tmp_user_data_dir
print(f"✅ 사용자 데이터 디렉토리 복사 완료: {user_data_dir}")
except Exception as e:
print(f"❌ 사용자 데이터 디렉토리 복사 실패: {e}")
# 실패 시 임시 디렉토리 정리
if tmp_user_data_dir and os.path.exists(tmp_user_data_dir):
try:
shutil.rmtree(tmp_user_data_dir)
except Exception:
pass
tmp_user_data_dir = None
user_data_dir = None
print(proxy_url)
profile = BrowserProfile(
# Security settings
# disable_security=True,
# Display settings
headless=headless,
# Data persistence
user_data_dir=user_data_dir,
# Network settings
proxy={"server": proxy_url} if proxy_url else None,
# Additional arguments
args=[
"--proxy-server=" + proxy_url if proxy_url else "",
# "--disable-features=Translate,PasswordManagerDefaultEnabled",
],
ignore_default_args=[
# "--disable-datasaver-prompt",
# "--disable-component-extensions-with-background-pages",
# "--disable-prompt-on-repost",
# "--safeBrowse-disable-auto-update",
# "--install-autogenerated-theme=0,0,0",
# "--disable-speech-synthesis-api",
# "--ash-no-nudges",
# "--test-type=gpu",
# "--noerrdialogs",
# "--disable-external-intent-requests",
# "--disable-breakpad",
# "--disable-backgrounding-occluded-windows",
# "--export-tagged-pdf",
# "--disable-focus-on-load",
# "--suppress-message-center-popups",
# "--disable-renderer-backgrounding",
# "--hide-crash-restore-bubble",
# "--disable-back-forward-cache",
# "--allow-legacy-extension-manifests",
# # "--disable-field-trial-config", # 왜 이걸 끄면 웹사이트가 압축된 형태로 보이는 진 모르곘음
# "--disable-popup-blocking",
# "--disable-background-networking",
# "--no-first-run",
# "--disable-blink-features=AutomationControlled",
# "--password-store=basic",
# "--enable-network-information-downlink-max",
# "--allow-pre-commit-input",
# "--enable-features=NetworkService,NetworkServiceInProcess",
# "--metrics-recording-only",
# "--silent-debugger-extension-api",
# "--disable-features=AcceptCHFrame,AutoExpandDetailsElement,AvoidUnnecessaryBeforeUnloadCheckSync,CertificateTransparencyComponentUpdater,DestroyProfileOnBrowserClose,DialMediaRouteProvider,ExtensionManifestV2Disabled,GlobalMediaControls,HttpsUpgrades,ImprovedCookieControls,LazyFrameLoading,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutomationControlled,BackForwardCache,OptimizationHints,ProcessPerSiteUpToMainFrameThreshold,InterestFeedContentSuggestions,CalculateNativeWinOcclusion,HeavyAdPrivacyMitigations,PrivacySandboxSettings4,AutofillServerCommunication,CrashReporting,OverscrollHistoryNavigation,InfiniteSessionRestore,ExtensionDisableUnsupportedDeveloper",
# "--disable-ipc-flooding-protection",
# "--disable-hang-monitor",
# "--disable-dev-shm-usage",
# "--disable-client-side-phishing-detection",
# "--log-level=2",
# "--generate-pdf-document-outline",
# "--disable-speech-api",
# "--disable-search-engine-choice-screen",
# "--no-service-autorun",
# "--no-pings",
# "--disable-component-update",
# '--simulate-outdated-no-au="Tue, 31 Dec 2099 23:59:59 GMT"',
# "--disable-background-timer-throttling",
# "--use-mock-keychain",
# "--disable-features=IsolateOrigins,site-per-process",
# 아래는 기존 예시에 있던 인자들입니다. 필요에 따라 유지하거나 제거하세요.
"--enable-automation",
"--disable-extensions",
"--hide-scrollbars",
],
)
return [profile, tmp_user_data_dir] if tmp_user_data_dir else [profile]

View file

@ -0,0 +1,17 @@
from typing import List
from pydantic import BaseModel
# 출력 모델
class OAuth(BaseModel):
provider: str
oauth_uri: str = "" # OAuth 리스트 추출 단계에서는 URI가 없을 수 있음
class OAuthList(BaseModel):
oauth_providers: List[str] # 이제 문자열 배열로 변경
# 기존 모델 유지 (backward compatibility)
BaseModel = OAuthList

View file

@ -0,0 +1,191 @@
import asyncio
import csv
import os
from lib.browser_use.agents import (
extract_oauth_list,
get_retry_queue_status,
start_retry_queue_processor,
test_oauth_login,
)
from lib.browser_use.cleanup import cleanup_browser_resources
from lib.utils import is_html_url, notify_backend, read_lines_between
from lib.utils.progress import (
current_progress,
is_shutdown_requested,
load_progress,
progress_file,
save_progress,
)
async def scan_one_url(url: str, skip_html_check: bool = False):
"""URL 스캔 통합 함수: OAuth 리스트 추출 → 개별 OAuth 로그인 시도"""
target_url = url if url.startswith("http") else f"https://{url}"
print(f"🚀 스캔 시작: {target_url}")
# Backend에 스캔 시작을 알림
notify_backend(target_url)
# 1) URL이 HTML 페이지인지 확인
if not is_html_url(target_url) and not skip_html_check:
print(f"{target_url} 은(는) HTML이 아닙니다. 스킵합니다.")
return
# 1단계: OAuth 리스트 추출
oauth_entries = await extract_oauth_list(target_url)
if not oauth_entries:
print(f"{target_url}에서 OAuth 제공자를 찾을 수 없습니다.")
return
print("-" * 50)
print(f"🔗 스캔 URL: {url}")
print(f"🔐 발견된 OAuth 제공자들: {len(oauth_entries)}")
for entry in oauth_entries:
print(f" - {entry}")
print("-" * 50)
# CSV에 OAuth 리스트 저장
csv_file = "./data/oauth_providers.csv"
file_exists = os.path.isfile(csv_file)
with open(csv_file, "a", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(["issuer", "provider", "oauth_uri", "login_tested"])
for entry in oauth_entries:
writer.writerow([url, entry, "", "pending"])
# 2단계: 각 OAuth 제공자별로 개별 로그인 시도
for i, oauth_entry in enumerate(oauth_entries):
print(f"\n🔄 OAuth 로그인 테스트 {i+1}/{len(oauth_entries)}: {oauth_entry}")
# OAuth 간 대기 시간
if i > 0:
print("⏳ OAuth 테스트 간 대기 중 (30초)...")
await asyncio.sleep(30)
# 개별 OAuth 로그인 시도
success = await test_oauth_login(url, oauth_entry)
# 결과를 CSV에 업데이트 (간단하게 로그만 남김)
status = "success" if success else "failed"
print(f"📝 {oauth_entry} 로그인 결과: {status}")
async def main_loop(
filepath: str, start_line: int, end_line: int, skip_html_check: bool = False
):
"""지정된 URL 목록에 대해 스캔을 실행하는 메인 루프"""
try:
# 재시도 큐 처리기 시작
await start_retry_queue_processor()
target_list = read_lines_between(
filepath=filepath, start_line=start_line, end_line=end_line
)
# 전체 목록 길이를 저장 (재개 시에도 유지되어야 함)
total_count = len(target_list)
current_progress["total"] = total_count
current_progress["start_line"] = start_line
current_progress["current_index"] = 0
prev_progress = load_progress()
if prev_progress and prev_progress.get("start_line") == start_line:
print("📋 이전 진행 상황을 발견했습니다:")
print(
f" - 이전 완료: {prev_progress['current_index']}/{prev_progress['total']}"
)
print(f" - 마지막 처리: {prev_progress.get('current_url', 'N/A')}")
resume = input("이어서 진행하시겠습니까? (y/n): ").lower().strip()
if resume == "y":
start_index = prev_progress.get("current_index", 0)
current_progress["current_index"] = start_index
# 전체 개수는 원래 목록 길이로 유지
current_progress["total"] = total_count
target_list = target_list[start_index:]
print(f"{start_index}번째부터 재개합니다.")
for i, url in enumerate(target_list):
# 종료 요청 체크
if is_shutdown_requested():
print("🛑 종료 요청으로 인해 스캔을 중단합니다.")
break
# current_index는 전체 목록에서의 현재 위치를 나타냄
current_url_index = current_progress["current_index"]
current_progress["current_url"] = url
print(
f"\n🔄 Processing {current_url_index + 1}/{current_progress['total']}: {url}"
)
print(
f"📍 {os.path.basename(filepath)}{start_line + current_url_index}번째 줄"
)
# 재시도 큐 상태 확인 및 출력
retry_status = await get_retry_queue_status()
if retry_status["queue_length"] > 0:
print(f"⏳ 재시도 큐에 {retry_status['queue_length']}개 작업 대기 중")
if i > 0:
print("⏳ API 쿼터 보호를 위해 30초 대기 중...")
# 대기 중에도 종료 요청 체크
for _ in range(30):
if is_shutdown_requested():
print("🛑 대기 중 종료 요청으로 스캔을 중단합니다.")
return
await asyncio.sleep(1)
try:
await scan_one_url(url, skip_html_check=skip_html_check)
except Exception as e:
print(f"{url} 스캔 중 오류 발생: {e}")
continue
# 스캔 완료 후 재시도 큐 상태 확인
retry_status_after = await get_retry_queue_status()
if retry_status_after["queue_length"] > 0:
print(
f"📊 스캔 완료 후 재시도 큐 상태: {retry_status_after['queue_length']}개 작업 대기 중"
)
# 다음 URL로 진행
current_progress["current_index"] = current_url_index + 1
save_progress()
# 모든 URL 처리 완료 후 재시도 큐가 빌 때까지 대기
if not is_shutdown_requested():
print("\n🔄 모든 URL 처리 완료. 재시도 큐 처리 대기 중...")
while True:
if is_shutdown_requested():
print("🛑 재시도 큐 대기 중 종료 요청으로 중단합니다.")
break
retry_status = await get_retry_queue_status()
if retry_status["queue_length"] == 0:
break
print(
f"⏳ 재시도 큐에 {retry_status['queue_length']}개 작업 남음. 30초 후 다시 확인..."
)
# 대기 중에도 종료 요청 체크
for _ in range(30):
if is_shutdown_requested():
print("🛑 재시도 큐 대기 중 종료 요청으로 중단합니다.")
break
await asyncio.sleep(1)
if not is_shutdown_requested():
print(f"\n🎉 모든 스캔이 완료되었습니다! ({total_count}개 URL)")
print("🎉 재시도 큐도 모두 처리되었습니다!")
else:
print("\n🛑 종료 요청으로 인해 스캔이 중단되었습니다.")
else:
print("\n🛑 종료 요청으로 인해 스캔이 중단되었습니다.")
finally:
# 항상 리소스 정리
print("🔄 브라우저 리소스를 정리합니다...")
await cleanup_browser_resources()

View file

@ -0,0 +1,22 @@
# read json file .sensitive.json
import json
import os
def GetSensitiveData():
"""
Reads sensitive data from a .sensitive.json file in the current directory.
Returns:
dict: A dictionary containing the sensitive data.
"""
file_path = os.path.join(os.getcwd(), ".sensitive.json")
if not os.path.exists(file_path):
return None
with open(file_path, "r") as file:
sensitive_data = json.load(file)
return sensitive_data

2
src/lib/llm/__init__.py Normal file
View file

@ -0,0 +1,2 @@
from lib.llm.create import *
from lib.llm.prompt import *

19
src/lib/llm/create.py Normal file
View file

@ -0,0 +1,19 @@
from browser_use.llm import ChatGoogle
from dotenv import load_dotenv
# 환경 변수 로드 (GOOGLE_API_KEY 필요)
load_dotenv(override=True)
def CreateChatGoogle(model: str):
"""Browser Use용 Google 모델 생성"""
if model == "fallback":
print("⚠️ Fallback 모델을 사용합니다. Environment 변수를 확인하세요.")
print("⚠️ Model gemini-2.0-flash-lite를 사용합니다.")
model = "gemini-2.0-flash-lite"
return ChatGoogle(
model=model,
temperature=0.0
# Browser Use는 내부적으로 재시도 로직을 처리합니다
)

View file

@ -0,0 +1,46 @@
from typing import Type, Union
from pydantic import BaseModel
def get_prompt(type: str) -> tuple[str, Type[BaseModel]] | str:
"""
Prompt를 반환합니다.
:param type: 'auth' {Auth List} 또는 'google' {OAuth Provider}, 'meta' {OAuth Provider} 지정합니다.
:return: 해당하는 프롬프트 문자열 또는 (프롬프트, 모델) 튜플
"""
if type.lower() == "auth":
from lib.llm.prompt._get_oauth import model, prompt
return prompt, model
elif type.lower() in ["google", "google account"]:
from lib.llm.prompt.google import model, prompt
return prompt, model
elif type.lower() in ["microsoft", "microsoftonline"]:
from lib.llm.prompt.microsoft import model, prompt
return prompt, model
elif type.lower() in ["meta", "facebook"]:
from lib.llm.prompt.facebook import model, prompt
return prompt, model
elif type.lower() in ["apple"]:
from lib.llm.prompt.apple import model, prompt
return prompt, model
elif type.lower() in ["github"]:
from lib.llm.prompt.github import model, prompt
return prompt, model
else:
from lib.llm.prompt._fallback import model, prompt
return prompt, model

View file

@ -0,0 +1,2 @@
from lib.llm.prompt._fallback.model import model
from lib.llm.prompt._fallback.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,66 @@
from dotenv import load_dotenv
import os
load_dotenv()
google_id = os.getenv("GOOGLE_ID")
google_password = os.getenv("GOOGLE_PASSWORD")
naver_id = os.getenv("NAVER_ID")
naver_password = os.getenv("NAVER_PASSWORD")
facebook_id = os.getenv("FACEBOOK_ID")
facebook_password = os.getenv("FACEBOOK_PASSWORD")
github_id = os.getenv("GITHUB_ID")
github_password = os.getenv("GITHUB_PASSWORD")
microsoft_id = os.getenv("MICROSOFT_ID")
microsoft_password = os.getenv("MICROSOFT_PASSWORD")
# Extended planner prompt
prompt = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **SSO Login button**, following all steps strictly as described below.
Instructions:
1. If any cookie or privacy popups appear, dismiss or accept them.
2. Navigate through the site's UI to find the **login or sign-in page** (e.g., via buttons like "Log In", "Sign In", "Get Started").
3. Click the **SSO login button**.
4. Check if the user is **already logged and immediately redirected back to the original site** without showing a login screen.
- If so, treat the login as successful and return immediately.
5. If login proceeds without interruptions, complete the login and **immediately close the browser window**. Do not perform any further actions.
6. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Credentials to use for login:
- Google `{google_id}` / `{google_password}`
- Naver `{naver_id}` / `{naver_password}`
- GitHub `{github_id}` / `{github_password}`
- facebook `{facebook_id}` / `{facebook_password}`
- Microsoft `{microsoft_id}` / `{microsoft_password}`
Constraints:
- Do NOT use search engines or guess URLs.
- Do NOT proceed with login if:
- CAPTCHA or MFA appears
- If the user is already logged and redirected back automatically, stop there and report success.
- If the login page cannot be found, return "login_page_not_found".
- If the login button is not found, return "sso_not_found".
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Final Output:
Return the result in the following format only:
```json
{{
"msg": "login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt._get_oauth.model import model
from lib.llm.prompt._get_oauth.prompt import prompt

View file

@ -0,0 +1,7 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
url: str | None = None
sso_list: list[str] = [] # List of SSO providers found on the login page

View file

@ -0,0 +1,61 @@
prompt = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL. Follow the steps below strictly and return results only in the specified format.
You are NOT allowed to navigate to URLs that are not directly discoverable within the initial domain. Do NOT use search engines or guess external login URLs.
0. INITIAL BLOCK CHECK
- If the browser is blocked when trying to access the page due to firewall, CAPTCHA, regional restrictions, or other access denials immediately terminate the process and return the following JSON:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Do NOT proceed to further steps in this case.
1. LOGIN PAGE NAVIGATION
- Navigate only to a **client-side (non-enterprise)** login page within the provided domain.
- Do NOT rely on external tools, search engines, or links not directly found on the site.
- If a consent popup (e.g. for privacy/cookies) appears, you MUST dismiss or close it before proceeding.
- Since step 0 confirmed access, assume the page now loads properly.
2. SSO BUTTON IDENTIFICATION
- On the login page, look for the following social login (SSO) buttons:
- Google, GitHub, Facebook, Microsoft, Naver, Etc.
- Proceed only if it is clearly an **actual SSO button**.
- Exclude the following:
- Passkey-related buttons
- Username/password fields
- Email-based login
- Non-OAuth methods such as certificate or phone verification
3. RETURN FORMAT
- If the login page is successfully found, return:
```json
{
"msg": "Login page found",
"url": "https://example.com/login",
"sso_list": ["Google", "GitHub"]
}
```
- If the login page cannot be found, return:
```json
{
"msg": "Login page not found",
"url": "",
"sso_list": []
}
```
- If blocked (as in step 0), return:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.apple.model import model
from lib.llm.prompt.apple.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "apple_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,62 @@
import os
# Extended planner prompt
prompt = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **Apple SSO button**, following all steps strictly as described below.
Target: Find a login page inside this domain that allows "Sign in with Apple", and use it to complete login via Apple.
Instructions:
1. If any cookie or privacy popups appear, dismiss or accept them.
2. Navigate through the site's UI to find the **login or sign-in page** (e.g., via buttons like "Log In", "Sign In", "Get Started").
- Only follow links within the same domain.
3. On the login page, look for a clearly labeled **Apple SSO button** typically labeled as:
- "Continue with Apple"
- "Sign in with Apple"
- or a button with the Apple icon
4. Click the **Apple login button**.
- The Apple login flow MUST open in a **new browser tab** (not a new window or popup).
- If the login opens in a new **window** or **popup**, do NOT continue. Immediately stop and return the appropriate status.
5. Check if the user is **already logged in to Apple and immediately redirected back to the original site** without showing a Apple login screen.
- If so, treat the login as successful and return immediately.
6. If redirected to the Apple login page:
a. If a **CAPTCHA**, complete it.
b. If a **MFA prompt**, or a request for **ID/password entry** appears, do NOT proceed - Immediately stop and return the appropriate status.
- If a **"Continue"**, **"Trust"**, **"Authorize"**, or **"Allow"** button is displayed, click it to grant consent.
7. If login proceeds without interruptions, complete the login and **immediately close the browser window**. Do not perform any further actions.
8. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Credentials to use for Apple login:
- Email: {os.getenv("APPLE_EMAIL", "")}
- Password: {os.getenv("APPLE_PASSWORD", "")}
Constraints:
- Do NOT use search engines or guess URLs.
- Do NOT use autofill, saved sessions, or cookies.
- Do NOT proceed with login if:
- The login opens in a new window (only tabs are allowed)
- CAPTCHA or MFA appears
- ID/password input is required
- If the user is already logged in to Apple and redirected back automatically, stop there and report success.
- If the login page cannot be found, return "login_page_not_found".
- If the Apple login button is not found, return "sso_not_found".
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Final Output:
Return the result in the following format only:
```json
{{
"msg": "Apple login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "apple_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.facebook.model import model
from lib.llm.prompt.facebook.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "facebook_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,71 @@
import os
# Extended planner prompt
prompt = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **Facebook SSO button**, following all steps strictly as described below.
Target: Find a login page inside this domain that allows "Sign in with Facebook", and use it to complete login via Facebook.
Instructions:
1. **CRITICAL**: Wait 3 seconds for page to load and interactive elements to appear
2. If any cookie or privacy popups appear, dismiss or accept them by looking for buttons like "Accept All", "Accept", "Allow All"
3. If any promotional/discount popups appear, dismiss them by looking for "Close", "X", or "No Thanks" buttons
4. Navigate through the site's UI to find the **login or sign-in page**:
- Look for "My Account", "Log In", "Sign In", "Get Started" buttons
- Only follow links within the same domain
5. On the login page, look for a clearly labeled **Facebook SSO button** typically labeled as:
- "Continue with Facebook"
- "Sign in with Facebook"
- "Login with Facebook"
- or a button with the Facebook 'f' icon
6. Click the **Facebook login button**.
- The Facebook login flow will open in a new browser tab or window (this is normal OAuth behavior)
- Continue with the Facebook login process in the new tab/window
7. **Facebook Login Flow Handling:**
- **If already logged in**: You may see a consent screen with buttons like "Continue as [Name]", "Continue", "Allow" - CLICK these buttons to proceed
- **If CAPTCHA appears on registration form**: Try clicking "Create account" button multiple times (up to 5 attempts) as some CAPTCHAs may not block form submission
- **If form submission fails after 5 attempts**: Report "success" anyway if all fields were filled and Facebook OAuth was completed successfully
- **Registration completion is optional**: The main goal is Facebook OAuth completion, not site registration
8. **After Facebook OAuth consent/login**:
- Wait for redirection back to the original site
- **CRITICAL**: If redirected to a registration/signup form with fields like:
- "Last name", "Email address", "Password"
- "Create account" or "Sign up" button
- ANY additional information form
- **MUST complete the registration process:**
- Fill in required fields with appropriate test data:
- Last name: "TestUser"
- Email: Use the Facebook email or "test@example.com"
- Password: "TestPassword123!"
- Click "Create account", "Sign up", or "Complete registration" button
- Only after completing ALL steps, record the final URL as successful login
9. If all steps are completed successfully, close your browser window.
10. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Credentials to use for Facebook login (if needed):
- Email/Phone: {os.getenv("FACEBOOK_EMAIL", "")}
- Password: {os.getenv("FACEBOOK_PASSWORD", "")}
Constraints:
- Do NOT use search engines or guess URLs
- Do NOT use autofill, saved sessions, or cookies
- Do NOT proceed with login if CAPTCHA or MFA appears
- **ALWAYS complete any additional registration forms** after Facebook OAuth
- **Fill required fields** with test data if signup form appears
- **Only return "success" after completing ALL registration steps**
- If the login page cannot be found, return "login_page_not_found"
- If the Facebook login button is not found, return "sso_not_found"
Final Output:
Return the result in the following format only:
```json
{{
"msg": "Facebook login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "idpw_required" | "facebook_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.github.model import model
from lib.llm.prompt.github.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "github_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,81 @@
import os
# Extended planner prompt
prompt = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **GitHub SSO button**, following all steps strictly as described below.
Target: Find a login page inside this domain that allows "Sign in with GitHub", and use it to complete login via GitHub.
Instructions:
1. If any cookie or privacy popups appear, dismiss or accept them.
2. Navigate through the site's UI to find the **login or sign-in page** (e.g., via buttons like "Log In", "Sign In", "Get Started").
- Only follow links within the same domain.
- If a "Sign Up" or "Create Account" page appears instead, it is acceptable **as long as it includes a GitHub SSO option**.
3. On the login or sign-up page, look for a clearly labeled **GitHub SSO button** typically labeled as:
- "Continue with GitHub"
- "Sign in with GitHub"
- or a button with the GitHub logo
4. Click the **GitHub login button**.
- The GitHub login flow MUST open in a **new browser tab** (not a new window or popup).
- If the login opens in a new **window** or **popup**, do NOT continue. Immediately stop and return the appropriate status.
5. Check if the user is **already logged in to GitHub and immediately redirected back to the original site** without showing a GitHub login screen.
- If so, treat the login as successful and return immediately.
6. If redirected to the GitHub login page:
a. Wait for the username or email input field, then enter the email: {os.getenv("GITHUB_EMAIL", "")}
b. Click the "Continue" or "Next" button if present.
c. Enter the password: {os.getenv("GITHUB_PASSWORD", "")}
d. Click the "Sign in" button.
e. If a page appears asking to "Authorize" access for the application, click the "Authorize" button.
- GitHub may take a while to redirect after authorization, so please wait patiently.
- If a CAPTCHA, MFA prompt, or other interruption appears, do NOT proceed.
- If login fails due to incorrect credentials or authentication errors, treat as `"idpw_required"` and stop.
- Immediately stop and return the appropriate status.
7. If login proceeds without interruptions, wait for redirection back to the original site and record the final URL.
8. Close your browser window after the login is completed.
9. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Credentials to use for GitHub login:
- Email: {os.getenv("GITHUB_EMAIL", "")}
- Password: {os.getenv("GITHUB_PASSWORD", "")}
Constraints:
- Do NOT use search engines or guess URLs.
- Do NOT use autofill, saved sessions, or cookies.
- Do NOT proceed with login if:
- The login opens in a new window (only tabs are allowed)
- CAPTCHA or MFA appears
- ID/password input is required and cannot be autofilled
- If the user is already logged in to GitHub and redirected back automatically, stop there and report success.
- If the login page cannot be found, return "login_page_not_found".
- If the GitHub login button is not found, return "sso_not_found".
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Final Output:
Return the result in the following format only:
```json
{{
"msg": "GitHub login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "github_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.google.model import model
from lib.llm.prompt.google.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "google_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,63 @@
import os
# Extended planner prompt
prompt = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **Google SSO button**, following all steps strictly as described below.
Target: Find a login page inside this domain that allows "Sign in with Google", and use it to complete login via Google.
Instructions:
1. If any cookie or privacy popups appear, dismiss or accept them.
2. Navigate through the site's UI to find the **login or sign-in page** (e.g., via buttons like "Log In", "Sign In", "Get Started").
- Only follow links within the same domain.
3. On the login page, look for a clearly labeled **Google SSO button** typically labeled as:
- "Continue with Google"
- "Sign in with Google"
- or a button with the Google 'G' icon
4. Click the **Google login button**.
- The Google login flow MUST open in a **new browser tab** (not a new window or popup).
- If the login opens in a new **window** or **popup**, do NOT continue. Immediately stop and return the appropriate status.
5. Check if the user is **already logged in to Google and immediately redirected back to the original site** without showing a Google login screen.
- If so, treat the login as successful and return immediately.
6. If redirected to the Google login page:
a. Wait for the username or email input field, then enter the email: {os.getenv("GOOGLE_EMAIL", "")}
b. Click the "Continue" or "Next" button if present. (If still on the same page, reapeat step a)
c. Wait for the password input field, then enter the password: {os.getenv("GOOGLE_PASSWORD", "")}
d. Click the "Sign in" or "Next" button.
7. If login proceeds without interruptions, wait for redirection back to the original site and record the final URL.
8. Close your browser window after the login is completed.
9. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Credentials to use for Google login:
- Email: {os.getenv("GOOGLE_EMAIL", "")}
- Password: {os.getenv("GOOGLE_PASSWORD", "")}
Constraints:
- Do NOT use search engines or guess URLs.
- Do NOT use autofill, saved sessions, or cookies.
- Do NOT proceed with login if:
- The login opens in a new window (only tabs are allowed)
- CAPTCHA or MFA appears
- If the user is already logged in to Google and redirected back automatically, stop there and report success.
- If the login page cannot be found, return "login_page_not_found".
- If the Google login button is not found, return "sso_not_found".
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Final Output:
Return the result in the following format only:
```json
{{
"msg": "Google login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "google_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.microsoft.model import model
from lib.llm.prompt.microsoft.prompt import prompt

View file

@ -0,0 +1,9 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = (
None # "success", "mfa_required", "microsoft_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
)
final_url: str | None = None

View file

@ -0,0 +1,60 @@
import os
# This code snippet is used to generate a prompt for a web automation agent that performs Microsoft SSO login.
prompt = f"""
당신은 자동화 에이전트입니다.
당신의 임무는 주어진 도메인에 방문하여 아래에 엄격히 설명된 모든 단계를 따라 **Microsoft SSO 버튼** 통해 전체 로그인을 수행하는 것입니다.
목표: 도메인 내에서 "Microsoft로 로그인" 가능한 로그인 페이지를 찾아 Microsoft을 통해 로그인을 완료하세요.
지침:
1. 쿠키 또는 개인정보 팝업이 나타나면 닫거나 수락하세요.
2. 사이트의 UI를 탐색하여 **로그인 또는 로그인 페이지**(: "로그인", "Sign In", "Get Started" 같은 버튼) 찾으세요.
- 동일한 도메인 내의 링크만 따라가세요.
3. 로그인 페이지에서 명확하게 표시된 **Microsoft SSO 버튼** 찾으세요. 일반적으로 다음과 같이 표시됩니다:
- "Continue with Microsoft"
- "Sign in with Microsoft"
- or a button with the Microsoft logo (usually four squares)
4. **Microsoft 로그인 버튼** 클릭하세요.
- Microsoft 로그인 플로우는 반드시 ** 브라우저 **에서 열려야 합니다 ( 창이나 팝업이 아님).
- 로그인이 ****이나 **팝업**에서 열리면, 즉시 중단하고 적절한 상태를 반환하세요.
5. 사용자가 **이미 Microsoft에 로그인되어 있고 즉시 원래 사이트로 리디렉션**된다면,
- 경우 로그인이 성공한 것으로 간주하고 즉시 반환하세요.
6. Microsoft 로그인 페이지로 리디렉션된 경우:
- **CAPTCHA**, **MFA 프롬프트** 요청이 나타나면 진행하지 마세요.
- 즉시 중단하고 적절한 상태를 반환하세요.
7. 로그인에 방해가 없다면, 원래 사이트로 리디렉션될 때까지 기다리고 최종 URL을 기록하세요.
8. 로그인 되어있지 않으면 아래의 EMAIL과 PASSWORD를 사용하여 로그인하세요:
- Email: {os.getenv("MICROSOFT_EMAIL", "")}
- Password: {os.getenv("MICROSOFT_PASSWORD", "")}
9. 로그인 완료 브라우저 창을 닫으세요.
10. Login is considered successful if:
- You are redirected to a page that indicates successful login (e.g., a welcome page, dashboard, or account page).
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
제약 사항:
- 검색 엔진을 사용하거나 URL을 추측하지 마세요.
- 자동완성, 저장된 세션 또는 쿠키를 사용하지 마세요.
- 다음과 같은 경우 로그인 절차를 진행하지 마세요:
- 로그인이 창에서 열릴 (탭만 허용)
- CAPTCHA 또는 MFA가 나타날
- ID/비밀번호 입력이 필요하지만 자동입력이 불가한 경우
- 사용자가 이미 Microsoft에 로그인되어 자동으로 리디렉션된다면, 즉시 성공으로 보고 종료하세요.
- 로그인 페이지를 찾을 없으면 "login_page_not_found" 반환하세요.
- Microsoft 로그인 버튼을 찾을 없으면 "sso_not_found" 반환하세요.
- 회원가입 페이지와 같은 화면이 나타나면 성공적인 로그인으로 간주하고 즉시 종료하세요.
최종 출력:
다음 형식으로만 결과를 반환하세요:
```json
{{
"msg": "Microsoft login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "microsoft_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,7 @@
# export from show_info
from lib.utils.agent_info import *
from lib.utils.config import *
from lib.utils.data import *
from lib.utils.parsing.is_html import *
from lib.utils.parsing.read_txt import *

View file

@ -0,0 +1,64 @@
import os
from dotenv import load_dotenv
from lib.utils.config import (
BACKEND_URL,
GOOGLE_API_KEY,
GOOGLE_MODEL,
GOOGLE_PLANNER_MODEL,
)
load_dotenv(override=True)
def show_info():
print("🔧 환경 설정:")
print(browser_use_version())
print(f"🔗 Backend URL: {BACKEND_URL}")
print(
f"🔑 Google API Key: {'*' * (len(GOOGLE_API_KEY) - 4) + GOOGLE_API_KEY[-4:] if GOOGLE_API_KEY else None}"
)
print(f"🌐 Google Model: {GOOGLE_MODEL}")
print(f"🌐 Google Planner Model: {GOOGLE_PLANNER_MODEL}")
def browser_use_version():
try:
# run uv pip show browser-use
import subprocess
result = subprocess.run(
["uv", "pip", "show", "browser-use"],
capture_output=True,
text=True,
check=True,
)
print("📦 Browser Use 패키지 정보:")
return result.stdout.strip()
except ImportError:
return None
def env_cheker():
if GOOGLE_API_KEY is None:
raise ValueError("GOOGLE_API_KEY 환경변수가 설정되지 않았습니다.")
if GOOGLE_PLANNER_MODEL != None and (
not os.getenv("ENABLE_PLANNER_MODEL_OAUTH_LOGIN")
or not os.getenv("ENABLE_PLANNER_MODEL_OAUTH_LIST")
):
print(
"⚠️ GOOGLE_PLANNER_MODEL이 설정되어 있지만, ENABLE_PLANNER_MODEL_OAUTH_LOGIN 또는 ENABLE_PLANNER_MODEL_OAUTH_LIST가 활성화되지 않았습니다."
)
print(
"⚠️ Planner 모델을 사용하려면 .env 파일에서 ENABLE_PLANNER_MODEL_OAUTH_LOGIN과 ENABLE_PLANNER_MODEL_OAUTH_LIST를 true로 설정하세요."
)
print(
"‼️ 하지만 현재 Planner 모델을 사용하는 것이 권장되지 않습니다. 이 기능은 오작동을 일으킬 수 있습니다."
)
print("⚠️ 이 경고는 1초동안 정지합니다.")
# 이 경고는 1초동안 sleep
import time
time.sleep(1)

11
src/lib/utils/config.py Normal file
View file

@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv
load_dotenv(verbose=True, override=True)
BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:11081")
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.5-flash")
GOOGLE_PLANNER_MODEL = os.getenv("GOOGLE_PLANNER_MODEL")
USER_DATA_DIR = os.getenv("USER_DATA_DIR", "./data/user_data")

View file

@ -0,0 +1,2 @@
from lib.utils.data.backend_client import *
from lib.utils.data.logger import *

View file

@ -0,0 +1,23 @@
import requests
from lib.utils.config import BACKEND_URL
def notify_backend(target_url):
# Backend에 스캔 시작을 알림
try:
response = requests.post(
f"{BACKEND_URL}/start", params={"url": target_url}, timeout=5
)
if response.status_code == 200:
print(f"✅ Backend notified: {response.text}")
else:
print(f"⚠️ Backend notification failed: {response.status_code}")
except requests.exceptions.ConnectionError:
print(
f"⚠️ Backend server not available at {BACKEND_URL}. Continuing without notification."
)
except requests.exceptions.Timeout:
print(f"⚠️ Backend notification timed out. Continuing without notification.")
except Exception as e:
print(f"⚠️ Failed to notify backend: {e}")

View file

@ -0,0 +1,30 @@
from datetime import datetime
from pathlib import Path
# 미리 정해진 파일 경로
FILE_PATH = Path("data/log.txt")
def logger(msg: str) -> None:
try:
"""
msg 문자열을 파일 끝에 추가합니다.
- 파일이 없으면 새로 생성
- 디렉터리가 없으면 생성
"""
# 상위 디렉터리 생성 (이미 있으면 무시)
FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
# 현재 시각 구해서 포맷팅
now = datetime.now()
timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
# 메시지에 개행이 없으면 자동으로 붙이기
newline = "" if msg.endswith("\n") else "\n"
line = f"[{timestamp}] {msg}{newline}"
# 'a' 모드: 파일이 없으면 생성, 있으면 이어쓰기
with FILE_PATH.open(mode="a", encoding="utf-8") as f:
f.write(line)
except:
print(msg)

View file

@ -0,0 +1,38 @@
import requests
def is_html_url(url: str, timeout: float = 10.0) -> bool:
"""
주어진 URL에 HEAD 요청을 보내고, 응답 헤더의 Content-Type이 HTML인지 확인합니다.
- url: 검사할 URL 문자열
- timeout: 요청 타임아웃( 단위)
반환값:
- Content-Type이 'text/html' 시작하면 True, 그렇지 않으면 False
"""
try:
with requests.get(url, timeout=timeout, stream=True) as response:
# 응답 코드가 200번대가 아니면 False로 간주
if not response.ok:
return False
content_type = response.headers.get("Content-Type", "")
# Content-Type에 'text/html'이 포함되어 있으면 HTML로 간주
return content_type.lower().startswith("text/html")
except requests.RequestException:
return False
if __name__ == "__main__":
test_urls = [
"https://www.example.com",
"https://api.github.com", # JSON API라서 HTML이 아닐 확률이 높음
"https://raw.githubusercontent.com", # 텍스트 파일 등 다양한 타입
]
for url in test_urls:
if is_html_url(url):
print(f"[HTML] {url}")
else:
print(f"[Not HTML] {url}")

View file

@ -0,0 +1,38 @@
def read_lines_between(filepath: str, start_line: int, end_line: int) -> list[str]:
"""
파일에서 start_line번 줄부터 end_line번 줄까지 읽어와
줄을 요소로 갖는 리스트를 반환하는 함수.
Parameters:
----------
filepath : str
읽을 텍스트 파일의 경로
start_line : int
읽기 시작할 번호 (1부터 시작)
end_line : int
읽을 마지막 번호 (start_line <= end_line)
Returns:
-------
list[str]
줄을 문자열로 저장한 리스트.
파일에 해당 범위의 줄이 없으면 가능한 만큼만 반환.
"""
if start_line < 1 or end_line < start_line:
raise ValueError(
"start_line은 1 이상이어야 하며, end_line은 start_line 이상이어야 합니다."
)
selected_lines: list[str] = []
with open(filepath, "r", encoding="utf-8") as f:
for idx, line in enumerate(f, start=1):
if idx < start_line:
# 아직 읽기 시작 전
continue
if idx > end_line:
# 읽을 범위를 벗어났으므로 중단
break
# 줄 끝의 개행 문자를 제거하고 리스트에 추가
selected_lines.append(line.rstrip("\n"))
return selected_lines

119
src/lib/utils/progress.py Normal file
View file

@ -0,0 +1,119 @@
import json
import os, sys
import signal
import time
import threading
from pathlib import Path
# 진행 상황 추적을 위한 전역 변수
current_progress = {"current_index": 0, "total": 0, "current_url": "", "start_line": 0}
progress_file = Path("data/scan_progress.json")
# Ctrl+C 처리를 위한 전역 변수
ctrl_c_count = 0
last_ctrl_c_time = 0
shutdown_requested = False
shutdown_lock = threading.Lock()
def save_progress():
"""현재 진행 상황을 파일에 저장"""
progress_file.parent.mkdir(parents=True, exist_ok=True)
with open(progress_file, "w", encoding="utf-8") as f:
json.dump(current_progress, f, ensure_ascii=False, indent=2)
def load_progress():
"""이전 진행 상황을 파일에서 불러오기"""
if os.path.exists(progress_file):
try:
with open(progress_file, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
return None
def signal_handler(signum, frame):
"""Ctrl+C 시그널 핸들러 - browser-use pause 기능과 호환"""
global shutdown_requested, ctrl_c_count, last_ctrl_c_time
current_time = time.time()
with shutdown_lock:
# 연속된 Ctrl+C 감지 (2초 내에 두 번 누르면 강제 종료)
if current_time - last_ctrl_c_time < 2.0:
ctrl_c_count += 1
else:
ctrl_c_count = 1
last_ctrl_c_time = current_time
# 두 번째 Ctrl+C이거나 이미 종료 요청이 있었다면 강제 종료
if ctrl_c_count >= 2 or shutdown_requested:
print("\n⚡ 강제 종료합니다!")
import asyncio
try:
loop = asyncio.get_running_loop()
for task in asyncio.all_tasks(loop):
task.cancel()
except RuntimeError:
pass
os._exit(1)
# 첫 번째 Ctrl+C: 정상 종료 요청
shutdown_requested = True
print("\n" + "=" * 60)
print("🛑 종료 신호를 받았습니다!")
print(f"📊 현재 진행 상황:")
print(f" - 전체: {current_progress['total']}개 URL")
print(f" - 완료: {current_progress['current_index']}개 URL")
print(f" - 현재 처리 중: {current_progress['current_url']}")
if current_progress.get('start_line'):
print(f" - domains.txt의 {current_progress['start_line'] + current_progress['current_index']}번째 줄")
if current_progress["total"] > 0:
print(f" - 진행률: {current_progress['current_index']}/{current_progress['total']} ({current_progress['current_index']/current_progress['total']*100:.1f}%)")
print("=" * 60)
# 진행 상황 저장
save_progress()
print(f"💾 진행 상황이 {progress_file}에 저장되었습니다.")
print("다음에 같은 명령어로 실행하면 이어서 진행할 수 있습니다.")
print("<EFBFBD> 2초 내에 Ctrl+C를 다시 누르면 강제 종료됩니다.")
# 정상적인 종료를 위해 KeyboardInterrupt 발생
raise KeyboardInterrupt()
def is_shutdown_requested():
"""종료 요청 상태를 확인하는 함수"""
with shutdown_lock:
return shutdown_requested
def request_shutdown():
"""외부에서 종료를 요청할 수 있는 함수"""
global shutdown_requested
with shutdown_lock:
if not shutdown_requested:
shutdown_requested = True
print("\n🛑 종료가 요청되었습니다.")
print(f"📊 현재 진행 상황:")
print(f" - 전체: {current_progress['total']}개 URL")
print(f" - 완료: {current_progress['current_index']}개 URL")
print(f" - 현재 처리 중: {current_progress['current_url']}")
if current_progress.get('start_line'):
print(f" - domains.txt의 {current_progress['start_line'] + current_progress['current_index']}번째 줄")
if current_progress["total"] > 0:
print(f" - 진행률: {current_progress['current_index']}/{current_progress['total']} ({current_progress['current_index']/current_progress['total']*100:.1f}%)")
save_progress()
print(f"💾 진행 상황이 {progress_file}에 저장되었습니다.")
print("다음에 같은 명령어로 실행하면 이어서 진행할 수 있습니다.")
def setup_signal_handler():
"""시그널 핸들러 등록 - browser-use와의 호환성을 위해 비활성화"""
# browser-use 라이브러리가 자체적으로 Ctrl+C 처리를 하므로
# 우리의 signal handler는 등록하지 않음
pass

View file

@ -0,0 +1,116 @@
"""
종료 처리를 위한 개선된 모듈
browser-use의 pause 기능과 호환되도록 설계
"""
import json
import os
import signal
import time
import threading
import asyncio
from pathlib import Path
# 진행 상황 추적을 위한 전역 변수
current_progress = {"current_index": 0, "total": 0, "current_url": "", "start_line": 0}
progress_file = Path("data/scan_progress.json")
# 종료 관리를 위한 전역 변수
shutdown_requested = False
shutdown_lock = threading.Lock()
original_handler = None
def save_progress():
"""현재 진행 상황을 파일에 저장"""
progress_file.parent.mkdir(parents=True, exist_ok=True)
with open(progress_file, "w", encoding="utf-8") as f:
json.dump(current_progress, f, ensure_ascii=False, indent=2)
def load_progress():
"""이전 진행 상황을 파일에서 불러오기"""
if os.path.exists(progress_file):
try:
with open(progress_file, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
return None
def request_shutdown():
"""종료 요청 함수 - 외부에서 호출 가능"""
global shutdown_requested
with shutdown_lock:
if not shutdown_requested:
shutdown_requested = True
print("\n🛑 종료가 요청되었습니다. 현재 작업을 완료한 후 종료합니다...")
save_progress()
print(f"💾 진행 상황이 {progress_file}에 저장되었습니다.")
def is_shutdown_requested():
"""종료 요청 상태를 확인하는 함수"""
with shutdown_lock:
return shutdown_requested
def cleanup_signal_handler():
"""signal handler를 정리하고 원래 상태로 복원"""
global original_handler
if original_handler is not None:
signal.signal(signal.SIGINT, original_handler)
original_handler = None
def setup_minimal_signal_handler():
"""최소한의 signal handler만 설정 - browser-use와 충돌 방지"""
global original_handler
# 원래 핸들러 저장
original_handler = signal.signal(signal.SIGINT, signal.SIG_DFL)
def graceful_signal_handler(signum, frame):
"""우아한 종료를 위한 최소한의 signal handler"""
print("\n🛑 종료 신호를 받았습니다...")
save_progress()
print(f"💾 진행 상황이 {progress_file}에 저장되었습니다.")
# 원래 핸들러로 복원하고 신호를 다시 발생시킴
signal.signal(signal.SIGINT, original_handler)
os.kill(os.getpid(), signal.SIGINT)
signal.signal(signal.SIGINT, graceful_signal_handler)
class GracefulShutdown:
"""컨텍스트 매니저로 사용할 수 있는 우아한 종료 클래스"""
def __init__(self):
self.original_handler = None
def __enter__(self):
self.original_handler = signal.signal(signal.SIGINT, self._signal_handler)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.original_handler is not None:
signal.signal(signal.SIGINT, self.original_handler)
def _signal_handler(self, signum, frame):
"""내부 signal handler"""
request_shutdown()
# 원래 핸들러 복원 후 신호 재전송
signal.signal(signal.SIGINT, self.original_handler)
os.kill(os.getpid(), signal.SIGINT)
# 기존 함수들과의 호환성을 위한 별칭
def setup_signal_handler():
"""기존 코드와의 호환성을 위한 함수"""
pass # browser-use의 signal handler를 방해하지 않음
def signal_handler(signum, frame):
"""기존 코드와의 호환성을 위한 함수"""
request_shutdown()

121
src/main.py Normal file
View file

@ -0,0 +1,121 @@
import argparse
import asyncio
import os
import sys
from dotenv import load_dotenv
from lib.browser_use.scanner import main_loop
from lib.utils import env_cheker
from lib.utils.progress import progress_file, setup_signal_handler
def setup_environment():
"""환경 변수 로드 및 관련 라이브러리를 초기화합니다."""
# .env 파일 로드
load_dotenv(verbose=True, override=True)
# 환경 변수 체크
env_cheker()
# Laminar 초기화 (선택적)
if os.getenv("LMNR_PROJECT_API_KEY"):
try:
from lmnr import Laminar
Laminar.initialize(project_api_key=os.getenv("LMNR_PROJECT_API_KEY"))
except ImportError:
print("⚠️ Laminar 라이브러리가 설치되지 않았습니다. 관련 기능이 비활성화됩니다.")
else:
print("⚠️ LMNR_PROJECT_API_KEY 환경 변수가 설정되지 않았습니다. Laminar 기능이 비활성화됩니다.")
def parse_arguments():
"""커맨드 라인 인자를 파싱합니다."""
parser = argparse.ArgumentParser(
prog="domain_scanner",
description="도메인 목록 파일에서 지정한 줄 범위를 읽어 SSO 스캔을 수행합니다.",
)
parser.add_argument(
"-f",
"--file",
type=str,
required=True,
help="도메인 목록이 들어 있는 텍스트 파일 경로 (예: ./domains.txt)",
)
parser.add_argument(
"-s", "--start", type=int, required=True, help="읽기 시작 줄 번호 (1-based)"
)
parser.add_argument(
"-e", "--end", type=int, required=True, help="읽기 종료 줄 번호 (1-based)"
)
parser.add_argument(
"-skh",
"--skip-html-check",
action="store_true",
help="HTML 페이지 체크를 건너뛰고 모든 URL을 스캔합니다.",
)
return parser.parse_args()
def main():
"""애플리케이션 메인 진입점"""
setup_environment()
setup_signal_handler()
args = parse_arguments()
# read and remove user data path
log_file = os.path.join("./data", "userdata.dump")
if not os.path.exists("./data"):
os.makedirs("./data")
if os.path.exists(log_file):
with open(log_file, "r") as f:
tmp_user_data_dir = f.read().strip()
try:
import shutil
if os.path.exists(tmp_user_data_dir):
shutil.rmtree(tmp_user_data_dir)
print(f"🔧 이전 실행의 임시 사용자 데이터 디렉토리 {tmp_user_data_dir}를 삭제하였습니다.")
except (PermissionError, FileNotFoundError, OSError) as e:
print(f"⚠️ 임시 사용자 데이터 디렉토리 삭제 실패: {e}")
try:
os.remove(log_file)
except OSError:
pass
try:
asyncio.run(
main_loop(
filepath=args.file,
start_line=args.start,
end_line=args.end,
skip_html_check=args.skip_html_check,
)
)
except KeyboardInterrupt:
print("\n🛑 사용자에 의해 중단되었습니다.")
# 진행 상황 저장
from lib.utils.progress import save_progress, request_shutdown
request_shutdown()
print("✅ 정리 완료.")
sys.exit(0)
except Exception as e:
print(f"\n❌ 예상치 못한 오류가 발생했습니다: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
finally:
# 정상 종료 시에만 진행 상황 파일 삭제
from lib.utils.progress import is_shutdown_requested
if not is_shutdown_requested() and os.path.exists(progress_file):
try:
os.remove(progress_file)
print("✅ 진행 상황 파일이 삭제되었습니다.")
except OSError as e:
print(f"⚠️ 진행 상황 파일 삭제 실패: {e}", file=sys.stderr)
if __name__ == "__main__":
main()

52
temp.md
View file

@ -1,52 +0,0 @@
You are an AI model specialized in web crawling and analysis. Given a URI, perform the following tasks:
1. Navigate to the provided URI and locate the login page. If its not found, explore common auth-related pages like /login or /auth.
2. On the login page, identify all available social login buttons (OAuth-based) such as Google, GitHub, Facebook, etc.
3. Simulate clicking each social login button and follow the redirect to capture the full redirect URL (including query parameters).
4. From the redirect URL and parameters, extract:
- `client_id`
- `redirect_uri`
- `response_type`
- `scope`
5. Based on URL patterns, infer the OAuth method: Authorization Code, Implicit, PKCE, etc.
6. Return data in the following JSON format only:
```json
{
"oauths": [
{
"issue": "<site being tested, e.g., git.imnya.ng>",
"oauth_uri": "<original button href or URL triggered>"
}
]
}
````
7. If the login button says something like "Login with GitHub" or "Login with Google", follow the flow and use the **final redirect URL after clicking** as the value of `oauth_uri`.
**Examples:**
```json
{
"oauths": [
{
"issue": "git.imnya.ng",
"provider": "GitHub",
"client_id": "Iv1.xxxxx",
"redirect_uri": "https://git.imnya.ng/user/oauth2/callback",
"response_type": "code",
"scope": "read:user",
"oauth_uri": "https://github.com/login/oauth/authorize?client_id=Iv1.xxxxx&redirect_uri=https%3A%2F%2Fgit.imnya.ng%2Fuser%2Foauth2%2Fcallback&response_type=code&scope=read%3Auser"
}
]
}
```
**Constraints:**
* Simulate realistic interaction with buttons (e.g., clicking them to follow redirects).
* Ensure the output is strictly in the specified JSON format.
* Avoid any additional text or explanations outside the JSON response.
* If no OAuth logins are found, return an empty array.
* WebAuthn, PassKey is not OAuth, so do not include it in the results.

2077
uv.lock generated

File diff suppressed because it is too large Load diff