feat: Absender/Titel frei wählbar, kein "Notruf"-Default mehr (0.1.2)
"Notruf" war als Library/CLI-Default falsch — das gehört in die Bridge-Config, nicht ins Allzweck-Tool. Jetzt: - kein eingebauter Default-Absender; ohne Angabe nutzt seven.io den Kontostandard-Absender (send_sms lässt "from" dann weg) - CLI --from für SMS (Absender/Titel) und Voice (Caller-ID); zusätzlich $SEVEN_SMS_FROM / $SEVEN_VOICE_FROM als persistenter Default - Tests: Default ohne Absender, per-call-Override, CLI --from + Env (19 gesamt) - README: Absender/Titel dokumentiert Die Bridge übergibt sms_from="Notruf" weiterhin explizit -> Voice Assistant unverändert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fad93e194f
commit
1344d1f7e9
7 changed files with 64 additions and 10 deletions
10
README.md
10
README.md
|
|
@ -19,12 +19,18 @@ pip install -e ".[dev]"
|
||||||
export SEVEN_API_KEY=dein-key
|
export SEVEN_API_KEY=dein-key
|
||||||
|
|
||||||
seven-send sms --to +4915112345678 --text "Hallo Welt"
|
seven-send sms --to +4915112345678 --text "Hallo Welt"
|
||||||
|
seven-send sms --to +4915112345678 --text "Hallo Welt" --from "MeineFirma" # Absender/Titel
|
||||||
seven-send voice --to +4915112345678 --text "Dies wird vorgelesen." --voice de-de-female
|
seven-send voice --to +4915112345678 --text "Dies wird vorgelesen." --voice de-de-female
|
||||||
|
|
||||||
seven-send --dry-run sms --to +49… --text "nur testen" # nichts senden
|
seven-send --dry-run sms --to +49… --text "nur testen" # nichts senden
|
||||||
seven-send --json sms --to +49… --text "..." # maschinenlesbar
|
seven-send --json sms --to +49… --text "..." # maschinenlesbar
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Absender/Titel:** Per `--from` setzbar (SMS: alphanumerischer Titel oder Nummer;
|
||||||
|
Voice: verifizierte Caller-ID). Ohne `--from` greift `$SEVEN_SMS_FROM` /
|
||||||
|
`$SEVEN_VOICE_FROM`, sonst der **Kontostandard-Absender** von seven.io — es gibt
|
||||||
|
**keinen** eingebauten Default-Absender.
|
||||||
|
|
||||||
Exit-Codes: `0` Erfolg · `1` Sendefehler · `2` Konfig-/Aufruf-Fehler.
|
Exit-Codes: `0` Erfolg · `1` Sendefehler · `2` Konfig-/Aufruf-Fehler.
|
||||||
|
|
||||||
## Library
|
## Library
|
||||||
|
|
@ -32,7 +38,7 @@ Exit-Codes: `0` Erfolg · `1` Sendefehler · `2` Konfig-/Aufruf-Fehler.
|
||||||
```python
|
```python
|
||||||
from seven_send import SevenClient
|
from seven_send import SevenClient
|
||||||
|
|
||||||
c = SevenClient(api_key, sms_from="Notruf", voice_name="de-de-female")
|
c = SevenClient(api_key, sms_from="MeineFirma", voice_name="de-de-female")
|
||||||
|
|
||||||
res = c.send_sms("+4915112345678", "Hallo")
|
res = c.send_sms("+4915112345678", "Hallo")
|
||||||
if not res: # bool(res) == res.ok
|
if not res: # bool(res) == res.ok
|
||||||
|
|
@ -52,7 +58,7 @@ res = await asyncio.to_thread(c.send_sms, "+49…", "Hallo")
|
||||||
| Parameter | Default | Bedeutung |
|
| Parameter | Default | Bedeutung |
|
||||||
|-----------|---------|-----------|
|
|-----------|---------|-----------|
|
||||||
| `api_key` | – | seven.io API-Key (oder `$SEVEN_API_KEY` für die CLI) |
|
| `api_key` | – | seven.io API-Key (oder `$SEVEN_API_KEY` für die CLI) |
|
||||||
| `sms_from` | `Notruf` | Absender-ID der SMS (alphanumerisch, max. 11 Zeichen) |
|
| `sms_from` | `None` | Absender-ID/Titel der SMS (alphanumerisch max. 11 Zeichen oder Nummer); leer = Kontostandard |
|
||||||
| `voice_from` | `None` | Caller-ID für Anrufe (verifizierte Nummer) |
|
| `voice_from` | `None` | Caller-ID für Anrufe (verifizierte Nummer) |
|
||||||
| `voice_name` | `de-de-female` | SSML-Stimme für die Anruf-Ansage |
|
| `voice_name` | `de-de-female` | SSML-Stimme für die Anruf-Ansage |
|
||||||
| `dry_run` | `False` | nichts senden, nur protokollieren |
|
| `dry_run` | `False` | nichts senden, nur protokollieren |
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "seven-send"
|
name = "seven-send"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
description = "Kleine Library + CLI zum Verschicken von SMS und TTS-Sprachanrufen über seven.io"
|
description = "Kleine Library + CLI zum Verschicken von SMS und TTS-Sprachanrufen über seven.io"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
from seven_send.client import Result, SevenClient, SevenError
|
from seven_send.client import Result, SevenClient, SevenError
|
||||||
|
|
||||||
__all__ = ["SevenClient", "Result", "SevenError"]
|
__all__ = ["SevenClient", "Result", "SevenError"]
|
||||||
__version__ = "0.1.1"
|
__version__ = "0.1.2"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from seven_send.client import DEFAULT_SMS_FROM, DEFAULT_VOICE_NAME, Result, SevenClient, SevenError
|
from seven_send.client import DEFAULT_VOICE_NAME, Result, SevenClient, SevenError
|
||||||
|
|
||||||
|
|
||||||
def _build_parser() -> argparse.ArgumentParser:
|
def _build_parser() -> argparse.ArgumentParser:
|
||||||
|
|
@ -28,12 +28,18 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||||
sms = sub.add_parser("sms", help="SMS senden")
|
sms = sub.add_parser("sms", help="SMS senden")
|
||||||
sms.add_argument("--to", required=True, help="Empfänger (E.164, z. B. +4915112345678)")
|
sms.add_argument("--to", required=True, help="Empfänger (E.164, z. B. +4915112345678)")
|
||||||
sms.add_argument("--text", required=True)
|
sms.add_argument("--text", required=True)
|
||||||
sms.add_argument("--from", dest="sender", default=DEFAULT_SMS_FROM, help="Absender-ID")
|
sms.add_argument(
|
||||||
|
"--from", dest="sender", default=os.getenv("SEVEN_SMS_FROM") or None,
|
||||||
|
help="Absender-ID/Titel (Default: $SEVEN_SMS_FROM, sonst seven.io-Kontostandard)",
|
||||||
|
)
|
||||||
|
|
||||||
voice = sub.add_parser("voice", help="TTS-Anruf auslösen")
|
voice = sub.add_parser("voice", help="TTS-Anruf auslösen")
|
||||||
voice.add_argument("--to", required=True, help="Empfänger (E.164)")
|
voice.add_argument("--to", required=True, help="Empfänger (E.164)")
|
||||||
voice.add_argument("--text", required=True, help="vorzulesender Text")
|
voice.add_argument("--text", required=True, help="vorzulesender Text")
|
||||||
voice.add_argument("--from", dest="sender", default=None, help="Caller-ID (verifizierte Nummer)")
|
voice.add_argument(
|
||||||
|
"--from", dest="sender", default=os.getenv("SEVEN_VOICE_FROM") or None,
|
||||||
|
help="Caller-ID (verifizierte Nummer; Default: $SEVEN_VOICE_FROM)",
|
||||||
|
)
|
||||||
voice.add_argument("--voice", dest="voice_name", default=DEFAULT_VOICE_NAME, help="SSML-Stimme")
|
voice.add_argument("--voice", dest="voice_name", default=DEFAULT_VOICE_NAME, help="SSML-Stimme")
|
||||||
voice.add_argument("--no-ssml", dest="ssml", action="store_false", help="Text roh ohne SSML senden")
|
voice.add_argument("--no-ssml", dest="ssml", action="store_false", help="Text roh ohne SSML senden")
|
||||||
return p
|
return p
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ import httpx
|
||||||
logger = logging.getLogger("seven_send")
|
logger = logging.getLogger("seven_send")
|
||||||
|
|
||||||
BASE_URL = "https://gateway.seven.io/api"
|
BASE_URL = "https://gateway.seven.io/api"
|
||||||
DEFAULT_SMS_FROM = "Notruf"
|
|
||||||
DEFAULT_VOICE_NAME = "de-de-female"
|
DEFAULT_VOICE_NAME = "de-de-female"
|
||||||
|
# Kein Default-Absender: ohne Angabe nutzt seven.io den Kontostandard-Absender.
|
||||||
|
|
||||||
|
|
||||||
class SevenError(Exception):
|
class SevenError(Exception):
|
||||||
|
|
@ -51,7 +51,7 @@ class SevenClient:
|
||||||
self,
|
self,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
*,
|
*,
|
||||||
sms_from: str = DEFAULT_SMS_FROM,
|
sms_from: str | None = None,
|
||||||
voice_from: str | None = None,
|
voice_from: str | None = None,
|
||||||
voice_name: str = DEFAULT_VOICE_NAME,
|
voice_name: str = DEFAULT_VOICE_NAME,
|
||||||
timeout: float = 15.0,
|
timeout: float = 15.0,
|
||||||
|
|
@ -70,7 +70,10 @@ class SevenClient:
|
||||||
|
|
||||||
# -- öffentlich ---------------------------------------------------------
|
# -- öffentlich ---------------------------------------------------------
|
||||||
def send_sms(self, to: str, text: str, *, sender: str | None = None) -> Result:
|
def send_sms(self, to: str, text: str, *, sender: str | None = None) -> Result:
|
||||||
data = {"to": to, "text": text, "from": sender or self.sms_from}
|
data = {"to": to, "text": text}
|
||||||
|
frm = sender or self.sms_from
|
||||||
|
if frm: # leer/None -> seven.io-Kontostandard-Absender
|
||||||
|
data["from"] = frm
|
||||||
return self._post("sms", to, data)
|
return self._post("sms", to, data)
|
||||||
|
|
||||||
def send_voice(
|
def send_voice(
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,27 @@ def test_cli_missing_key(monkeypatch, capsys):
|
||||||
rc = cli.main(["sms", "--to", "+49", "--text", "Hi"])
|
rc = cli.main(["sms", "--to", "+49", "--text", "Hi"])
|
||||||
assert rc == 2
|
assert rc == 2
|
||||||
assert "Konfig-Fehler" in capsys.readouterr().err
|
assert "Konfig-Fehler" in capsys.readouterr().err
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_sms_from_flag(monkeypatch):
|
||||||
|
monkeypatch.delenv("SEVEN_SMS_FROM", raising=False)
|
||||||
|
cap = []
|
||||||
|
_patch(monkeypatch, cap)
|
||||||
|
cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi", "--from", "Firma"])
|
||||||
|
assert cap[0]["data"]["from"] == "Firma"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_sms_no_from_by_default(monkeypatch):
|
||||||
|
monkeypatch.delenv("SEVEN_SMS_FROM", raising=False)
|
||||||
|
cap = []
|
||||||
|
_patch(monkeypatch, cap)
|
||||||
|
cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi"])
|
||||||
|
assert "from" not in cap[0]["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_sms_from_env(monkeypatch):
|
||||||
|
monkeypatch.setenv("SEVEN_SMS_FROM", "EnvAbs")
|
||||||
|
cap = []
|
||||||
|
_patch(monkeypatch, cap)
|
||||||
|
cli.main(["--api-key", "k", "sms", "--to", "+49", "--text", "Hi"])
|
||||||
|
assert cap[0]["data"]["from"] == "EnvAbs"
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,21 @@ def test_voice_no_ssml(monkeypatch):
|
||||||
assert cap[0]["data"]["text"] == "roh"
|
assert cap[0]["data"]["text"] == "roh"
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_sms_default_omits_sender(monkeypatch):
|
||||||
|
# Ohne sms_from kein "from" -> seven.io nutzt den Kontostandard-Absender
|
||||||
|
cap = []
|
||||||
|
_patch_post(monkeypatch, cap, FakeResp(payload={"success": "100"}))
|
||||||
|
SevenClient("key").send_sms("+49", "Hi")
|
||||||
|
assert "from" not in cap[0]["data"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_sms_sender_override(monkeypatch):
|
||||||
|
cap = []
|
||||||
|
_patch_post(monkeypatch, cap, FakeResp(payload={"success": "100"}))
|
||||||
|
SevenClient("key", sms_from="Firma").send_sms("+49", "Hi", sender="Aktion")
|
||||||
|
assert cap[0]["data"]["from"] == "Aktion" # per-call schlägt Instanz-Default
|
||||||
|
|
||||||
|
|
||||||
def test_failure_code(monkeypatch):
|
def test_failure_code(monkeypatch):
|
||||||
cap = []
|
cap = []
|
||||||
_patch_post(monkeypatch, cap, FakeResp(payload={"success": "401"}))
|
_patch_post(monkeypatch, cap, FakeResp(payload={"success": "401"}))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue