first commit
This commit is contained in:
Binary file not shown.
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Executable
+69
@@ -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.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user