izteamslots 1.6.1 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +59 -197
- package/backend/account_store.py +3 -0
- package/backend/chatgpt_workspace_api.py +32 -10
- package/backend/codex_switcher.py +570 -0
- package/backend/dto.py +27 -0
- package/backend/file_logger.py +3 -3
- package/backend/jobs.py +28 -28
- package/backend/openai_web_auth.py +4 -15
- package/backend/rpc_server.py +31 -3
- package/backend/slot_orchestrator.py +4 -0
- package/backend/ui_facade.py +39 -6
- package/docs/architecture.md +69 -0
- package/docs/providers.md +52 -0
- package/docs/troubleshooting.md +64 -0
- package/package.json +1 -1
- package/requirements.txt +2 -2
- package/tests/test_account_store.py +26 -0
- package/tests/test_codex_switcher.py +475 -0
- package/tests/test_jobs.py +32 -0
- package/tests/test_rpc_protocol.py +23 -0
- package/tests/test_workspace_api.py +90 -0
- package/ui/src/menus/mainMenus.ts +79 -2
- package/ui/src/menus/types.ts +28 -0
- package/ui/src/screens/MainScreen.ts +130 -2
- package/ui/tests/mainMenus.test.ts +163 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## [1.7.1](https://github.com/izzzzzi/izTeamSlots/compare/v1.7.0...v1.7.1) (2026-04-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **codex-switcher:** eliminate redundant _load_accounts calls in pick_first_ready ([1024e95](https://github.com/izzzzzi/izTeamSlots/commit/1024e9582137aeaaea43484c94002455160ed37d))
|
|
7
|
+
* deduplicate _decode_jwt_payload — single canonical implementation in codex_switcher ([5e579fe](https://github.com/izzzzzi/izTeamSlots/commit/5e579fe44caccf381d47d43484c56b4242589f87))
|
|
8
|
+
* **jobs:** move thread assignment inside lock to prevent race condition ([40a3be2](https://github.com/izzzzzi/izTeamSlots/commit/40a3be2ce0937ae6eeef72c508c820d5de2d1029))
|
|
9
|
+
* **logger:** use UTC timestamps consistent with rest of codebase ([302dfa1](https://github.com/izzzzzi/izTeamSlots/commit/302dfa1c2c5348fcab11c5606c5eb916ff0b5d26))
|
|
10
|
+
* remove redundant Mailbox creation in relogin_worker_email ([883af9b](https://github.com/izzzzzi/izTeamSlots/commit/883af9bd7650c887056885514329b38d6bd5acab))
|
|
11
|
+
* **security:** set chmod 0600 on meta.json and index.json files ([08c27af](https://github.com/izzzzzi/izTeamSlots/commit/08c27af9b330fae9f9a6316fd0fc7c6d38813506))
|
|
12
|
+
* **security:** strengthen API key masking — show at most 4 chars for long keys ([545ac15](https://github.com/izzzzzi/izTeamSlots/commit/545ac15c4505fa94aff59ef5f1ede2b11a8819c2))
|
|
13
|
+
* **workspace-api:** add pagination to get_members and get_pending_invites ([48229d2](https://github.com/izzzzzi/izTeamSlots/commit/48229d209a20593be9693df18f504dba9ee18ddc))
|
|
14
|
+
|
|
15
|
+
# [1.7.0](https://github.com/izzzzzi/izTeamSlots/compare/v1.6.1...v1.7.0) (2026-03-07)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* remove unused variable to satisfy ruff F841 ([e5cfb77](https://github.com/izzzzzi/izTeamSlots/commit/e5cfb773e4449037aa96f7f940c22cd7eb78ecdc))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* add Codex account switcher with auto-rotation ([70c8953](https://github.com/izzzzzi/izTeamSlots/commit/70c8953a6cde92127a17a5ea220bd8476bbc3447))
|
|
26
|
+
|
|
1
27
|
## [1.6.1](https://github.com/izzzzzi/izTeamSlots/compare/v1.6.0...v1.6.1) (2026-03-07)
|
|
2
28
|
|
|
3
29
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# izTeamSlots
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Локальный менеджер ChatGPT Team слотов: админы, инвайты, регистрация, перелогин и Codex-сессии**
|
|
6
6
|
|
|
7
7
|
[](https://github.com/izzzzzi/izTeamSlots/actions/workflows/ci.yml)
|
|
8
8
|
[](https://github.com/izzzzzi/izTeamSlots/actions/workflows/release.yml)
|
|
@@ -18,111 +18,54 @@
|
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Обзор
|
|
24
|
-
|
|
25
|
-
**izTeamSlots** — локальное приложение с архитектурой:
|
|
26
|
-
|
|
27
|
-
- **Python backend** (бизнес-логика, браузерная автоматизация, storage);
|
|
28
|
-
- **TypeScript OpenTUI frontend** (терминальный интерфейс оператора).
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Возможности
|
|
33
|
-
|
|
34
|
-
- Управление админами: добавить, перелогинить, удалить, открыть браузерный профиль.
|
|
35
|
-
- Пайплайн слотов: `создать почту -> инвайт -> регистрация -> OAuth-логин`.
|
|
36
|
-
- Перелогин слотов: одного выбранного или всех по очереди.
|
|
37
|
-
- Codex-файлы: авто-сохранение `codex-<email>-Team.json` в аккаунт и в `./codex/`.
|
|
38
|
-
- Doctor-проверка: валидация/восстановление файловой структуры аккаунтов при старте.
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Структура проекта
|
|
42
|
-
|
|
43
|
-
```text
|
|
44
|
-
izTeamSlots/
|
|
45
|
-
├── bin/ # CLI-бинарники
|
|
46
|
-
│ └── izteamslots.mjs # Кроссплатформенный entrypoint (Node.js)
|
|
47
|
-
├── scripts/ # Установочные скрипты
|
|
48
|
-
│ ├── setup.mjs # Диспетчер: выбирает .sh или .cmd
|
|
49
|
-
│ ├── setup.sh # Unix: uv + venv + Bun
|
|
50
|
-
│ └── setup.cmd # Windows: uv + venv + Bun
|
|
51
|
-
├── backend/ # Весь Python-бэкенд
|
|
52
|
-
│ ├── __init__.py # PROJECT_ROOT
|
|
53
|
-
│ ├── __main__.py # python -m backend
|
|
54
|
-
│ ├── account_store.py # CRUD аккаунтов (JSON storage)
|
|
55
|
-
│ ├── mail/ # Почтовые провайдеры (плагины)
|
|
56
|
-
│ │ ├── __init__.py # Фабрики: create_provider, create_slot_provider
|
|
57
|
-
│ │ ├── base.py # MailProvider ABC, Mailbox, Mail, Inbox
|
|
58
|
-
│ │ ├── boomlify.py # Boomlify Temp Mail API
|
|
59
|
-
│ │ ├── trickads.py # trickadsagencyltd.com temp mail
|
|
60
|
-
│ │ └── imap.py # Любой IMAP-сервер
|
|
61
|
-
│ ├── openai_web_auth.py # Браузерная автоматизация (SeleniumBase)
|
|
62
|
-
│ ├── chatgpt_workspace_api.py # ChatGPT Workspace API через браузер
|
|
63
|
-
│ ├── slot_orchestrator.py # Оркестратор пайплайна слотов
|
|
64
|
-
│ ├── dto.py # DTO для UI
|
|
65
|
-
│ ├── file_logger.py # Логирование в файл
|
|
66
|
-
│ ├── jobs.py # Задачи в потоках
|
|
67
|
-
│ ├── rpc_protocol.py # JSON-RPC протокол
|
|
68
|
-
│ ├── rpc_server.py # RPC-сервер (stdio)
|
|
69
|
-
│ └── ui_facade.py # Фасад между RPC и бизнес-логикой
|
|
70
|
-
├── ui/ # TypeScript TUI (OpenTUI)
|
|
71
|
-
│ ├── package.json
|
|
72
|
-
│ └── src/
|
|
73
|
-
│ ├── main.ts # Entrypoint UI
|
|
74
|
-
│ ├── screens/MainScreen.ts # Главный экран
|
|
75
|
-
│ ├── transport/stdioClient.ts # JSON-RPC клиент
|
|
76
|
-
│ └── menus/ # Меню, таблицы, форматирование
|
|
77
|
-
├── requirements.txt # Python-зависимости
|
|
78
|
-
├── ruff.toml # Конфиг линтера Python
|
|
79
|
-
├── LICENSE # MIT
|
|
80
|
-
└── README.md
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## Установка
|
|
86
|
-
|
|
87
|
-
### npm (рекомендуется)
|
|
21
|
+
## Quick Start
|
|
88
22
|
|
|
89
23
|
```bash
|
|
90
|
-
npm
|
|
24
|
+
npm install -g izteamslots@latest
|
|
25
|
+
izteamslots
|
|
91
26
|
```
|
|
92
27
|
|
|
93
|
-
|
|
28
|
+
Дальше:
|
|
29
|
+
1. Откройте `Настройки` и задайте почтовый провайдер / API-ключ.
|
|
30
|
+
2. Добавьте админа через ручной вход в браузере.
|
|
31
|
+
3. Запустите создание слотов.
|
|
94
32
|
|
|
95
|
-
|
|
33
|
+
## Что умеет
|
|
96
34
|
|
|
97
|
-
|
|
35
|
+
- Добавление и ручной перелогин админов.
|
|
36
|
+
- Создание слотов: `почта -> инвайт -> регистрация -> OAuth`.
|
|
37
|
+
- Перелогин одного слота или всех сразу.
|
|
38
|
+
- Сохранение `codex-<email>-Team.json`.
|
|
39
|
+
- Логи, локальные browser profiles и doctor-проверка.
|
|
40
|
+
- Синхронизация workspace с локальными слотами.
|
|
41
|
+
- Свитч Codex-аккаунтов: мониторинг usage, авто-ротация auth.json при достижении лимита.
|
|
98
42
|
|
|
99
|
-
|
|
43
|
+
## Ограничения
|
|
100
44
|
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
- macOS / Linux: `~/.izteamslots/.env`
|
|
107
|
-
- аккаунты и профили браузера:
|
|
108
|
-
- Windows: `C:\Users\<USER>\.izteamslots\accounts`
|
|
109
|
-
- macOS / Linux: `~/.izteamslots/accounts`
|
|
45
|
+
- Вход админа сейчас поддерживается только в ручном режиме.
|
|
46
|
+
- Проект зависит от текущего web UI OpenAI / ChatGPT.
|
|
47
|
+
- Браузерная автоматизация может ломаться после изменений на стороне сайта.
|
|
48
|
+
- Токены, профили браузера и `codex` хранятся локально.
|
|
49
|
+
- Основные платформы: macOS и Windows.
|
|
110
50
|
|
|
111
|
-
|
|
51
|
+
## Где лежат данные
|
|
112
52
|
|
|
113
|
-
|
|
53
|
+
При глобальной установке данные сохраняются в `~/.izteamslots`.
|
|
114
54
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
55
|
+
- `accounts/` — аккаунты и browser profiles
|
|
56
|
+
- `codex/` — сохранённые codex-файлы
|
|
57
|
+
- `logs/` — app/job logs
|
|
58
|
+
- `.env` — локальные настройки
|
|
120
59
|
|
|
121
|
-
|
|
60
|
+
Примеры:
|
|
61
|
+
- Windows: `C:\Users\<USER>\.izteamslots`
|
|
62
|
+
- macOS / Linux: `~/.izteamslots`
|
|
63
|
+
|
|
64
|
+
Если вы обновляете старую версию, `codex`-файлы могут временно лежать ещё и внутри директории пакета. В актуальной версии основным путём считается именно `~/.izteamslots`.
|
|
122
65
|
|
|
123
|
-
|
|
66
|
+
## Настройка
|
|
124
67
|
|
|
125
|
-
|
|
68
|
+
Настройки можно задать через меню `Настройки` внутри приложения или вручную через `~/.izteamslots/.env`.
|
|
126
69
|
|
|
127
70
|
```bash
|
|
128
71
|
# Linux / macOS
|
|
@@ -134,122 +77,41 @@ mkdir "$env:USERPROFILE\.izteamslots" -Force
|
|
|
134
77
|
echo "BOOMLIFY_API_KEY=your_api_key" > "$env:USERPROFILE\.izteamslots\.env"
|
|
135
78
|
```
|
|
136
79
|
|
|
137
|
-
Конфиг загружается в порядке приоритета: `~/.izteamslots/.env` > `./.env` > встроенный.
|
|
138
|
-
|
|
139
80
|
| Переменная | По умолчанию | Описание |
|
|
140
81
|
|-----------|:------------:|----------|
|
|
141
|
-
| `BOOMLIFY_API_KEY` | — | API-ключ Boomlify
|
|
142
|
-
| `BOOMLIFY_DOMAIN` | авто | Домен
|
|
82
|
+
| `BOOMLIFY_API_KEY` | — | API-ключ Boomlify |
|
|
83
|
+
| `BOOMLIFY_DOMAIN` | авто | Домен временных почт |
|
|
143
84
|
| `BOOMLIFY_TIME` | `permanent` | Время жизни ящика |
|
|
144
85
|
| `SLOT_MAIL_PROVIDER` | `boomlify` | Провайдер почты для слотов |
|
|
145
86
|
| `MAIL_PROVIDER` | `trickads` | Провайдер почты для админов |
|
|
87
|
+
| `CODEX_SWITCHER_ENABLED` | `false` | Включить автосвитч Codex-аккаунтов |
|
|
88
|
+
| `CODEX_SWITCHER_INTERVAL_MINUTES` | `15` | Интервал фоновой проверки usage (минуты) |
|
|
146
89
|
|
|
147
|
-
##
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
izteamslots
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Из исходников: `npm start`
|
|
154
|
-
|
|
155
|
-
Это запустит OpenTUI frontend через **Bun**, который поднимет Python RPC backend (`python -m backend`) по `stdio`.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## Архитектура
|
|
90
|
+
## Свитч Codex-аккаунтов
|
|
160
91
|
|
|
161
|
-
|
|
162
|
-
flowchart TD
|
|
163
|
-
A[izteamslots CLI] -->|bun| B[ui/src/main.ts]
|
|
164
|
-
B --> C[MainScreen.ts]
|
|
165
|
-
C --> D[StdioRpcClient]
|
|
166
|
-
D -->|spawns + stdio JSON-RPC| E[python -m backend]
|
|
167
|
-
E --> F[RPCServer]
|
|
168
|
-
F --> G[UIFacade]
|
|
169
|
-
G --> H[AccountStore]
|
|
170
|
-
G --> I[SlotManager]
|
|
171
|
-
I --> J[openai_web_auth]
|
|
172
|
-
I --> K[chatgpt_workspace_api]
|
|
173
|
-
I --> L[Mail Providers]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Почтовые провайдеры (плагины)
|
|
177
|
-
|
|
178
|
-
Система почты построена на плагинах — абстрактный класс `MailProvider` и конкретные реализации.
|
|
179
|
-
|
|
180
|
-
```mermaid
|
|
181
|
-
flowchart TD
|
|
182
|
-
MP[MailProvider — абстрактный класс]
|
|
183
|
-
MP --> B[BoomlifyProvider]
|
|
184
|
-
MP --> T[TrickAdsProvider]
|
|
185
|
-
MP --> I[IMAPProvider]
|
|
186
|
-
|
|
187
|
-
CF[create_provider] -->|MAIL_PROVIDER env| MP
|
|
188
|
-
CSP[create_slot_provider] -->|SLOT_MAIL_PROVIDER env| MP
|
|
189
|
-
CPM[create_provider_for_mailbox] -->|по формату password| MP
|
|
190
|
-
|
|
191
|
-
style MP fill:#1e3a5f,color:#fff
|
|
192
|
-
style B fill:#1a4731,color:#fff
|
|
193
|
-
style T fill:#1a4731,color:#fff
|
|
194
|
-
style I fill:#1a4731,color:#fff
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Встроенные провайдеры
|
|
198
|
-
|
|
199
|
-
| Провайдер | Модуль | Описание | Env-переменные |
|
|
200
|
-
|-----------|--------|----------|----------------|
|
|
201
|
-
| `boomlify` | `mail/boomlify.py` | Boomlify Temp Mail API (по умолчанию для слотов) | `BOOMLIFY_API_KEY`, `BOOMLIFY_DOMAIN`, `BOOMLIFY_TIME` |
|
|
202
|
-
| `trickads` | `mail/trickads.py` | trickadsagencyltd.com temp mail (по умолчанию для админов) | — |
|
|
203
|
-
| `imap` | `mail/imap.py` | Любой IMAP-сервер | `IMAP_HOST`, `IMAP_PORT`, `IMAP_SSL`, `IMAP_FOLDER` |
|
|
204
|
-
|
|
205
|
-
### Фабричные функции
|
|
92
|
+
Встроенный механизм ротации Codex-аккаунтов. Все codex-файлы из пула (`<DATA_ROOT>/codex`) отображаются в разделе **Свитч аккаунтов** главного меню.
|
|
206
93
|
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
94
|
+
Что доступно:
|
|
95
|
+
- **Таблица аккаунтов** — active-статус, primary usage %, reset time, состояние токена.
|
|
96
|
+
- **Ручное обновление** — запросить usage по всем аккаунтам.
|
|
97
|
+
- **Ручное переключение** — выбрать аккаунт и записать его в `auth.json`.
|
|
98
|
+
- **Первый готовый** — автоматически выбрать первый аккаунт без near-limit.
|
|
99
|
+
- **Автосвитч** — фоновый шедулер проверяет usage и переключает `auth.json`, если `primary_used_percent >= 90%`. Включается через настройку `CODEX_SWITCHER_ENABLED`.
|
|
100
|
+
- **Авто-рефреш токенов** — если access token истекает, обновляется через OAuth.
|
|
210
101
|
|
|
211
|
-
|
|
102
|
+
Путь к `auth.json` определяется через `CODEX_HOME` или `~/.codex/auth.json`.
|
|
212
103
|
|
|
213
|
-
|
|
104
|
+
## Почтовые провайдеры
|
|
214
105
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class MyProvider(MailProvider):
|
|
219
|
-
name = "my_provider"
|
|
220
|
-
|
|
221
|
-
def generate(self) -> Mailbox:
|
|
222
|
-
# создать временный ящик, вернуть Mailbox(email, password)
|
|
223
|
-
...
|
|
224
|
-
|
|
225
|
-
def inbox(self, mailbox: Mailbox) -> Inbox:
|
|
226
|
-
# получить письма, вернуть Inbox(email, messages=[Mail(...), ...])
|
|
227
|
-
...
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
## Пайплайн слотов
|
|
233
|
-
|
|
234
|
-
```mermaid
|
|
235
|
-
flowchart TD
|
|
236
|
-
S1[Создать временную почту] --> S2[Отправить инвайт через API]
|
|
237
|
-
S2 --> S3[Ожидать письмо с инвайт-ссылкой]
|
|
238
|
-
S3 --> S4[Регистрация по ссылке в браузере]
|
|
239
|
-
S4 --> S5[Ввод email / пароль / код подтверждения]
|
|
240
|
-
S5 --> S6[Заполнение имени и даты рождения]
|
|
241
|
-
S6 --> S7[Закрыть браузер регистрации]
|
|
242
|
-
S7 --> S8[OAuth PKCE логин]
|
|
243
|
-
S8 --> S9[Сохранить access_token и codex-файл]
|
|
244
|
-
|
|
245
|
-
style S1 fill:#1e3a5f,color:#fff
|
|
246
|
-
style S9 fill:#1a4731,color:#fff
|
|
247
|
-
```
|
|
106
|
+
- В проект уже встроены `boomlify`, `trickads` и `imap`.
|
|
107
|
+
- Можно добавлять собственные почтовые провайдеры.
|
|
108
|
+
- Для этого нужно реализовать `MailProvider` и зарегистрировать его в backend.
|
|
248
109
|
|
|
249
|
-
|
|
110
|
+
Подробности: [docs/providers.md](./docs/providers.md)
|
|
250
111
|
|
|
251
|
-
##
|
|
112
|
+
## Документация
|
|
252
113
|
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
114
|
+
- [docs/providers.md](./docs/providers.md) — встроенные и кастомные почтовые провайдеры
|
|
115
|
+
- [docs/architecture.md](./docs/architecture.md) — структура проекта, архитектура и пайплайн слотов
|
|
116
|
+
- [docs/troubleshooting.md](./docs/troubleshooting.md) — частые проблемы и способы диагностики
|
|
117
|
+
- [CONTRIBUTING.md](./CONTRIBUTING.md) — вклад в проект, проверки и тесты
|
package/backend/account_store.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
import shutil
|
|
5
6
|
import threading
|
|
6
7
|
import uuid
|
|
@@ -77,6 +78,8 @@ class AccountStore:
|
|
|
77
78
|
tmp = path.with_suffix(".tmp")
|
|
78
79
|
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
79
80
|
tmp.replace(path)
|
|
81
|
+
if os.name != "nt":
|
|
82
|
+
os.chmod(path, 0o600)
|
|
80
83
|
|
|
81
84
|
# --- Admin CRUD ---
|
|
82
85
|
|
|
@@ -91,20 +91,42 @@ class ChatGPTWorkspaceAPI:
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
def get_pending_invites(self) -> list[dict]:
|
|
94
|
-
"""Получить список ожидающих
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
|
|
94
|
+
"""Получить список ожидающих инвайтов (с пагинацией)."""
|
|
95
|
+
return self._paginate(
|
|
96
|
+
f"/backend-api/accounts/{self.account_id}/invites",
|
|
97
|
+
items_key="invites",
|
|
98
98
|
)
|
|
99
|
-
return data.get("invites", [])
|
|
100
99
|
|
|
101
100
|
def get_members(self) -> list[dict]:
|
|
102
|
-
"""Получить список участников workspace."""
|
|
103
|
-
|
|
104
|
-
"
|
|
105
|
-
|
|
101
|
+
"""Получить список участников workspace (с пагинацией)."""
|
|
102
|
+
return self._paginate(
|
|
103
|
+
f"/backend-api/accounts/{self.account_id}/users",
|
|
104
|
+
items_key="items",
|
|
105
|
+
fallback_key="users",
|
|
106
106
|
)
|
|
107
|
-
|
|
107
|
+
|
|
108
|
+
def _paginate(
|
|
109
|
+
self,
|
|
110
|
+
path: str,
|
|
111
|
+
items_key: str,
|
|
112
|
+
fallback_key: str | None = None,
|
|
113
|
+
page_size: int = 100,
|
|
114
|
+
max_pages: int = 20,
|
|
115
|
+
) -> list[dict]:
|
|
116
|
+
"""Fetch all pages from a paginated endpoint."""
|
|
117
|
+
all_items: list[dict] = []
|
|
118
|
+
for page_num in range(max_pages):
|
|
119
|
+
offset = page_num * page_size
|
|
120
|
+
data = self._request("GET", f"{path}?offset={offset}&limit={page_size}")
|
|
121
|
+
items = data.get(items_key) or (data.get(fallback_key, []) if fallback_key else [])
|
|
122
|
+
all_items.extend(items)
|
|
123
|
+
if not data.get("has_more") and len(items) >= page_size:
|
|
124
|
+
continue
|
|
125
|
+
if len(items) < page_size:
|
|
126
|
+
break
|
|
127
|
+
if data.get("has_more") is False:
|
|
128
|
+
break
|
|
129
|
+
return all_items
|
|
108
130
|
|
|
109
131
|
def delete_member(self, user_id: str) -> dict:
|
|
110
132
|
"""Удалить участника из workspace по user_id."""
|