Kontakte LDAP, iCloud, Gmail synchronisieren

Hallo.

Hat jemand von Euch eine Idee wie man die Kontakte von iCloud oder Gmail zu einem LDAP-Server synchronieren kann?

Hintergrund ist, dass ich eine 3CX-Soft-PBX am laufen habe welche schön über LDAP auf einen Lightweight Directory Dienst zugreifen kann. Damit kann ich herstellerübergreifend auf allen Endgeräten die Kontakte synchron halten. Nur die Smartphone-Welt schiesst da quer. Zumindest auf meinen iPhones kann ich LDAP zwar lesend einrichten aber offensichtlich nicht schreibend. Erfasse ich einen neuen Kontakt auf dem Smartphone kommt dieser daher nicht im Directory an.

Hallo,

ich habe das auch mal probiert, ich hatte früher eine Askozia (Asterisk-Derivat) Telefonanlage und jetzt auch eine 3CX (weil 3CX Askozia gekauft hat),

Und ich bin auh Apple-verseucht.

Die iCloud-Kontate nutze ich nicht, weil man die ja nicht zwischen mehreren Personen (=Familie) teilen kann. Also habe ich die Kontakte auf meiner Synology-NAS gespeichert (gibt das ein Kontakt-Modul)

Dann wollte ich für die Telefonanlage LDAP machen. Da braucht man ja erstmal einen LDAP-Server. Da habe ich den LDAP-Server der Synology genutzt (der nur für die Verwaltung von Accounts dient) und ein eigenes Schema gemacht für Adressen.
Für den Import der Adressen in LDAP gibt es Shell-Komandos (ldapsearch, ldapadd …) mit denen man Daten manipulieren kann.

So, dann wollte ich an die Adressen kommen … nichts gefunden, das automatisch zu machen. Nur manuell den Export aus
den Apple-Kontakten als .ldif-Datei. Dann mit einen php-Script aus dem Internet namens vcfconvert die .ldif-Datei analysiert und weiter mit den o.g. ldap-Kommandos in den LDAP-Server.

Klappte prima … bis dann mit einem Update auf der Synology plötzlich ein Teil der LDAP-Kommandos fehlten.

Da hat mich die Lust verlassen … :frowning: meine Telefonnummern sind auch relativ statisch.

Wenn ich Dir irgendwie helfen kann, gerne.

demel

ps: falls es Dich interessiert: ich habe für die 3cx ein IPS-Script, das eine Telefonliste erzeugt.
Dafür benutze ich das CDR-Interface der 3CX. Für Telefonnummern, die ich nicht in dem internen Telefonbuch der 3CXeingetragen habe, suche ich noch in öffentlichen Telefonbüchern.
Die Anrufe werden in einer geloggten Variable gespeichert und in einer HTML-Box-Variable als Tabelle ausgegeben.

Hi.

Das mit der Synology fällt leider aus. Die hängt bei mir in der Windows Domäne und damit ist der LDAP-Server der Syno aussen vor. Die 3CX fragt bei mir daher den LDAP-Server von meinem Windows Server ab.

An dem Skript für die Telefonliste hätte ich natürlich Interesse. Die 3CX läuft bei mir noch nicht so lange und ich habe die Integration in IPS daher noch nicht wirklich auf dem Radar gehabt.

Ok, damit hast Du ja einen LDAP-Server. Dann musste du den ggfs. um ein Schema erweitern, um die Adressen/Telefonnummern speichern zu können (kann sein, das der schon ein solches Schema hat). Ein solches Schema habe ich Dir angehängt.

Das Füllen vom LDAP mit Daten, dafür gibt es ja diese LDAP-Programm resp. auch ldap-php-Erweiterungen.

Bleibt das Problem, an die Kontakte zu kommen, da - wie gesagt - habe ich nichts anderes gefunden als die regelmäßig von Hand komplett zu exportieren und abzugleichen. Parsen des Formates eben mit vcfconvert

Ok, also

  1. auf der 3CX unter Einstellung KDS den Export aktivieren


    alle Felder zum Export aktivieren oder Script #1 anpassen

  2. Server-Socket aktivieren


  3. eine RegisterVaiaible anlegen

  4. Dazu 2 Variablen und 2 Scripte


    Variable „Record“: Typ String, protokolliert
    Variable „Telefonliste“: HTML-Box

