Skip to content

Commit 1e909c2

Browse files
committed
fix(auth): stabilize manual flows and tab focus
1 parent ff06171 commit 1e909c2

4 files changed

Lines changed: 61 additions & 41 deletions

File tree

.github/workflows/ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
name: CI
22

33
on:
4-
push:
5-
branches: [main]
64
pull_request:
75
branches: [main]
86
workflow_call:

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ npm cache clean --force && npm install -g izteamslots@latest
9494

9595
> Chrome и chromedriver скачиваются автоматически при первом запуске через SeleniumBase.
9696
97+
### Где лежат данные после `npm install -g`
98+
99+
При глобальной установке runtime-данные сохраняются не в папку пакета, а в пользовательскую директорию `~/.izteamslots`.
100+
101+
- `codex/`:
102+
- Windows: `C:\Users\<USER>\.izteamslots\codex`
103+
- macOS / Linux: `~/.izteamslots/codex`
104+
- `.env`:
105+
- Windows: `C:\Users\<USER>\.izteamslots\.env`
106+
- macOS / Linux: `~/.izteamslots/.env`
107+
- аккаунты и профили браузера:
108+
- Windows: `C:\Users\<USER>\.izteamslots\accounts`
109+
- macOS / Linux: `~/.izteamslots/accounts`
110+
111+
Если вы обновляете старую версию, `codex`-файлы могут временно лежать ещё и внутри директории пакета. В актуальной версии основным путём считается именно `~/.izteamslots`.
112+
97113
### Из исходников
98114

