Configurar la API de Gmail para reemplazar la extensión PHP IMAP y trabajar con el protocolo OAuth2

Una vez uno de los afortunados, no está preparado para el hecho de que a partir del 15 de febrero de 2021 la autorización para Gmail y otros productos solo se ejecutará a través de OAuth, leí el artículo " Google entierra la extensión PHP IMAP " y tristemente comencé a tomar medidas sobre el reemplazo de la extensión PHP IMAP su proyecto en la API de Google. Había más preguntas que respuestas, así que escribí un manual al mismo tiempo.



Tengo PHP IMAP utilizado para las siguientes tareas:



  1. Retirar cartas viejas de los buzones . Desafortunadamente, en el panel de control de la cuenta corporativa de G Suite, solo se puede configurar el período para eliminar mensajes de todos los buzones de correo de la organización después de N días posteriores a la recepción. Sin embargo, necesito eliminar cartas solo en buzones de correo especificados y después de una cantidad diferente de días después de la recepción.
  2. Filtrar, analizar y marcar letras . Muchas cartas se envían automáticamente desde nuestro sitio, algunas de las cuales no llegan a los destinatarios, sobre las cuales, en consecuencia, llegan informes. Es necesario capturar estos informes, desmontarlos, encontrar un cliente por correo electrónico y formar una carta legible por humanos para el gerente, para que pueda contactar al cliente y aclarar la relevancia de la dirección de correo electrónico.


Resolveremos estas dos tareas usando la API de Gmail en este artículo (y al mismo tiempo deshabilitaremos el acceso para aplicaciones inseguras en la configuración del buzón, que estaba habilitado para que PHP IMAP funcionara y, de hecho, dejará de funcionar en un día terrible en febrero de 2021). Usaremos la llamada cuenta de servicio de la aplicación Gmail, que, si se configura en consecuencia, permite conectarse a todos los buzones de correo de la organización y realizar cualquier acción en ellos.



1. Cree un proyecto en la consola para desarrolladores de API de Google.



Con la ayuda de este proyecto, realizaremos la interacción API con Gmail, y en él crearemos la misma cuenta de servicio.



Para crear un proyecto:



  1. Vaya a la consola para desarrolladores de la API de Google e inicie sesión como administrador de G Suite (bueno, o quién es su usuario allí con todos los derechos)
  2. Buscamos el botón "Crear proyecto".



    Encontré aquí:
    image



    Y luego aquí:
    image



    Complete el nombre del proyecto y guarde:



    Creación de proyectos
    image



  3. Vaya al proyecto y haga clic en el botón "Habilitar API y servicios":



    Habilitar API y servicios
    image



    Elegir la API de Gmail



2. Crea y configura una cuenta de servicio



Para hacer esto, puede usar el manual oficial o continuar leyendo:



  1. Vaya a nuestra API de Gmail agregada, haga clic en el botón "Crear credenciales" y seleccione "Cuenta de servicio":



    Creación de cuenta de servicio
    image



    Complete algo y haga clic en "Crear":



    Detalles de la cuenta de servicio
    image



    Todo lo demás se puede dejar en blanco:



    Derechos de acceso para la cuenta de servicio
    image



    image



  2. , . G Suite, « — API».



    API
    image

    image



  3. « »:



    image



    «», « » , « OAuth» — :



    - https://mail.google.com/ -

    - https://www.googleapis.com/auth/gmail.modify -

    - https://www.googleapis.com/auth/gmail.readonly -

    - https://www.googleapis.com/auth/gmail.metadata -




    image

    image



  4. « G Suite»:



    image



    Y también complete el nombre de su producto en el campo a continuación.

  5. Ahora necesita crear una clave de cuenta de servicio: este es un archivo que debería estar disponible en su aplicación. Él, de hecho, se utilizará para la autorización.



    Para hacer esto, desde la página "Credenciales" de su proyecto, siga el enlace "Administrar cuentas de servicio":



    Cartas credenciales
    image



    y seleccione "Acciones - Crear clave", escriba: JSON:



    Gestión de cuentas de servicio
    image



    Después de eso, se generará un archivo de clave y se descargará en su computadora, que debe colocarse en su proyecto y debe tener acceso cuando llame a la API de Gmail.



Esto completa la configuración de la API de Gmail, luego habrá un poco de mi código cocoa, de hecho, implementando las funciones que han sido resueltas por la extensión PHP IMAP hasta ahora.



3. Escribiendo el código



Hay documentación oficial bastante buena ( haga clic y haga clic ) sobre la API de Gmail , que utilicé. Pero desde que comencé a escribir un manual detallado, adjuntaré mi propio código de cacao.