Script 1 „CDR auswerten“


<?

$scriptName = IPS_GetName($_IPS['SELF']) . '(' . $_IPS['SELF'] . ')';

$cols = [
		'historyid',
		'callid',
		'duration',
		'time-start',
		'time-answered',
		'time-end',
		'reason-terminated',
		'from-no',
		'to-no',
		'from-dn',
		'to-dn',
		'dial-no',
		'reason-changed',
		'final-number',
		'final-dn',
		'bill-code',
		'bill-rate',
		'bill-cost',
		'bill-name',
		'chain',
		'from-type',
		'to-type',
		'final-type',
		'from-dispname',
		'to-dispname',
		'final-dispname',
	];

if ($_IPS['SENDER'] == "RegisterVariable") {
    $data = $_IPS['VALUE'];
	if ($data != '') {
		$data = preg_replace('%(
)%', '', $data);
		$flds = explode(',', $data);
		$cdr = [];
		for ($idx = 0; $idx < sizeof($flds); $idx++) {
			if (isset($cols[$idx])) {
				$col = $cols[$idx];
				$val = $flds[$idx];
				$cdr[$col] = $val;
			}
		}
		IPS_LogMessage($scriptName, 'cdr=' . print_r($cdr, true));
		$msg = 'call ' . $cdr['callid'] . ': from=' . $cdr['from-dispname'] . ', to=' . $cdr['to-dispname'] . ' => ' . $cdr['reason-terminated'];
		IPS_LogMessage($scriptName, $msg);
		SetValueString(47610 /*[3CX - Call-Data-Records\Record]*/, json_encode($cdr));
	}
}

Script 2: „Telefonliste erstellen (HTML-Box)“

<?

function telno2name($telno)
{
	if (!preg_match('/^0/', $telno, $regs)) {
		$telno = '02327' . $telno;
	}
	
	$names = [ 'FirstName', 'LastName', 'Company', 'Mobile', 'Mobile2', 'Home', 'Home2', 'Business', 'Business2', 'Email', 'Other', 'BusinessFax', 'HomeFax', 'Pager' ];
	$tn_names = [ 'Mobile', 'Mobile2', 'Home', 'Home2', 'Business', 'Business2', 'BusinessFax', 'HomeFax', 'Pager' ];
	
	$data = IPS_GetMediaContent(13550 /*[Telefon: Kontakte]*/);
	$data = base64_decode($data);
	$rows = explode("
", $data);
	$n_row = 0;
	$nm = '';
	foreach ($rows as $row) {
		if (!$n_row++) {
			continue;
		}
	    $fields = str_getcsv($row, ',');
	    $line = [];
	    for ($i = 0; $i < sizeof($names); $i++) {
	        $line[$names[$i]] = isset($fields[$i]) ? $fields[$i] : '';
	    }
		foreach ($tn_names as $tn_name) {
			$tn = $line[$tn_name];
			if ($tn == '')
				continue;
			if (!strcmp($tn, $telno)) {
				$nm = $line['LastName'];
				if ($line['FirstName'] != '') {
					$nm = $line['FirstName'] . ($nm != '' ? ' ' : '' ). $nm;
				}
				if ($nm == '') {
					$nm = $line['Company'];
				}
				break;
			}
		}
		if ($nm != '')
			break;
	}
	return $nm;
}

function reserve_search($telno)
{
	$displayname = '';
	
	// nationale Nummern ohne +49
	$telno = preg_replace('/^\+49/', '0', $telno);
	// internationale Nummern ohne +
	$telno = preg_replace('/^\+/', '00', $telno);
	// ggfs Ortsvorwahl dazu
	if (!preg_match('/^0/', $telno, $regs)) {
		$telno = '02327' . $telno;
	}
	
	// Das Örtliche

	$url = 'https://www.dasoertliche.de/Controller?form_name=search_inv&la=de&ph=' . $telno;
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_TIMEOUT, 1);
	$cdata = curl_exec($ch);
	curl_close($ch);
	
	if ($cdata) {
		if (preg_match('/var handlerData = [\[][\[]([^;]*)[\]][\]];/', $cdata, $regs)) {
			$fields = str_getcsv($regs[1], ',', '\'');
			if (isset($fields[14])) {
				$displayname = utf8_decode($fields[14]);
			}
		}
	}
	
	if (strlen($displayname)) {
	    return $displayname; 
	}

	// Das Telefonbuch
	$url = 'https://www.dastelefonbuch.de/' . rawurlencode('Rückwärts-Suche') . '/' . $telno;
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_TIMEOUT, 1);
	$cdata = curl_exec($ch);
	curl_close($ch);
	
	if ($cdata) {
		if (preg_match('/data-entry-data="([^"]*)"/', $cdata, $regs)) {
			// echo 'regs=' . print_r($regs, true) . PHP_EOL;
			$rr = explode('&', $regs[1]);
			foreach ($rr as $r) {
				$p = explode('=', $r);
				if ($p[0] == 'na') {
					$displayname = utf8_decode(urldecode($p[1]));
					break;
				}
			}
		}
	}
	
	if (strlen($displayname)) {
	    return $displayname; 
	}
	
	return false;
}
	