99115
```bash
@@ -237,4 +253,3 @@ flowchart TD
237253
- Проект хранит токены и браузерные профили локально на диске.
238254
- Не публикуйте папки `accounts/` и `codex/` в публичные репозитории.
239255
- Для стабильной работы перелогина у worker должен быть `openai_password`.
240-

backend/openai_web_auth.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,17 @@ def __init__(self, driver: Any, context: BrowserContext) -> None:
151151
@property
152152
def url(self) -> str:
153153
try:
154-
return self._driver.current_url
154+
current_url = self._driver.current_url
155+
if current_url.startswith("chrome://") or current_url in {"about:blank", "data:,"}:
156+
_activate_best_tab(self._driver)
157+
current_url = self._driver.current_url
158+
return current_url
155159
except Exception:
156160
return ""
157161

158162
def goto(self, url: str, wait_until: str | None = None, timeout: int | None = None) -> None:
159163
self._driver.get(url)
164+
_activate_best_tab(self._driver, [url, urllib.parse.urlparse(url).netloc])
160165
if wait_until in {"domcontentloaded", "load"}:
161166
t = (timeout or 30000) / 1000
162167
WebDriverWait(self._driver, t).until(
@@ -1184,20 +1189,13 @@ def oauth_login_manual(
11841189
_log(f"В браузере завершите вход вручную для {expected_email}.")
11851190
else:
11861191
_log("В браузере завершите вход вручную.")
1187-
_log("Можно ввести email, пароль или код подтверждения вручную прямо в открытом окне.")
1188-
_log("После успешного входа дождусь OAuth callback и сохраню сессию автоматически.")
1192+
_log("Я больше ничего не нажимаю: email, пароль, код и consent подтверждайте сами в браузере.")
1193+
_log("Как только произойдёт переход на localhost callback, я перехвачу код и сохраню сессию.")
11891194

1190-
_handle_consent_and_wait(page, _log, callback_wait_seconds=timeout_seconds)
11911195
auth_code = _wait_for_callback(holder, timeout=timeout_seconds)
11921196
_log("Authorization code получен")
11931197

11941198
session_result = _exchange_oauth_code(auth_code, redirect_uri, code_verifier)
1195-
_bootstrap_chatgpt_session(
1196-
page,
1197-
_log,
1198-
timeout_seconds=min(timeout_seconds, 300),
1199-
interactive=True,
1200-
)
12011199
return page, session_result
12021200
except Exception:
12031201
try:

backend/slot_orchestrator.py

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,13 @@ def _get_api(self, page: Page) -> ChatGPTWorkspaceAPI:
101101
raise RuntimeError("account_id/access_token не установлены — сначала login_admin()")
102102
return ChatGPTWorkspaceAPI(page, self.account_id, self.access_token)
103103

104-
def _finalize_admin_login(self, page: Page, session: dict[str, Any]) -> None:
104+
def _finalize_admin_login(
105+
self,
106+
page: Page,
107+
session: dict[str, Any],
108+
*,
109+
manual_web_flow: bool = False,
110+
) -> None:
105111
if not self._admin:
106112
raise RuntimeError(f"Админ {self.admin_email} не найден в AccountStore")
107113

@@ -115,38 +121,41 @@ def _finalize_admin_login(self, page: Page, session: dict[str, Any]) -> None:
115121
workspaces = get_workspaces(page, log=self._log)
116122
if workspaces:
117123
self._admin.workspaces = workspaces
118-
available_ids = {str(ws["workspace_id"]) for ws in workspaces if ws.get("workspace_id")}
119124
self._log(
120125
"Доступные workspace: "
121126
+ ", ".join(f"{ws.get('name', '?')} [{ws.get('workspace_id', '?')}]" for ws in workspaces)
122127
)
123128

124-
target_workspace_id = self._admin.workspace_id if self._admin.workspace_id in available_ids else None
125-
if self._admin.workspace_id and not target_workspace_id:
126-
self._log(
127-
f"[предупреждение] Workspace из OAuth ({self._admin.workspace_id}) не найден среди кнопок на странице"
128-
)
129-
130-
if not target_workspace_id:
131-
for ws in workspaces:
132-
name = str(ws.get("name", ""))
133-
workspace_id = str(ws.get("workspace_id", ""))
134-
if workspace_id and "Личная" not in name and "Personal" not in name:
135-
target_workspace_id = workspace_id
136-
break
137-
138-
if not target_workspace_id and workspaces:
139-
target_workspace_id = str(workspaces[0]["workspace_id"])
140-
141-
self._admin.workspace_id = target_workspace_id
142-
if target_workspace_id:
143-
select_workspace(page, target_workspace_id, log=self._log)
144-
self._admin.account_id = target_workspace_id
145-
if not ensure_chatgpt_web_session(page, self._log, timeout_seconds=45, open_home=True):
146-
raise RuntimeError(
147-
"Не удалось подтвердить web-сессию chatgpt.com после выбора workspace"
129+
if manual_web_flow:
130+
self._log("Ручной режим: workspace не выбираю автоматически.")
131+
else:
132+
available_ids = {str(ws["workspace_id"]) for ws in workspaces if ws.get("workspace_id")}
133+
target_workspace_id = self._admin.workspace_id if self._admin.workspace_id in available_ids else None
134+
if self._admin.workspace_id and not target_workspace_id:
135+
self._log(
136+
f"[предупреждение] Workspace из OAuth ({self._admin.workspace_id}) не найден среди кнопок на странице"
148137
)
149138

139+
if not target_workspace_id:
140+
for ws in workspaces:
141+
name = str(ws.get("name", ""))
142+
workspace_id = str(ws.get("workspace_id", ""))
143+
if workspace_id and "Личная" not in name and "Personal" not in name:
144+
target_workspace_id = workspace_id
145+
break
146+
147+
if not target_workspace_id and workspaces:
148+
target_workspace_id = str(workspaces[0]["workspace_id"])
149+
150+
self._admin.workspace_id = target_workspace_id
151+
if target_workspace_id:
152+
select_workspace(page, target_workspace_id, log=self._log)
153+
self._admin.account_id = target_workspace_id
154+
if not ensure_chatgpt_web_session(page, self._log, timeout_seconds=45, open_home=True):
155+
raise RuntimeError(
156+
"Не удалось подтвердить web-сессию chatgpt.com после выбора workspace"
157+
)
158+
150159
close_browser(page, log=self._log)
151160
self._admin.last_login = datetime.now(timezone.utc).isoformat()
152161
self.store.update_admin(self._admin)
@@ -161,7 +170,7 @@ def _finalize_admin_login(self, page: Page, session: dict[str, Any]) -> None:
161170
self._log("Админ авторизован!")
162171

163172
def finalize_admin_session(self, page: Page, session: dict[str, Any]) -> None:
164-
self._finalize_admin_login(page, session)
173+
self._finalize_admin_login(page, session, manual_web_flow=True)
165174

166175
def login_admin(self) -> None:
167176
"""Логин админа через OAuth PKCE — получаем все токены (access, id, refresh)."""
@@ -198,7 +207,7 @@ def login_admin_manual(self) -> None:
198207
)
199208
if session.get("email") and session["email"] != self._admin.email:
200209
self._log(f"[предупреждение] Вошли как {session['email']}, а не как {self._admin.email}")
201-
self._finalize_admin_login(page, session)
210+
self._finalize_admin_login(page, session, manual_web_flow=True)
202211

203212
def create_slots(self, count: int) -> list[WorkerAccount]:
204213
"""Создать N временных почт."""

0 commit comments

Comments
 (0)