first commit

This commit is contained in:
Stefan Hacker
2026-06-09 10:17:37 +02:00
commit a5b1de275d
16 changed files with 894 additions and 0 deletions
Binary file not shown.
+500
View File
@@ -0,0 +1,500 @@
import de.vertico.starface.module.core.model.VariableType;
import de.vertico.starface.module.core.model.Visibility;
import de.vertico.starface.module.core.runtime.IBaseExecutable;
import de.vertico.starface.module.core.runtime.IRuntimeEnvironment;
import de.vertico.starface.module.core.runtime.annotations.Function;
import de.vertico.starface.module.core.runtime.annotations.InputVar;
import de.vertico.starface.module.core.runtime.annotations.OutputVar;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* BlacklistBlock - STARFACE Custom Block
*
* Prüft bei jedem eingehenden Anruf die Anrufernummer gegen eine konfigurierbare
* Blacklist und führt eine wählbare Aktion aus (auflegen, besetzt, umleiten, Ansage).
* Anonyme Anrufe (unterdrückte Rufnummer) werden separat behandelt.
*
* Das Modul muss als Typ "Call-Processing" mit Aktivierung "bei allen eingehenden
* Anrufen" konfiguriert werden, damit der Block im Anruf-Kontext läuft.
*
* Für STARFACE 8.x, 9.x, 10.x (Java 21)
*/
@Function(
visibility = Visibility.Public,
modifiesCalls = true,
description = "Prüft eingehende Anrufe gegen eine Blacklist und behandelt anonyme Anrufe"
)
public class BlacklistBlock implements IBaseExecutable {
// Voll qualifizierte Namen der STARFACE-internen Blöcke (per Reflection genutzt,
// damit der Block über STARFACE-Versionen hinweg robust bleibt).
private static final String CN_AGI_RUNTIME =
"de.vertico.starface.module.core.runtime.IAGIRuntimeEnvironment";
private static final String CN_GET_CALLER =
"de.vertico.starface.module.core.runtime.functions.callHandling.call.GetCaller2";
private static final String CN_HANGUP =
"de.vertico.starface.module.core.runtime.functions.callHandling.call.Hangup";
private static final String CN_BUSY =
"de.vertico.starface.module.core.runtime.functions.callHandling.call.Busy";
private static final String CN_CALL_NUMBER =
"de.vertico.starface.module.core.runtime.functions.callHandling.call.CallPhonenumber2";
private static final String CN_ANSWER =
"de.vertico.starface.module.core.runtime.functions.callHandling.call.Answer";
private static final String CN_PLAYBACK =
"de.vertico.starface.module.core.runtime.functions.callHandling.audio.PlaybackResourceFile2";
// ============================================================
// INPUT VARIABLEN - werden im Module Designer konfiguriert
// ============================================================
@InputVar(
label = "Blacklist-Einträge",
description = "Eine Nummer pro Zeile (oder durch Komma getrennt). Pro Zeile optional eine "
+ "eigene Aktion und ein Ziel im Format: nummer;AKTION;ziel\n"
+ "Beispiele:\n"
+ "+49301234567 -> Standard-Aktion\n"
+ "0190;BUSY -> dieser Eintrag immer besetzt\n"
+ "004989;REDIRECT;100 -> auf Nebenstelle 100 umleiten\n"
+ "+4972112345;ALLOW -> Ausnahme (durchlassen)\n"
+ "AKTIONEN: HANGUP, BUSY, REDIRECT, ANNOUNCE, ALLOW. "
+ "Zeilen mit '#' am Anfang sind Kommentare.",
type = VariableType.STRING,
guiHints = { "multiline" }
)
public String blacklistEntries = "";
@InputVar(
label = "Standard-Aktion (Blacklist)",
description = "Aktion für Blacklist-Einträge ohne eigene Aktion. "
+ "HANGUP = auflegen, BUSY = besetzt, REDIRECT = umleiten, ANNOUNCE = Ansage + auflegen",
type = VariableType.STRING,
possibleValues = { "HANGUP", "BUSY", "REDIRECT", "ANNOUNCE" }
)
public String defaultAction = "HANGUP";
@InputVar(
label = "Standard-Umleitungsziel",
description = "Zielrufnummer/Nebenstelle für REDIRECT-Einträge ohne eigenes Ziel (z.B. 100 oder +4972199999)",
type = VariableType.STRING
)
public String defaultRedirectTarget = "";
@InputVar(
label = "Ansage-Datei",
description = "Name der hochgeladenen Sound-Ressource für die Aktion ANNOUNCE (ohne Pfad). "
+ "Die Datei muss als Ressource im Modul hinterlegt sein.",
type = VariableType.STRING
)
public String announcementFile = "";
@InputVar(
label = "Aktion bei anonymen Anrufen",
description = "Behandlung von Anrufen mit unterdrückter Rufnummer. "
+ "NONE = nichts tun (Anruf normal durchstellen), sonst HANGUP/BUSY/REDIRECT/ANNOUNCE",
type = VariableType.STRING,
possibleValues = { "NONE", "HANGUP", "BUSY", "REDIRECT", "ANNOUNCE" }
)
public String anonymousAction = "NONE";
@InputVar(
label = "Umleitungsziel für anonyme Anrufe",
description = "Zielrufnummer/Nebenstelle, falls die Aktion für anonyme Anrufe REDIRECT ist",
type = VariableType.STRING
)
public String anonymousRedirectTarget = "";
@InputVar(
label = "Ländervorwahl",
description = "Eigene Ländervorwahl ohne führende Null/Plus (Deutschland = 49). "
+ "Wird genutzt, um nationale Nummern (0721...) und internationale (+49721...) einheitlich zu vergleichen.",
type = VariableType.STRING
)
public String countryCode = "49";
@InputVar(
label = "Präfix-/Bereichssperre erlauben",
description = "Wenn aktiv, sperrt ein Eintrag auch alle Nummern, die mit ihm beginnen "
+ "(z.B. '0190' sperrt den gesamten Bereich). Wenn aus, wird nur exakt verglichen.",
type = VariableType.BOOLEAN
)
public Boolean prefixMatch = true;
// ============================================================
// OUTPUT VARIABLEN - Ergebnis der Prüfung
// ============================================================
@OutputVar(
label = "Anrufernummer",
description = "Die erkannte Anrufernummer (international normalisiert)",
type = VariableType.STRING
)
public String caller = "";
@OutputVar(
label = "Anonym",
description = "true, wenn der Anrufer die Rufnummer unterdrückt hat",
type = VariableType.BOOLEAN
)
public Boolean isAnonymous = false;
@OutputVar(
label = "Geblockt",
description = "true, wenn der Anruf durch die Blacklist oder die Anonym-Regel behandelt wurde",
type = VariableType.BOOLEAN
)
public Boolean blocked = false;
@OutputVar(
label = "Ausgeführte Aktion",
description = "Die tatsächlich ausgeführte Aktion (HANGUP/BUSY/REDIRECT/ANNOUNCE/ALLOW/NONE)",
type = VariableType.STRING
)
public String actionTaken = "NONE";
@OutputVar(
label = "Status",
description = "Status-Meldung der Prüfung",
type = VariableType.STRING
)
public String statusMessage = "";
// Runtime
private IRuntimeEnvironment runtime;
private org.apache.logging.log4j.Logger log;
// Ermittelte Anrufer-Daten (statt innerer Klasse, um separate .class-Dateien zu vermeiden)
private String callerRaw = "";
private String callerNameVal = "";
@Override
public void execute(IRuntimeEnvironment runtime) throws Exception {
this.runtime = runtime;
this.log = runtime.getLog();
// Sicherstellen, dass wir im Anruf-Kontext laufen (AGI-Runtime)
Class<?> agiClass;
try {
agiClass = Class.forName(CN_AGI_RUNTIME);
} catch (ClassNotFoundException e) {
statusMessage = "Fehler: AGI-Runtime nicht verfügbar";
log.error("Blacklist: " + statusMessage);
return;
}
if (!agiClass.isInstance(runtime)) {
statusMessage = "Übersprungen - kein Anruf-Kontext (Modul muss 'Call-Processing' / "
+ "'bei allen eingehenden Anrufen' sein)";
log.warn("Blacklist: " + statusMessage);
return;
}
// 1. Anrufer-Informationen ermitteln (füllt callerRaw, callerNameVal, caller, isAnonymous)
if (!getCaller()) {
statusMessage = "Anrufer konnte nicht ermittelt werden - Anruf wird durchgestellt";
log.warn("Blacklist: " + statusMessage);
return;
}
log.info("Blacklist: Eingehender Anruf - Nummer='" + callerRaw
+ "' (intl='" + caller + "'), anonym=" + isAnonymous
+ ", Name='" + callerNameVal + "'");
// 2. Anonyme Anrufe separat behandeln
if (Boolean.TRUE.equals(isAnonymous)) {
String act = norm(anonymousAction);
if (act.isEmpty() || act.equals("NONE") || act.equals("ALLOW")) {
statusMessage = "Anonymer Anruf - keine Aktion, wird durchgestellt";
actionTaken = "NONE";
log.info("Blacklist: " + statusMessage);
return;
}
performAction(act, anonymousRedirectTarget);
blocked = true;
statusMessage = "Anonymer Anruf -> " + actionTaken;
log.info("Blacklist: " + statusMessage);
return;
}
// 3. Nummer gegen Blacklist prüfen.
// matchBlacklist() füllt matchAction/matchTarget/matchEntry, Rückgabe = Treffer ja/nein.
if (!matchBlacklist(caller)) {
statusMessage = "Nummer nicht in Blacklist - wird durchgestellt";
actionTaken = "NONE";
log.info("Blacklist: " + statusMessage);
return;
}
if (matchAction.equals("ALLOW")) {
statusMessage = "Treffer auf Ausnahme (ALLOW) - wird durchgestellt";
actionTaken = "ALLOW";
log.info("Blacklist: " + statusMessage + " [Regel: " + matchEntry + "]");
return;
}
performAction(matchAction, !matchTarget.isEmpty() ? matchTarget : defaultRedirectTarget);
blocked = true;
statusMessage = "Blacklist-Treffer [" + matchEntry + "] -> " + actionTaken;
log.info("Blacklist: " + statusMessage);
}
// ============================================================
// Anrufer ermitteln über GetCaller2
// ============================================================
/** Füllt callerRaw, callerNameVal, caller (intl) und isAnonymous. Rückgabe false bei Fehler. */
private boolean getCaller() {
try {
Object getCaller = newBlock(CN_GET_CALLER);
execBlock(getCaller);
callerNameVal = strField(getCaller, "callerName");
Object anon = field(getCaller, "isAnonymous");
isAnonymous = (anon instanceof Boolean) && (Boolean) anon;
// Externe Nummer bevorzugen, dann Signalisierung, dann interne Nummer
String ext = strField(getCaller, "callerExtNumber");
String sig = strField(getCaller, "callerSignallingNumber");
String intn = strField(getCaller, "callerIntNumber");
callerRaw = !ext.isEmpty() ? ext : (!sig.isEmpty() ? sig : intn);
caller = toIntl(callerRaw);
// Wenn keinerlei Nummer da ist, gilt der Anruf praktisch als anonym
if (callerRaw.isEmpty() && !isAnonymous) {
log.debug("Blacklist: Keine Anrufernummer vorhanden - als anonym behandelt");
isAnonymous = true;
}
return true;
} catch (Exception e) {
log.error("Blacklist: Fehler beim Ermitteln des Anrufers: " + e.getMessage(), e);
return false;
}
}
// ============================================================
// Blacklist-Matching
// ============================================================
// Ergebnis des letzten Treffers (statt innerer Klasse)
private String matchAction = "";
private String matchTarget = "";
private String matchEntry = "";
/** Prüft callerIntl gegen die Einträge. Bei Treffer werden matchAction/Target/Entry gesetzt. */
private boolean matchBlacklist(String callerIntl) {
if (blacklistEntries == null || blacklistEntries.trim().isEmpty()) {
return false;
}
if (callerIntl == null || callerIntl.isEmpty()) {
return false;
}
// Trennung an Zeilenumbrüchen und Kommas
String[] lines = blacklistEntries.split("[\\r\\n,]+");
for (String line : lines) {
String entry = line.trim();
if (entry.isEmpty() || entry.startsWith("#")) {
continue;
}
String[] parts = entry.split(";");
String number = parts[0].trim();
String action = parts.length > 1 ? norm(parts[1]) : norm(defaultAction);
String target = parts.length > 2 ? parts[2].trim() : "";
if (action.isEmpty()) {
action = "HANGUP";
}
String entryIntl = toIntl(number);
if (entryIntl.isEmpty()) {
continue;
}
boolean hit;
if (callerIntl.equals(entryIntl)) {
hit = true;
} else if (Boolean.TRUE.equals(prefixMatch)
&& entryIntl.length() >= 3
&& callerIntl.startsWith(entryIntl)) {
hit = true;
} else {
hit = false;
}
if (hit) {
matchAction = action;
matchTarget = target;
matchEntry = entry;
return true;
}
}
return false;
}
// ============================================================
// Aktionen ausführen
// ============================================================
private void performAction(String action, String target) {
try {
switch (action) {
case "HANGUP":
case "REJECT":
execBlock(newBlock(CN_HANGUP));
actionTaken = "HANGUP";
break;
case "BUSY":
execBlock(newBlock(CN_BUSY));
actionTaken = "BUSY";
break;
case "REDIRECT":
case "FORWARD":
if (target == null || target.trim().isEmpty()) {
log.error("Blacklist: REDIRECT ohne Ziel - lege stattdessen auf");
execBlock(newBlock(CN_HANGUP));
actionTaken = "HANGUP";
} else {
redirect(target.trim());
actionTaken = "REDIRECT";
}
break;
case "ANNOUNCE":
announce();
actionTaken = "ANNOUNCE";
break;
default:
log.warn("Blacklist: Unbekannte Aktion '" + action + "' - lege auf");
execBlock(newBlock(CN_HANGUP));
actionTaken = "HANGUP";
}
} catch (Exception e) {
log.error("Blacklist: Fehler beim Ausführen der Aktion '" + action + "': " + e.getMessage(), e);
// Im Zweifel auflegen
try {
execBlock(newBlock(CN_HANGUP));
actionTaken = "HANGUP";
} catch (Exception ignore) {
// nichts mehr möglich
}
}
}
private void redirect(String target) throws Exception {
Object call = newBlock(CN_CALL_NUMBER);
setField(call, "phoneNumber", target);
// Ursprünglichen Anrufer mitsignalisieren, damit das Ziel sieht, wer anruft
setField(call, "callerName", callerNameVal != null ? callerNameVal : "");
setField(call, "callerNumber", callerRaw != null ? callerRaw : "");
setField(call, "timeout", 30);
execBlock(call);
log.info("Blacklist: Anruf von " + caller + " umgeleitet auf " + target);
}
private void announce() throws Exception {
// Anruf annehmen, Ansage abspielen, dann auflegen
Object answer = newBlock(CN_ANSWER);
setField(answer, "delay", 0);
execBlock(answer);
if (announcementFile != null && !announcementFile.trim().isEmpty()) {
Object playback = newBlock(CN_PLAYBACK);
setField(playback, "defaultFile", announcementFile.trim());
execBlock(playback);
} else {
log.warn("Blacklist: ANNOUNCE ohne Ansage-Datei konfiguriert");
}
execBlock(newBlock(CN_HANGUP));
}
// ============================================================
// Reflection-Hilfen
// ============================================================
private Object newBlock(String className) throws Exception {
return Class.forName(className).getDeclaredConstructor().newInstance();
}
/** Führt einen Call-Handling-Block mit der aktuellen AGI-Runtime aus. */
private void execBlock(Object block) throws Exception {
Class<?> agiClass = Class.forName(CN_AGI_RUNTIME);
Method m = block.getClass().getMethod("execute", agiClass);
m.invoke(block, runtime);
}
private void setField(Object obj, String name, Object value) {
try {
Field f = obj.getClass().getField(name);
f.set(obj, value);
} catch (NoSuchFieldException e) {
log.debug("Blacklist: Feld '" + name + "' nicht vorhanden in " + obj.getClass().getName());
} catch (Exception e) {
log.warn("Blacklist: Konnte Feld '" + name + "' nicht setzen: " + e.getMessage());
}
}
private Object field(Object obj, String name) {
try {
Field f = obj.getClass().getField(name);
return f.get(obj);
} catch (Exception e) {
return null;
}
}
private String strField(Object obj, String name) {
Object v = field(obj, name);
return v != null ? v.toString() : "";
}
// ============================================================
// Nummern-Normalisierung
// ============================================================
/**
* Normalisiert eine Rufnummer in eine einheitliche internationale Ziffernform.
* Beispiele (countryCode=49):
* +49 721 12345 -> 4972112345
* 0049 721 12345 -> 4972112345
* 0721 12345 -> 4972112345
* 100 -> 100 (interne Nebenstelle)
*/
private String toIntl(String raw) {
if (raw == null) {
return "";
}
String r = raw.trim();
if (r.isEmpty()) {
return "";
}
boolean plus = r.startsWith("+");
String digits = r.replaceAll("[^0-9]", "");
if (digits.isEmpty()) {
return "";
}
String cc = countryCode != null ? countryCode.replaceAll("[^0-9]", "") : "";
if (plus) {
return digits;
}
if (digits.startsWith("00")) {
return digits.substring(2);
}
if (digits.startsWith("0")) {
return cc + digits.substring(1);
}
return digits;
}
private String norm(String s) {
return s == null ? "" : s.trim().toUpperCase();
}
}
+156
View File
@@ -0,0 +1,156 @@
# Blacklist - Installations- und Konfigurationshandbuch
Schritt-für-Schritt-Anleitung zur Installation des Blacklist-Moduls im STARFACE Module Designer.
---
## Voraussetzungen
- STARFACE 8.x, 9.x oder 10.x
- Admin-Zugriff auf STARFACE
- Kompilierte `BlacklistBlock.class` (siehe `./build-block.sh`)
> Es ist **keine** zusätzliche Bibliothek nötig (anders als bei Mail2Fax kein JavaMail).
> Es wird auch **kein** SSH-Zugriff zum Installieren von Libs benötigt.
---
## Teil 1: Modul-Erstellung im Module Designer
### Schritt 1: Neues Modul erstellen
STARFACE Admin → **Module → Module Designer → Neues Modul**
- **Modul-Name**: `Blacklist`
- **Beschreibung**: "Sperrt/behandelt eingehende Anrufe anhand einer Blacklist und anonyme Anrufe"
- **Version**: `1.0.0`
### Schritt 2: Class-Datei hochladen
Tab **Ressourcen****Datei hochladen**`BlacklistBlock.class`
Optional (nur für die Aktion `ANNOUNCE`): eine Ansage-Audiodatei (WAV) hochladen.
Der Dateiname wird später im Feld **Ansage-Datei** eingetragen.
### Schritt 3: Funktion erstellen
Tab **Funktionen** → neue Funktion `Blacklist` anlegen und den Block `BlacklistBlock`
in den Ablauf ziehen.
### Schritt 4: GUI-Elemente (Eingabefelder) anlegen
Lege die Konfigurationsfelder an und verknüpfe sie mit den `@InputVar`-Feldern des Blocks:
| Feldname | Typ | Label | Auswahlwerte |
|----------|-----|-------|--------------|
| `blacklistEntries` | STRING (mehrzeilig) | Blacklist-Einträge | |
| `defaultAction` | STRING | Standard-Aktion | HANGUP, BUSY, REDIRECT, ANNOUNCE |
| `defaultRedirectTarget` | STRING | Standard-Umleitungsziel | |
| `announcementFile` | STRING | Ansage-Datei | |
| `anonymousAction` | STRING | Aktion bei anonymen Anrufen | NONE, HANGUP, BUSY, REDIRECT, ANNOUNCE |
| `anonymousRedirectTarget` | STRING | Umleitungsziel anonyme Anrufe | |
| `countryCode` | STRING | Ländervorwahl | (Default 49) |
| `prefixMatch` | BOOLEAN | Bereichssperre erlauben | (Default an) |
> Die Auswahlwerte (`possibleValues`) sind bereits im Block hinterlegt und erscheinen
> i.d.R. automatisch als Dropdown. Falls nicht, als normales Textfeld anlegen.
### Schritt 5: GUI-Felder zuweisen
Jedes GUI-Element mit dem gleichnamigen Block-Parameter verbinden. Alle `@InputVar`-Felder
sollten verknüpft sein.
### Schritt 6: Modul speichern
Modul speichern.
---
## Teil 2: Modul als Anruf-Modul aktivieren
Damit der Block bei eingehenden Anrufen läuft, muss das Modul als **Call-Processing-Modul**
eingebunden werden.
### Variante A: Für alle eingehenden Anrufe
1. Module → **Modulkonfigurationen****[+] Neu**
2. Modul `Blacklist` auswählen, Instanz benennen (z.B. "Blacklist - Zentrale")
3. Felder ausfüllen (siehe Teil 3)
4. Bei den Modul-Einstellungen den Typ **Call-Processing** wählen und die Aktivierung auf
**"bei allen eingehenden Anrufen"** setzen.
5. Modul **aktivieren**.
### Variante B: Pro Benutzer/Gruppe (Rufverteilung)
Alternativ kann das Modul gezielt in die **Rufverteilung** eines Benutzers oder einer
Gruppe eingehängt werden (Administration → Benutzer/Gruppe → Rufverteilung → Modul
einfügen, **vor** der eigentlichen Zustellung). So gilt die Blacklist nur für diese
Nummern.
> **Wichtig:** Bei einem Treffer mit Aktion `HANGUP`/`BUSY`/`REDIRECT`/`ANNOUNCE` wird der
> Anruf beendet bzw. umgeleitet und erreicht die nachgelagerte Zustellung nicht mehr.
> Bei `ALLOW`/`NONE`/keinem Treffer läuft der Anruf normal weiter.
---
## Teil 3: Konfiguration (Beispiel)
**Blacklist-Einträge:**
```
# Werbeanrufe-Bereiche komplett sperren
0190;BUSY
0900;BUSY
# einzelner Störer: auflegen
+49301234567;HANGUP
# Ausnahme: dieser eine Anschluss aus dem 089-Raum ist ok
+4989123456;ALLOW
089;ANNOUNCE
# nervige Nummer auf die Zentrale umleiten
+4972198765;REDIRECT;100
```
**Standard-Aktion (Blacklist):** `HANGUP`
**Standard-Umleitungsziel:** `100`
**Ansage-Datei:** `blacklist_ansage.wav` (zuvor als Ressource hochgeladen)
**Aktion bei anonymen Anrufen:** `BUSY`
*(oder `REDIRECT` mit Ziel, oder `NONE` zum Durchstellen)*
**Ländervorwahl:** `49`
**Bereichssperre erlauben:** ✓ an
Mit dieser Konfiguration:
- `0190…`/`0900…`/anonyme Anrufer hören Besetzt
- `+49 30 1234567` wird aufgelegt
- der gesamte `089…`-Bereich bekommt eine Ansage **außer** `+49 89 123456` (Ausnahme oben)
- `+49 721 98765` wird auf Nebenstelle 100 umgeleitet
---
## Logs und Fehlersuche
**Log-Datei:**
```
/var/log/starface/starface.log
```
**Nützliche Meldungen:**
```bash
# alle Blacklist-Entscheidungen
grep "Blacklist:" /var/log/starface/starface.log
# Treffer
grep "Blacklist-Treffer" /var/log/starface/starface.log
# anonyme Anrufe
grep "Blacklist: Anonymer Anruf" /var/log/starface/starface.log
```
**Häufige Punkte:**
1. Block tut nichts / "kein Anruf-Kontext": Das Modul ist nicht als *Call-Processing* /
"bei allen eingehenden Anrufen" eingebunden.
2. REDIRECT ohne Wirkung: Zielrufnummer/Nebenstelle prüfen, ggf. mit Amtskennziffer.
3. ANNOUNCE ohne Ton: Audiodatei als Ressource hochgeladen und Dateiname im Feld korrekt?
4. Nummer wird nicht erkannt: `countryCode` prüfen und im Log die normalisierte
`intl='…'`-Form mit dem Eintrag vergleichen.
</content>
+113
View File
@@ -0,0 +1,113 @@
# BlacklistBlock - STARFACE Custom Block
Ein Custom Block für den STARFACE Module Designer, der eingehende Anrufe gegen eine
konfigurierbare Blacklist prüft und anonyme Anrufe (unterdrückte Rufnummer) separat
behandelt. Pro Eintrag kann festgelegt werden, **was** mit dem Anruf passieren soll.
## Funktionsweise
Der Block wird **bei jedem eingehenden Anruf** ausgeführt (Modultyp *Call-Processing*).
Ablauf:
1. `GetCaller2` ermittelt Anrufernummer, Name und das Flag `isAnonymous`.
2. **Anonymer Anruf** (unterdrückte Rufnummer): es greift die *Aktion bei anonymen Anrufen*.
3. Andernfalls wird die Nummer gegen die **Blacklist-Einträge** geprüft (erster Treffer gewinnt).
4. Bei einem Treffer wird die zugehörige Aktion ausgeführt.
5. Kein Treffer / Aktion `NONE` / `ALLOW` → der Block tut nichts, der Anruf läuft normal weiter.
Die Aktionen nutzen die eingebauten STARFACE-Blöcke (`Hangup`, `Busy`, `CallPhonenumber2`,
`Answer` + `PlaybackResourceFile2`), die per Reflection im selben Anruf-Kontext aufgerufen
werden dadurch bleibt der Block über STARFACE-Versionen hinweg robust.
## Aktionen
| Aktion | Wirkung |
|------------|---------------------------------------------------------------------|
| `HANGUP` | Anruf sofort auflegen (Anrufer wird getrennt) |
| `BUSY` | Besetztzeichen senden |
| `REDIRECT` | Anruf auf eine andere Nummer/Nebenstelle umleiten (Ziel erforderlich)|
| `ANNOUNCE` | Anruf annehmen, Ansage abspielen, dann auflegen |
| `ALLOW` | Ausnahme Anruf trotz Treffer durchstellen (für Whitelist-Fälle) |
| `NONE` | (nur anonym) nichts tun, Anruf normal durchstellen |
## Kompatibilität
- STARFACE 8.x, 9.x, 10.x (Java 21)
## Konfigurationsfelder (Eingabe)
| Feld | Typ | Beschreibung | Default |
|------|-----|--------------|---------|
| `blacklistEntries` | STRING (mehrzeilig) | Nummern + optionale Aktion/Ziel, eine pro Zeile | |
| `defaultAction` | STRING (Auswahl) | Aktion für Einträge ohne eigene Aktion | `HANGUP` |
| `defaultRedirectTarget` | STRING | Ziel für REDIRECT-Einträge ohne eigenes Ziel | |
| `announcementFile` | STRING | Name der Sound-Ressource für `ANNOUNCE` | |
| `anonymousAction` | STRING (Auswahl) | Behandlung anonymer Anrufe | `NONE` |
| `anonymousRedirectTarget` | STRING | Ziel bei `REDIRECT` für anonyme Anrufe | |
| `countryCode` | STRING | Eigene Ländervorwahl (Deutschland = 49) | `49` |
| `prefixMatch` | BOOLEAN | Bereichssperre: Eintrag sperrt auch beginnende Nummern | `true` |
## Ausgabe-Variablen
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `caller` | STRING | Erkannte Anrufernummer (international normalisiert) |
| `isAnonymous` | BOOLEAN | Rufnummer unterdrückt? |
| `blocked` | BOOLEAN | Wurde der Anruf behandelt (geblockt/umgeleitet)? |
| `actionTaken` | STRING | Tatsächlich ausgeführte Aktion |
| `statusMessage` | STRING | Status-Meldung |
## Format der Blacklist-Einträge
Eine Nummer pro Zeile (Komma geht auch). Pro Zeile optional eine eigene Aktion und ein Ziel:
```
nummer;AKTION;ziel
```
Beispiele:
```
# Kommentarzeile, wird ignoriert
+49301234567 # nutzt die Standard-Aktion
0190;BUSY # dieser (Bereich) immer besetzt
004989;REDIRECT;100 # auf Nebenstelle 100 umleiten
+4972112345;ALLOW # Ausnahme: trotz Bereichssperre durchstellen
0900;ANNOUNCE # Ansage abspielen und auflegen
```
**Reihenfolge:** Der erste passende Eintrag gewinnt. `ALLOW`-Ausnahmen daher **vor**
breite Bereichssperren setzen.
## Nummern-Vergleich
Alle Nummern werden vor dem Vergleich in eine einheitliche internationale Ziffernform
gebracht (`countryCode` = 49):
| Eingabe | normalisiert |
|---------|--------------|
| `+49 721 12345` | `4972112345` |
| `0049 721 12345` | `4972112345` |
| `0721 12345` | `4972112345` |
| `100` (intern) | `100` |
Bei `prefixMatch = true` sperrt ein Eintrag auch alle Nummern, die mit ihm beginnen
(z.B. `0190` → gesamter `0190`-Bereich). Zum Schutz vor versehentlichem Über-Sperren
greift die Bereichssperre erst ab 3 Ziffern; kürzere Einträge werden nur exakt verglichen.
## Build
```bash
./build-block.sh
```
Die STARFACE-API-JARs werden in `libs/starface/` erwartet. Sind sie dort nicht vorhanden,
nutzt das Script automatisch die JARs des `mail2fax`-Moduls
(`../../mail2fax/v8-9-10/libs/starface/`). Ergebnis: `BlacklistBlock.class`.
Es werden **keine** zusätzlichen Bibliotheken benötigt (kein JavaMail o.ä.).
## Installation im Module Designer
Siehe **[INSTALLATION.md](INSTALLATION.md)** für die Schritt-für-Schritt-Anleitung.
</content>
+69
View File
@@ -0,0 +1,69 @@
#!/bin/bash
#
# Kompiliert den BlacklistBlock für den STARFACE Module Designer
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
echo "========================================"
echo " BlacklistBlock Kompilierung"
echo "========================================"
echo ""
# STARFACE JARs finden.
# Standard: ./libs/starface - Fallback: das mail2fax-Modul (gleiches STARFACE-API).
LIB_DIR="${STARFACE_LIBS:-libs/starface}"
if [ ! "$(ls -A "$LIB_DIR" 2>/dev/null)" ]; then
ALT="../../mail2fax/v8-9-10/libs/starface"
if [ "$(ls -A "$ALT" 2>/dev/null)" ]; then
echo "Keine JARs in $LIB_DIR - verwende $ALT"
LIB_DIR="$ALT"
fi
fi
CLASSPATH=""
for jar in "$LIB_DIR"/*.jar; do
if [ -f "$jar" ]; then
CLASSPATH="$CLASSPATH:$jar"
fi
done
CLASSPATH="${CLASSPATH:1}"
if [ -z "$CLASSPATH" ]; then
echo "FEHLER: Keine STARFACE JARs gefunden."
echo "Lege die STARFACE-API-JARs in $LIB_DIR ab oder setze STARFACE_LIBS=<pfad>."
echo "(Die JARs liegen bereits im mail2fax-Modul unter v8-9-10/libs/starface/)"
exit 1
fi
echo "Klassenpfad: $LIB_DIR"
echo "Kompiliere BlacklistBlock.java..."
javac -source 21 -target 21 \
-cp "$CLASSPATH" \
-proc:none \
BlacklistBlock.java
if [ -f "BlacklistBlock.class" ]; then
echo ""
echo "========================================"
echo " Erfolgreich!"
echo "========================================"
echo ""
ls -lh BlacklistBlock.class
echo ""
echo "Nächste Schritte:"
echo "1. STARFACE Admin -> Module -> Module Designer -> Neues Modul"
echo "2. Unter 'Ressourcen' BlacklistBlock.class hochladen"
echo " (optional: Ansage-WAV-Datei für die Aktion ANNOUNCE)"
echo "3. Eine Funktion 'Blacklist' anlegen und den Block hinzufügen"
echo "4. GUI-Felder anlegen und mit den @InputVar-Feldern verknüpfen"
echo "5. Modultyp 'Call-Processing', Aktivierung 'bei allen eingehenden Anrufen'"
echo "Details siehe INSTALLATION.md"
echo ""
else
echo "FEHLER: Kompilierung fehlgeschlagen"
exit 1
fi
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.