NextCloud cli interface for downloading vcf files

Hi,
I’m working on a phone assistant that answers incoming phone calls. when im busy,sleeping,unavailable. And it tells my status,redirects to phone or voicemail (or todo tells last location and cellphone status)

This system works with home assistant asterisk and some self made scripts. One of them returns the caller name and group from a phone number (as fast as possible Because it happens in side a voip call). Based on nextcloud vcf files. (the global one and groups)

Now i want to automate the downloading of the nextcloud vcf files. to update the callername script for asterisk.

I asked chatgtp and it said it was doable with a occ contacts command but that is not found. asking for the documentation of that feature resulted in a 404

it also came after some questions with a python script but that is not able to sign in because i cant find a way to get a Bearer token.

Is there any way to download vcf files in an automated matter?

Well i made a cypress script to download the contacts.

const host = Cypress.env("host");
const username = Cypress.env("username");
const password = Cypress.env("password");


describe('Contact and Contact Group Download', () => {
  beforeEach(() => {
    // Before each test, log in to the application
    cy.visit(host); 
    cy.get('#user').type(username); 
    cy.get('#password').type(password); // 
    cy.get('button[type="submit"]').click();
  });

   it('Download all contacts', () => {
    //Download full contacts and close and very close groups  
     cy.contains('Contacts').click();
     cy.get('.menu-icon').click();
     cy.get('[title="close"]').click();   
     cy.get('[title="close"]').parent().find('[aria-haspopup="menu"]').click();
      cy.get('[role="menuitem"]').filter(':visible').contains('Export').click();
     cy.get('[title="very close"]').click();
     cy.get('[title="very close"]').parent().find('[aria-haspopup="menu"]').click();
      cy.get('[role="menuitem"]').filter(':visible').contains('Export').click();
     cy.contains('Contacts settings').click();
     cy.get('[title="Contacts"]').parent().parent().find('.addressbook__menu').click();
     cy.contains('Download').click();
   });
});

But Nextcloud meets the CardDAV specifications! Exactly for this case. So you can simply use the API and you don’t have to take the detour via a cypress test.

Here some entry points, how you can speak with the carddav server:

  • create an app password in the security panel of your user settings.
  • retrieve basic information with:
curl -X PROPFIND -u 'Username:Password' -H 'Content-Type: text/xml; charset="utf-8"' --data '<?xml version="1.0" encoding="utf-8" ?><D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav"><D:prop><D:current-user-principal /><C:addressbook-home-set /></D:prop></D:propfind>' 'https://your-nextcloud-server/remote.php/dav/addressbooks/users/your-username/'

that gives you a list of your addressbooks.

  • become the url for the vcf file of your “contacts” Adress-Book:
curl -X PROPFIND -u 'Username:Password' -H 'Content-Type: text/xml; charset="utf-8"' --data '<?xml version="1.0" encoding="utf-8" ?><D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav"><D:prop><D:getetag /><D:displayname /><D:getcontenttype /></D:prop></D:propfind>' 'https://your-nextcloud-server/remote.php/dav/addressbooks/users/your-username/contacts/'

With that url, you get the .vcf file. (At least I got :wink:)

Of course you can put the credentials in a ~/.netrc file, then you should replace
-u 'Username:Password'
with a simple
-n

Please note that I’m not very up to date on the matter right now, it’s just like a shoot from the hip, meant to help you figure out the right path to a good and solid solution.
Wouldn’t that be great to build on? It could interact directly with your Asterisk solution.

I am looking forward to see, what you make out of it!

Much luck,
ernolf

1 Like

thanks.

I got a php script downloading the vcards now.

It is very slow downloading each vcard seperately.

#!/usr/bin/php
<?php
include("./vCard.php");
$username = "daft";
$password = "password";
$host = "https://cloud";

$vcardDir = "/opt/sascha/phonebook/vcards/";

$dbservername = "192.168.5.1";
$dbusername = "sascha";
$dbpassword = "password";
$dbname = "asterisk";
$phoneCode = "+31";
// Create a PDO database connection
$pdo = new PDO("mysql:host=$dbservername;dbname=$dbname", $dbusername, $dbpassword);



mkdir($vcardDir);

$files = glob($vcardDir.'/*');
foreach($files as $file) 
{
    if(is_file($file))
        unlink($file);
} 

exec("curl -X PROPFIND -u '".$username.":".$password."' -H 'Content-Type: text/xml; charset=\"utf-8\"' --data '<?xml version=\"1.0\" encoding=\"utf-8\" ?><D:propfind xmlns:D=\"DAV:\" xmlns:C=\"urn:ietf:params:xml:ns:carddav\"><D:prop><D:getetag /><D:displayname /><D:getcontenttype /></D:prop></D:propfind>' '".$host."/remote.php/dav/addressbooks/users/".$username."/contacts/' > ".$vcardDir."directory_listing.xml ");