Entonces, en primer lugar, instalamos Google Client Library (apiclient) usando composer:



composer require google/apiclient



(Al principio, como un verdadero experto literario, instalé la versión 2.0 del cliente api, como se indica en PHP Quickstart , pero al principio, todo tipo de vornings y alarmas recayeron en PHP 7.4 , por lo que no te aconsejo que hagas lo mismo)



Luego, en base a ejemplos de la documentación oficial, escribimos nuestra propia clase para trabajar con Gmail, sin olvidar especificar el archivo de clave de la cuenta de servicio:



Clase para trabajar con Gmail
<?php
//     Gmail
class GmailAPI
{
    private $credentials_file = __DIR__ . '/../Gmail/credentials.json'; //   

    // ---------------------------------------------------------------------------------------------
    /**
     *   Google_Service_Gmail Authorized Gmail API instance
     *
     * @param  string $strEmail  
     * @return Google_Service_Gmail Authorized Gmail API instance
     * @throws Exception
     */
    function getService(string $strEmail){
        //    
        try{
            $client = new Google_Client();
            $client->setAuthConfig($this->credentials_file);
            $client->setApplicationName('My Super Project');
            $client->setScopes(Google_Service_Gmail::MAIL_GOOGLE_COM);
            $client->setSubject($strEmail);
            $service = new Google_Service_Gmail($client);
        }catch (Exception $e) {
            throw new \Exception('   getService: '.$e->getMessage());
        }
        return $service;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *    ID    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrOptionalParams      
     *         Gmail  after: 2020/08/20 in:inbox label:
     *      q  $opt_param
     * @return array  ID     array('arrErrors' => $arrErrors),   
     * @throws Exception
     */
    function listMessageIDs(Google_Service_Gmail $service, string $strEmail, array $arrOptionalParams = array()) {
        $arrIDs = array(); //  ID 

        $pageToken = NULL; //     
        $messages = array(); //    

        //  
        $opt_param = array();
        //    ,       Gmail      q
        if (count($arrOptionalParams)) $opt_param['q'] = str_replace('=', ':', http_build_query($arrOptionalParams, null, ' '));

        //   ,   ,     
        do {
            try {
                if ($pageToken) {
                    $opt_param['pageToken'] = $pageToken;
                }
                $messagesResponse = $service->users_messages->listUsersMessages($strEmail, $opt_param);
                if ($messagesResponse->getMessages()) {
                    $messages = array_merge($messages, $messagesResponse->getMessages());
                    $pageToken = $messagesResponse->getNextPageToken();
                }
            } catch (Exception $e) {
                throw new \Exception('   listMessageIDs: '.$e->getMessage());
            }
        } while ($pageToken);

        //   ID  
        if (count($messages)) {
            foreach ($messages as $message) {
                $arrIDs[] = $message->getId();
            }
        }
        return $arrIDs;
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *      ID  batchDelete
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  array $arrIDs  ID      listMessageIDs
     * @throws Exception
     */
    function deleteMessages(Google_Service_Gmail $service, string $strEmail, array $arrIDs){
        //      1000 ,      batchDelete
        $arrParts = array_chunk($arrIDs, 999);
        if (count($arrParts)){
            foreach ($arrParts as $arrPartIDs){
                try{
                    //     
                    $objBatchDeleteMessages = new Google_Service_Gmail_BatchDeleteMessagesRequest();
                    //   
                    $objBatchDeleteMessages->setIds($arrPartIDs);
                    //  
                    $service->users_messages->batchDelete($strEmail,$objBatchDeleteMessages);
                }catch (Exception $e) {
                    throw new \Exception('   deleteMessages: '.$e->getMessage());
                }
            }
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     get
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  string $strFormat The format to return the message in.
     * Acceptable values are:
     * "full": Returns the full email message data with body content parsed in the payload field; the raw field is not used. (default)
     * "metadata": Returns only email message ID, labels, and email headers.
     * "minimal": Returns only email message ID and labels; does not return the email headers, body, or payload.
     * "raw": Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.
     * @param  array $arrMetadataHeaders When given and format is METADATA, only include headers specified.
     * @return  object Message
     * @throws Exception
     */
    function getMessage(Google_Service_Gmail $service, string $strEmail, string $strMessageID, string $strFormat = 'full', array $arrMetadataHeaders = array()){
        $arrOptionalParams = array(
            'format' => $strFormat // ,    
        );
        //   - metadata,     
        if (($strFormat == 'metadata') and count($arrMetadataHeaders))
            $arrOptionalParams['metadataHeaders'] = implode(',',$arrMetadataHeaders);

        try{
            $objMessage = $service->users_messages->get($strEmail, $strMessageID,$arrOptionalParams);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   getMessage: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *   ,    
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @return  object $objLabels -  -  
     * @throws Exception
     */
    function listLabels(Google_Service_Gmail $service, string $strEmail){
        try{
            $objLabels = $service->users_labels->listUsersLabels($strEmail);
            return $objLabels;
        }catch (Exception $e) {
            throw new \Exception('   listLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

    /**
     *     ()  
     *
     * @param  Google_Service_Gmail $service Authorized Gmail API instance.
     * @param  string $strEmail  
     * @param  string $strMessageID ID 
     * @param  array $arrAddLabelIds  ID ,     
     * @param  array $arrRemoveLabelIds  ID ,     
     * @return  object Message -  
     * @throws Exception
     */
    function modifyLabels(Google_Service_Gmail $service, string $strEmail, string $strMessageID, array $arrAddLabelIds = array(), array $arrRemoveLabelIds = array()){
        try{
            $objPostBody = new Google_Service_Gmail_ModifyMessageRequest();
            $objPostBody->setAddLabelIds($arrAddLabelIds);
            $objPostBody->setRemoveLabelIds($arrRemoveLabelIds);
            $objMessage = $service->users_messages->modify($strEmail,$strMessageID,$objPostBody);
            return $objMessage;
        }catch (Exception $e) {
            throw new \Exception('   modifyLabels: '.$e->getMessage());
        }
    }
    // ---------------------------------------------------------------------------------------------

}




Siempre que interactuamos con Gmail, lo primero que hacemos es llamar a la función getService ($ strEmail) de la clase GmailAPI, que devuelve un objeto "autorizado" para trabajar con el buzón $ strEmail. Además, este objeto ya se pasa a cualquier otra función para realizar directamente las acciones que necesitamos. Todas las demás funciones de la clase GmailAPI ya realizan tareas específicas:



  • listMessageIDs: busca mensajes de acuerdo con los criterios especificados y devuelve su ID (la cadena de búsqueda que se pasa a la función de API de Gmail listUsersMessages debe ser similar a la cadena de búsqueda en la interfaz web del buzón),
  • deleteMessages: elimina los mensajes con ID que se le han pasado (la función batchDelete API Gmail elimina no más de 1000 mensajes en una sola pasada, por lo que tuve que dividir la matriz de ID pasados ​​a la función en varias matrices de 999 letras cada una y realizar la eliminación varias veces),
  • getMessage: obtiene toda la información sobre el mensaje con el ID que se le ha pasado,
  • listLabels: devuelve una lista de banderas en el buzón (lo usé para obtener la ID de la bandera que se creó originalmente en la interfaz web del buzón y se asigna a los mensajes deseados)
  • modificarLabels: agregar o eliminar banderas al mensaje


A continuación, tenemos la tarea de eliminar cartas antiguas en varios buzones de correo. Al mismo tiempo, consideramos las cartas antiguas recibidas hace su número de días para cada buzón. Para realizar esta tarea, escribimos el siguiente script, que cron ejecuta diariamente:



Eliminar correos electrónicos antiguos
<?php
/**
 *      Gmail
 *      
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

//       
$arrMailBoxesForClean = array(
    'a@domain.com' => 30,
    'b@domain.com' => 30,
    'c@domain.com' => 7,
    'd@domain.com' => 7,
    'e@domain.com' => 7,
    'f@domain.com' => 1
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail

//     ,      
foreach ($arrMailBoxesForClean as $strEmail => $intDays) {
    try{
        //    
        $service = $objGmailAPI->getService($strEmail);
        //       
        $arrParams = array('before' => date('Y/m/d', (time() - 60 * 60 * 24 * $intDays)));
        //   ,   
        $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail,$arrParams);
        //     ID   $arrIDs
        if (count($arrIDs)) $objGmailAPI->deleteMessages($service,$strEmail,$arrIDs);
        //    
        unset($service,$arrIDs);
    }catch (Exception $e) {
        $arrErrors[] = $e->getMessage();
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '       ';
    $strMessage = '         :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




La secuencia de comandos se conecta a cada buzón de correo especificado, selecciona cartas antiguas y las elimina.



La tarea de generar informes para el administrador sobre correos electrónicos no entregados basados ​​en informes automáticos se resuelve mediante el siguiente script:



Filtrar y marcar correos electrónicos
<?php
/*
 *    a@domain.com
 *      ,     : : mailer-daemon@googlemail.com
 *      .        ,   b@domain.com
 *   
 */
require __DIR__ .'/../general/config/config.php'; //   
require __DIR__ .'/../vendor/autoload.php'; //   

$strEmail = 'a@domain.com';
$strLabelID = 'Label_2399611988534712153'; //  reportProcessed -    

//  
$arrParams = array(
    'from' => 'mailer-daemon@googlemail.com', //       
    'in' => 'inbox', //  
    'after' => date('Y/m/d', (time() - 60 * 60 * 24)), //   
    'has' => 'nouserlabels' //  
);

$arrErrors = array(); //  
$objGmailAPI = new GmailAPI(); //     GMail
$arrClientEmails = array(); //    ,      

try{
    //    
    $service = $objGmailAPI->getService($strEmail);
    //         ,    
    $arrIDs = $objGmailAPI->listMessageIDs($service,$strEmail, $arrParams);
    //      'X-Failed-Recipients',    ,      
    if (count($arrIDs)){
        foreach ($arrIDs as $strMessageID){
            //   
            $objMessage = $objGmailAPI->getMessage($service,$strEmail,$strMessageID,'metadata',array('X-Failed-Recipients'));
            //  
            $arrHeaders = $objMessage->getPayload()->getHeaders();
            //  
            foreach ($arrHeaders as $objMessagePartHeader){
                if ($objMessagePartHeader->getName() == 'X-Failed-Recipients'){
                    $strClientEmail = mb_strtolower(trim($objMessagePartHeader->getValue()), 'UTF-8');
                    if (!empty($strClientEmail)) {
                        if (!in_array($strClientEmail, $arrClientEmails)) $arrClientEmails[] = $strClientEmail;
                    }
                    //    reportProcessed,       
                    $objGmailAPI->modifyLabels($service,$strEmail,$strMessageID,array($strLabelID));
                }
            }
        }
    }
    unset($service,$arrIDs,$strMessageID);
}catch (Exception $e) {
    $arrErrors[] = $e->getMessage();
}

//     ,      ,    
if (count($arrClientEmails)) {
    $objClients = new clients();
    //   email  
    $arrAllClientsEmails = $objClients->getAllEmails();

    foreach ($arrClientEmails as $strClientEmail){
        $arrUsages = array();
        foreach ($arrAllClientsEmails as $arrRow){
            if (strpos($arrRow['email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['email2'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
            if (strpos($arrRow['site_user_settings_contact_email'], $strClientEmail) !== false) {
                $arrUsages[] = '  email  "<a href="'.MANAGEURL.'?m=admin&sm=clients&edit='.$arrRow['s_users_id'].'">'.$arrRow['name'].'</a>"';
            }
        }
        $intUsagesCnt = count($arrUsages);
        if ($intUsagesCnt > 0){
            $strMessage = '          <span style="color: #000099;">'.$strClientEmail.'</span><br/>
                  ';
            if ($intUsagesCnt == 1){
                $strMessage .= ' '.$arrUsages[0].'<br/>';
            }else{
                $strMessage .= ':<ul>';
                foreach ($arrUsages as $strUsage){
                    $strMessage .= '<li>'.$strUsage.'</li>';
                }
                $strMessage .= '</ul>';
            }
            $strMessage .= '<br/>,        .<br/><br/>
                    ,    ';
            if (empty($objMailSender)) $objMailSender = new mailSender();
            $objMailSender->sendMail('b@domain.com',' email ',$strMessage);
        }
    }
}

if (count($arrErrors)){
    $strTo = 'my_email@domain.com';
    $strSubj = '      ';
    $strMessage = '        :'.
        '<ul><li>'.implode('</li><li>',$arrErrors).'</li></ul>'.
        '<br/>URL: '.filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
    if (empty($objMailSender)) $objMailSender = new mailSender();
    $objMailSender->sendMail($strTo,$strSubj,$strMessage);
}




Este script, como el primero, se conecta al buzón especificado, selecciona las cartas necesarias (informes sobre mensajes no entregados) sin una marca, encuentra en la carta la dirección de correo electrónico a la que se intentó enviar la carta y marca esta carta con la marca "Procesada". ... Luego, se realizan manipulaciones con la dirección de correo electrónico encontrada, como resultado de lo cual se forma una carta legible para el empleado responsable.



Las fuentes están disponibles en GitHub .



Eso es todo lo que quería contar en este artículo. ¡Gracias por leer! Si mi código le picó en los ojos, simplemente enrolle el spoiler o escriba sus comentarios, estaré encantado de recibir críticas constructivas.



All Articles