izteamslots 1.3.2 → 1.4.0
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 +15 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +12 -10
- package/backend/mail/__init__.py +53 -30
- package/backend/mail/base.py +1 -0
- package/backend/mail/boomlify.py +1 -0
- package/package.json +1 -1
- package/ui/src/screens/MainScreen.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [1.4.0](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.3...v1.4.0) (2026-03-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* auto-discovery for mail providers — no manual registration needed ([ca54650](https://github.com/izzzzzi/izTeamSlots/commit/ca54650a42696b714c79cf328da5d58b5e2d1f60))
|
|
7
|
+
|
|
8
|
+
## [1.3.3](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.2...v1.3.3) (2026-03-06)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* quick start recommends configuring mail API key first ([c852b9d](https://github.com/izzzzzi/izTeamSlots/commit/c852b9d63d9e5867b897791e8fd11e712e1a515f))
|
|
14
|
+
* remove extra leading spaces from hero logo ([ddb43bb](https://github.com/izzzzzi/izTeamSlots/commit/ddb43bbc6ba0075736ac7acba433032b45dbfc37))
|
|
15
|
+
|
|
1
16
|
## [1.3.2](https://github.com/izzzzzi/izTeamSlots/compare/v1.3.1...v1.3.2) (2026-03-06)
|
|
2
17
|
|
|
3
18
|
|
package/CONTRIBUTING.md
CHANGED
package/README.md
CHANGED
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
```text
|
|
46
46
|
izTeamSlots/
|
|
47
47
|
├── app.py # Entrypoint: запускает UI
|
|
48
|
+
├── bin/ # CLI-бинарники
|
|
49
|
+
│ └── izteamslots.mjs # Кроссплатформенный entrypoint (Node.js)
|
|
50
|
+
├── scripts/ # Установочные скрипты
|
|
51
|
+
│ ├── setup.mjs # Диспетчер: выбирает .sh или .cmd
|
|
52
|
+
│ ├── setup.sh # Unix: uv + venv + Bun
|
|
53
|
+
│ └── setup.cmd # Windows: uv + venv + Bun
|
|
48
54
|
├── backend/ # Весь Python-бэкенд
|
|
49
55
|
│ ├── __init__.py # PROJECT_ROOT
|
|
50
56
|
│ ├── __main__.py # python -m backend
|
|
@@ -71,8 +77,9 @@ izTeamSlots/
|
|
|
71
77
|
│ ├── screens/MainScreen.ts # Главный экран
|
|
72
78
|
│ ├── transport/stdioClient.ts # JSON-RPC клиент
|
|
73
79
|
│ └── menus/ # Меню, таблицы, форматирование
|
|
74
|
-
├──
|
|
75
|
-
├──
|
|
80
|
+
├── requirements.txt # Python-зависимости
|
|
81
|
+
├── ruff.toml # Конфиг линтера Python
|
|
82
|
+
├── LICENSE # MIT
|
|
76
83
|
└── README.md
|
|
77
84
|
```
|
|
78
85
|
|
|
@@ -100,7 +107,9 @@ npm install
|
|
|
100
107
|
|
|
101
108
|
### Настройка
|
|
102
109
|
|
|
103
|
-
|
|
110
|
+
API-ключи и провайдеры почты настраиваются прямо в приложении через меню **«Настройки»**. Значения сохраняются в `~/.izteamslots/.env`.
|
|
111
|
+
|
|
112
|
+
Альтернативно — создайте файл вручную:
|
|
104
113
|
|
|
105
114
|
```bash
|
|
106
115
|
# Linux / macOS
|
|
@@ -114,8 +123,6 @@ echo "BOOMLIFY_API_KEY=your_api_key" > "$env:USERPROFILE\.izteamslots\.env"
|
|
|
114
123
|
|
|
115
124
|
Конфиг загружается в порядке приоритета: `~/.izteamslots/.env` > `./.env` > встроенный.
|
|
116
125
|
|
|
117
|
-
Опциональные переменные:
|
|
118
|
-
|
|
119
126
|
| Переменная | По умолчанию | Описание |
|
|
120
127
|
|-----------|:------------:|----------|
|
|
121
128
|
| `BOOMLIFY_API_KEY` | — | API-ключ Boomlify (обязательно для слотов) |
|
|
@@ -234,8 +241,3 @@ flowchart TD
|
|
|
234
241
|
- Не публикуйте папки `accounts/` и `codex/` в публичные репозитории.
|
|
235
242
|
- Для стабильной работы перелогина у worker должен быть `openai_password`.
|
|
236
243
|
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Участие в разработке
|
|
240
|
-
|
|
241
|
-
См. [CONTRIBUTING.md](CONTRIBUTING.md).
|
package/backend/mail/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Pluggable mail provider system.
|
|
1
|
+
"""Pluggable mail provider system with auto-discovery.
|
|
2
2
|
|
|
3
3
|
Usage::
|
|
4
4
|
|
|
@@ -17,18 +17,17 @@ Select provider via env var or name::
|
|
|
17
17
|
|
|
18
18
|
provider = create_provider("imap", host="imap.example.com")
|
|
19
19
|
|
|
20
|
-
Available providers:
|
|
21
|
-
trickads - trickadsagencyltd.com temp mail (generic/default)
|
|
22
|
-
boomlify - Boomlify temp mail API (slots default)
|
|
23
|
-
imap - any IMAP server (env: IMAP_HOST, IMAP_PORT, IMAP_SSL)
|
|
24
|
-
|
|
25
20
|
Custom providers:
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
Create a .py file in backend/mail/, subclass MailProvider, set ``name``
|
|
22
|
+
and optionally ``password_prefix``. The provider will be discovered
|
|
23
|
+
automatically — no edits to __init__.py needed.
|
|
28
24
|
"""
|
|
29
25
|
from __future__ import annotations
|
|
30
26
|
|
|
27
|
+
import importlib
|
|
31
28
|
import os
|
|
29
|
+
import pkgutil
|
|
30
|
+
from pathlib import Path
|
|
32
31
|
from typing import Any
|
|
33
32
|
|
|
34
33
|
from .base import Inbox, Mail, MailAuthError, Mailbox, MailError, MailProvider, MailServiceUnavailable
|
|
@@ -46,7 +45,38 @@ __all__ = [
|
|
|
46
45
|
"create_slot_provider",
|
|
47
46
|
]
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
_REGISTRY: dict[str, type[MailProvider]] | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _discover_providers() -> dict[str, type[MailProvider]]:
|
|
52
|
+
"""Scan this package for MailProvider subclasses and build a registry."""
|
|
53
|
+
registry: dict[str, type[MailProvider]] = {}
|
|
54
|
+
package_dir = Path(__file__).parent
|
|
55
|
+
|
|
56
|
+
for module_info in pkgutil.iter_modules([str(package_dir)]):
|
|
57
|
+
if module_info.name.startswith("_") or module_info.name == "base":
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
module = importlib.import_module(f".{module_info.name}", __package__)
|
|
61
|
+
except Exception:
|
|
62
|
+
continue
|
|
63
|
+
for attr in vars(module).values():
|
|
64
|
+
if (
|
|
65
|
+
isinstance(attr, type)
|
|
66
|
+
and issubclass(attr, MailProvider)
|
|
67
|
+
and attr is not MailProvider
|
|
68
|
+
and attr.name != "base"
|
|
69
|
+
):
|
|
70
|
+
registry[attr.name] = attr
|
|
71
|
+
|
|
72
|
+
return registry
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_registry() -> dict[str, type[MailProvider]]:
|
|
76
|
+
global _REGISTRY # noqa: PLW0603
|
|
77
|
+
if _REGISTRY is None:
|
|
78
|
+
_REGISTRY = _discover_providers()
|
|
79
|
+
return _REGISTRY
|
|
50
80
|
|
|
51
81
|
|
|
52
82
|
def create_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
|
|
@@ -58,23 +88,12 @@ def create_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
|
|
|
58
88
|
**kwargs: Passed to provider constructor.
|
|
59
89
|
"""
|
|
60
90
|
provider_name = name or os.environ.get("MAIL_PROVIDER", "trickads")
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
from .trickads import TrickAdsProvider
|
|
68
|
-
return TrickAdsProvider(**kwargs)
|
|
69
|
-
|
|
70
|
-
if provider_name == "imap":
|
|
71
|
-
from .imap import IMAPProvider
|
|
72
|
-
return IMAPProvider(**kwargs)
|
|
73
|
-
|
|
74
|
-
raise ValueError(
|
|
75
|
-
f"Unknown mail provider: {provider_name!r}. "
|
|
76
|
-
f"Available: {', '.join(_BUILTIN_PROVIDERS)}"
|
|
77
|
-
)
|
|
91
|
+
registry = _get_registry()
|
|
92
|
+
cls = registry.get(provider_name)
|
|
93
|
+
if cls is None:
|
|
94
|
+
available = ", ".join(sorted(registry)) or "(none)"
|
|
95
|
+
raise ValueError(f"Unknown mail provider: {provider_name!r}. Available: {available}")
|
|
96
|
+
return cls(**kwargs)
|
|
78
97
|
|
|
79
98
|
|
|
80
99
|
def create_slot_provider(name: str | None = None, **kwargs: Any) -> MailProvider:
|
|
@@ -89,10 +108,14 @@ def create_slot_provider(name: str | None = None, **kwargs: Any) -> MailProvider
|
|
|
89
108
|
def create_provider_for_mailbox(mailbox: Mailbox, **kwargs: Any) -> MailProvider:
|
|
90
109
|
"""Pick provider based on stored mailbox credentials.
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
Checks ``password_prefix`` of each registered provider first,
|
|
112
|
+
then falls back to the default provider.
|
|
94
113
|
"""
|
|
95
114
|
password = mailbox.password.strip()
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
registry = _get_registry()
|
|
116
|
+
|
|
117
|
+
for cls in registry.values():
|
|
118
|
+
if cls.password_prefix and password.startswith(cls.password_prefix):
|
|
119
|
+
return cls(**kwargs)
|
|
120
|
+
|
|
98
121
|
return create_provider(**kwargs)
|
package/backend/mail/base.py
CHANGED
package/backend/mail/boomlify.py
CHANGED
package/package.json
CHANGED
|
@@ -14,8 +14,8 @@ const EMPTY_STATE: AppState = {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const HERO_LOGO = [
|
|
17
|
-
"
|
|
18
|
-
"
|
|
17
|
+
"izTeamSlots",
|
|
18
|
+
"Локальный центр управления слотами",
|
|
19
19
|
].join("\n")
|
|
20
20
|
|
|
21
21
|
type RpcJobResult = { job_id: string }
|
|
@@ -578,6 +578,7 @@ export class MainScreen {
|
|
|
578
578
|
private getRecommendedSteps(): string[] {
|
|
579
579
|
if (this.state.admins.length === 0) {
|
|
580
580
|
return [
|
|
581
|
+
"Откройте «Настройки» и укажите API-ключ временной почты.",
|
|
581
582
|
"Добавьте первого админа через раздел «Админы».",
|
|
582
583
|
"После добавления выполните логин, чтобы сохранить токен и профиль браузера.",
|
|
583
584
|
"Затем создайте слоты и проверьте входящие письма.",
|