function build_name($dispname, $telno, $type)
{
	$name = $dispname;
	if ($name == $telno) {
		$s = telno2name($telno);
		if ($s == '') {
			$s = reserve_search($telno);
		}
		if ($s != '') {
			$name = $s;
		}
	}
	if ($type != 'Extension' && $telno != '' && $name != $telno) {
		$name .= ' (' . $telno . ')';
	}
	return $name;
}

function complete_record($cdr)
{
	$time_start = $cdr['time-start'];
	$tm = date_create_from_format('Y.m.d H:i:s e', $time_start . ' UTC');
	$cdr['timestamp'] = $tm ? $tm->format('U') : 0;
	
	$from_type = $cdr['from-type'];
	$from_no = $cdr['from-no'];
	$from_dispname = $cdr['from-dispname'];
	
	$to_type = $cdr['to-type'];
	$to_no = $cdr['to-no'];
	$to_dispname = $cdr['to-dispname'];

	$dir = $from_type == 'Line' ? 'in' : 'out';
	$cdr['direction'] = $dir;
	
	$duration = $cdr['duration'];
	
	$reason_terminated = $cdr['reason-terminated'];
	switch ($reason_terminated) {
		case 'TerminatedByDst':
		case 'TerminatedBySrc':
			$img = $dir == 'in' ? 'tel_call-in.gif' : 'tel_call-out.gif';
			$txt = 'Gespräch normal beendet';
			break;
		case 'Failed':
		case 'Failed_Cancelled':
		case 'TerminatedByRule':
			$img = $dir == 'in' ? 'tel_call-in_failed.gif' : 'tel_call-out_failed.gif';
			$txt = $duration == '' ? 'Gespräch nicht zustande gekommen' : 'Gespräch abgebrochen';
			break;
		case 'TargetBusy':
			$img = $dir == 'in' ? 'tel_call-in_failed.gif' : 'tel_call-out_failed.gif';
			$txt = 'Gesprächspartner besetzt';
			break;
		case 'TargetNotFound':
			$img = $dir == 'in' ? 'tel_call-in_failed.gif' : 'tel_call-out_failed.gif';
			$txt = 'Telefonnummer nicht erreichbar';
			break;
		default:
			$img = 'tel_error.png';
			$txt = 'Unbekannter Status (' . $reason_terminated . ')';
			break;
	}
	$cdr['status_img'] = 'user/media' . DIRECTORY_SEPARATOR . $img;
	$cdr['status_txt'] = $txt;

	$cdr['source'] = build_name($from_dispname, $from_no, $from_type);
	$cdr['target'] = build_name($to_dispname, $to_no, $to_type);
	
	return $cdr;
}

$from = time() - (7 * 24 * 60 * 60);;
$values = AC_GetLoggedValues(17849 /*[Archive]*/, 47610 /*[3CX - Call-Data-Records\Record]*/, $from, 0, 0);
$cdrs = [];
foreach ($values as $value) {
	$cdr = json_decode($value['Value'], true);
	$cdrs[] = complete_record($cdr);
}