exec("xmlstarlet sel -N D=\"DAV:\" -N C=\"urn:ietf:params:xml:ns:carddav\" -t -m \"//D:href\" -v . -n ".$vcardDir."directory_listing.xml > ".$vcardDir."vcard_urls.txt");

$command = "#!/bin/bash\n" .
    "while IFS= read -r url;\n" .
    "do\n" .
    "  if [ \"\$url\" != \"/remote.php/dav/addressbooks/users/$username/contacts/\" ]; then\n" .
    "    filename=$vcardDir\$(basename \"\$url\");\n" .
    "    curl -u '".$username.":".$password."' -o \"\$filename\" \"$host\$url\" &\n" .  // Use & to run the curl command in the background
    "  fi;\n" .
    "done < ".$vcardDir."vcard_urls.txt\n";

// Execute the modified command
exec($command);

// Optionally, you can wait for all background processes to finish
while (pcntl_waitpid(0, $status) != -1) {
    // Process the status of each background process if needed
}

// Set PDO to throw exceptions on errors
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$checkTableSQL = "SHOW TABLES LIKE 'phonebook'";
$result = $pdo->query($checkTableSQL);

if ($result->rowCount() == 0) {
// Create the phonebook table
    $createTableSQL = "CREATE TABLE phonebook (
        id INT AUTO_INCREMENT PRIMARY KEY,
        full_name VARCHAR(255),
        category VARCHAR(50),
        phone_type VARCHAR(50),
        phone_number VARCHAR(20),
        pref TINYINT(1)
    )";
    $pdo->exec($createTableSQL);
} else {
    $sql = "TRUNCATE TABLE phonebook";

    $stmt = $pdo->prepare($sql);

    // Execute the SQL query to truncate the table
    $stmt->execute();
}




// Loop through all vCard files in the directory
$vcardFiles = glob("./vcards/*.vcf");

// Loop through all vCard files
foreach ($vcardFiles as $vcardFile) {
    // Parse the vCard file
    $vcard = new vCard($vcardFile);

    // Check if vCard data is valid before accessing it
    if (!empty($vcard->fn[0])) {
        // Extract full name
        $fullName = (string)$vcard->fn[0];

        // Extract categories
        if (@$vcard->CATEGORIES[0]) {
           $categories = $vcard->CATEGORIES[0];

            // Check if "very close" exists in $vcard->CATEGORIES[0]
           if (in_array("very close", $categories)) {
               $category = "very close";
            } elseif (in_array("close", $categories)) {
               $category = "close";
           } else {
               $category = "all";
           }
        } else {
           $category = "all";
        }
        // Extract phone numbers
        $phoneNumbers = [];
        print_r($vcard->tel);
        $telCount = 0;
        foreach ($vcard->tel as $tel) {
            $pref = false;
             if (!empty($tel['Value'])) {
                foreach($tel['Type'] as $type) {
                   if ($type == 'pref') {
                      $pref = true;
                    } else {
	                $telType = $type;
                    }
                }
		$phone = (string)$tel['Value'];

		// Remove dashes and spaces
		$phone = str_replace(['-', ' '], '', $phone);

		// Check if the phone number starts with '00'
		if (strpos($phone, '00') === 0) {
		    // Replace '00' with '+'
		    $phone = '+' . substr($phone, 2);
		} elseif (strpos($phone, '0') === 0) {
		    // Replace '0' with $phoneCode
		    $phone = $phoneCode . substr($phone, 1);
		}

                $phoneNumbers[$telCount]['tel'] = $phone;
                $phoneNumbers[$telCount]['pref'] = $pref;
                $phoneNumbers[$telCount]['type'] = $telType;
	        $telCount++;
            }
        }
        // Output the extracted information
        echo "File: $vcardFile\n";
        echo "Full Name: $fullName\n";
        echo "Category: $category\n";
        foreach ($phoneNumbers as  $phoneNumber) {
            echo "Phone Number: $type ".$phoneNumber['tel']." ".$phoneNumber['pref']." \n";

           $sql = "INSERT INTO phonebook (full_name, category, phone_type, phone_number, pref) VALUES (:full_name, :category, :phone_type, :phone_number, :pref)";
           $stmt = $pdo->prepare($sql);
           $stmt->bindParam(':full_name', $fullName, PDO::PARAM_STR);
           $stmt->bindParam(':category', $category, PDO::PARAM_STR);
           $stmt->bindParam(':phone_type', $phoneNumber['type'], PDO::PARAM_STR);
           $stmt->bindParam(':phone_number', $phoneNumber['tel'], PDO::PARAM_STR);
           $stmt->bindParam(':pref', $phoneNumber['pref'], PDO::PARAM_INT);

           $stmt->execute();
        }
        echo "\n";
    }
}
?>
1 Like

This topic was automatically closed 8 days after the last reply. New replies are no longer allowed.