$html = '';
$html .= '<style>' . PHP_EOL;
$html .= 'body { margin: 1; padding: 0; font-family: "Open Sans", sans-serif; font-size: 20px; }' . PHP_EOL;
$html .= 'table { border-collapse: collapse; border: 0px solid; margin: 0.5em;}' . PHP_EOL;
$html .= 'th, td { padding: 1; }' . PHP_EOL;
$html .= 'thead, tdata { text-align: left; }' . PHP_EOL;
$html .= '#spalte_icon { width: 25px; }' . PHP_EOL;
$html .= '#spalte_zeitpunkt { width: 125px; }' . PHP_EOL;
$html .= '#spalte_quelle { width: 400px; }' . PHP_EOL;
$html .= '#spalte_ziel { width: 400px; }' . PHP_EOL;
$html .= '#spalte_dauer { width: 20px; }' . PHP_EOL;
$html .= '</style>' . PHP_EOL;

$cdr_n = sizeof($cdrs);
$b = false;
if ($cdr_n) {
	foreach ($cdrs as $cdr) {
		$timestamp = $cdr['timestamp'];
		$duration = $cdr['duration'];
		$img = $cdr['status_img'];
		$txt = $cdr['status_txt'];
		$source = $cdr['source'];
		$target = $cdr['target'];
		
		if ($duration == '') {
			$duration = '-';
		}
		
		$dt = date('d.m. H:i', $timestamp);
		
		if (!$b) {
            $html .= '<table>' . PHP_EOL;
			$html .= '<colgroup><col id="spalte_icon"></colgroup>' . PHP_EOL;
            $html .= '<colgroup><col id="spalte_zeitpunkt"></colgroup>' . PHP_EOL;
			$html .= '<colgroup><col id="spalte_quelle"></colgroup>' . PHP_EOL;
			$html .= '<colgroup><col id="spalte_ziel"></colgroup>' . PHP_EOL;
			$html .= '<colgroup><col id="spalte_dauer"></colgroup>' . PHP_EOL;
			$html .= '<colgroup></colgroup>' . PHP_EOL;
            $html .= '<thead>' . PHP_EOL;
            $html .= '<tr>' . PHP_EOL;
            $html .= '<th> </th>' . PHP_EOL;
            $html .= '<th>Zeitpunkt</th>' . PHP_EOL;
			$html .= '<th>Anrufer</th>' . PHP_EOL;
			$html .= '<th>Ziel</th>' . PHP_EOL;
			$html .= '<th>Dauer</th>' . PHP_EOL;
			$html .= '<th>&nbsp</th>' . PHP_EOL;
            $html .= '</tr>' . PHP_EOL;
            $html .= '</thead>' . PHP_EOL;
            $html .= '<tdata>' . PHP_EOL;
            $b = true;
        }

        $html .= '<tr>' . PHP_EOL;
		$html .= '<td><img src=' . $img . ' width="18" height="18" title="' . $txt . '"</td>' . PHP_EOL;
        $html .= '<td>' . $dt . '</td>' . PHP_EOL;
		$html .= '<td>' . $source . '</td>' . PHP_EOL;
		$html .= '<td>' . $target . '</td>' . PHP_EOL;
        $html .= '<td>' . $duration . '</td>' . PHP_EOL;
        $html .= '</tr>' . PHP_EOL;
	}
	if ($b) {
        $html .= '</tdata>' . PHP_EOL;
        $html .= '</table>' . PHP_EOL;
    } else {
		$html .= '<center>keine Anrufe</center><br>' . PHP_EOL;
	}
	$html .= '</body>' . PHP_EOL;
    $html .= '</html>' . PHP_EOL;
}

SetValueString(43833 /*[3CX - Call-Data-Records\Telefonliste]*/, $html);

  1. ein Media-Objekt „Telefon: Kontakte“.
    Dazu habe ich einfach alle Kontakte, die ich in der 3CX habe, als CSV exportiert und diese cdv-Datein in IPS geladen.

Weil meine Daten ja ziemlich statisch sind und das mit LDAP ja eben irgendwann nicht mehr funktionierte habe ich meine Telefonnummern in 3CX gepflegt.
Wen das nicht benötigt wird, ist das Media-Objekt und die Auswertung in telefon2name in script #2 überflüssig.

Ich hoffe, es ist nicht zu verwirrend

demel

mozillaAbPersonAlpha.txt (5.14 KB)