enviar correos correo con php gmail-api

correos - phpmailer gmail



No se puede obtener el cuerpo del correo electrónico con la API de PHP de Gmail (6)

Tengo problemas con la API de PHP de Gmail.

¡Quiero recuperar el contenido del cuerpo de los correos electrónicos, pero puedo recuperarlo solo para los correos electrónicos que tienen archivos adjuntos! Mi pregunta es por qué

Aquí está mi código hasta ahora:

// Authentication things above... $client = getClient(); $gmail = new Google_Service_Gmail($client); $list = $gmail->users_messages->listUsersMessages(''me'', [''maxResults'' => 1000]); while ($list->getMessages() != null) { foreach ($list->getMessages() as $mlist) { $message_id = $mlist->id; $optParamsGet2[''format''] = ''full''; $single_message = $gmail->users_messages->get(''me'', $message_id, $optParamsGet2); $threadId = $single_message->getThreadId(); $payload = $single_message->getPayload(); $headers = $payload->getHeaders(); $parts = $payload->getParts(); //print_r($parts); PRINTS SOMETHING ONLY IF I HAVE ATTACHMENTS... $body = $parts[0][''body'']; $rawData = $body->data; $sanitizedData = strtr($rawData,''-_'', ''+/''); $decodedMessage = base64_decode($sanitizedData); //should display my body content } if ($list->getNextPageToken() != null) { $pageToken = $list->getNextPageToken(); $list = $gmail->users_messages->listUsersMessages(''me'', [''pageToken'' => $pageToken, ''maxResults'' => 1000]); } else { break; } }

La segunda opción para recuperar contenido que conozco es mediante el uso del fragmento ubicado en la parte Encabezados , pero solo recupera los 50 primeros caracteres, lo que no es muy útil.


Una solución simple y robusta

No estaba satisfecho con las otras respuestas porque todas son defectuosas (elaboración en spoiler), y algunas son largas y están mezcladas con características que el autor de la pregunta (y yo) no buscó.

Para advertirle sobre posibles problemas en otras respuestas:
sin respaldo de texto sin formato
o no tratar con el cuerpo del mensaje falsy : la cadena ''0'' (es poco probable que suceda, pero no demasiado improbable)
o falta una búsqueda lo suficientemente profunda a través de la estructura de árbol de carga útil

Así que pensé que ahorraría a otros el problema y compartiría mi código (probado en toda mi bandeja de entrada).

// input: the message object (not the payload!) // output: html or plain text function msg_body($msg) { $body = msg_body_recursive($msg->payload); return array_key_exists(''html'', $body) ? $body[''html''] : $body[''plain'']; } function msg_body_recursive($part) { if($part->mimeType == ''text/html'') { return [''html'' => decodeBody($part->body->data)]; } else if($part->mimeType == ''text/plain'') { return [''plain'' => decodeBody($part->body->data)]; } else if($part->parts) { $return = []; foreach($part->parts as $sub_part) { $result = msg_body_recursive($sub_part); $return = array_merge($return, $result); if(array_key_exists(''html'', $return)) break; } return $return; } return []; } function decodeBody($encoded) { $sanitizedData = strtr($encoded,''-_'', ''+/''); return base64_decode($sanitizedData); }


Como una mejora adicional, el código debe ser recursivo, también debe cargar el mensaje en formato "completo" para extraer el cuerpo. Debajo de tres funciones puedes poner en tu propia clase.

private function decodeBody($body) { $rawData = $body; $sanitizedData = strtr($rawData,''-_'', ''+/''); $decodedMessage = base64_decode($sanitizedData); if(!$decodedMessage) return false; return $decodedMessage; } private function decodeParts($parts) { foreach ($parts as $part) { if ($part->getMimeType() === ''text/html'' && $part->getBody()) if ($result = $this->decodeBody($part->getBody()->getData())) return $result; } foreach ($parts as $part) { if ($result = $this->decodeParts($part->getParts())) return $result; } } /** * @param Google_Service_Gmail_Message $message * @return bool|null|string */ public function getMessageBody($message) { $payload = $message->getPayload(); if ($result = $this->decodeBody($payload->getBody()->getData())) return $result; return $this->decodeParts($payload->getParts()); }


Escribí este código como una mejora de la respuesta de @ ya que filtra la respuesta html correctamente.

<?php ini_set("display_errors", 1); ini_set("track_errors", 1); ini_set("html_errors", 1); error_reporting(E_ALL); require_once __DIR__ . ''/vendor/autoload.php''; session_start(); function decodeBody($body) { $rawData = $body; $sanitizedData = strtr($rawData,''-_'', ''+/''); $decodedMessage = base64_decode($sanitizedData); if(!$decodedMessage){ $decodedMessage = FALSE; } return $decodedMessage; } function fetchMails($gmail, $q) { try{ $list = $gmail->users_messages->listUsersMessages(''me'', array(''q'' => $q)); while ($list->getMessages() != null) { foreach ($list->getMessages() as $mlist) { $message_id = $mlist->id; $optParamsGet2[''format''] = ''full''; $single_message = $gmail->users_messages->get(''me'', $message_id, $optParamsGet2); $payload = $single_message->getPayload(); // With no attachment, the payload might be directly in the body, encoded. $body = $payload->getBody(); $FOUND_BODY = decodeBody($body[''data'']); // If we didn''t find a body, let''s look for the parts if(!$FOUND_BODY) { $parts = $payload->getParts(); foreach ($parts as $part) { if($part[''body''] && $part[''mimeType''] == ''text/html'') { $FOUND_BODY = decodeBody($part[''body'']->data); break; } } } if(!$FOUND_BODY) { foreach ($parts as $part) { // Last try: if we didn''t find the body in the first parts, // let''s loop into the parts of the parts (as @Tholle suggested). if($part[''parts''] && !$FOUND_BODY) { foreach ($part[''parts''] as $p) { // replace ''text/html'' by ''text/plain'' if you prefer if($p[''mimeType''] === ''text/html'' && $p[''body'']) { $FOUND_BODY = decodeBody($p[''body'']->data); break; } } } if($FOUND_BODY) { break; } } } // Finally, print the message ID and the body print_r($message_id . " <br> <br> <br> *-*-*- " . $FOUND_BODY); } if ($list->getNextPageToken() != null) { $pageToken = $list->getNextPageToken(); $list = $gmail->users_messages->listUsersMessages(''me'', array(''pageToken'' => $pageToken)); } else { break; } } } catch (Exception $e) { echo $e->getMessage(); } } $client = new Google_Client(); $client->setAuthConfig(''client_secrets.json''); $client->addScope(Google_Service_Gmail::GMAIL_READONLY); if (isset($_SESSION[''access_token'']) && $_SESSION[''access_token'']) { $client->setAccessToken($_SESSION[''access_token'']); $gmail = new Google_Service_Gmail($client); $q = '' after:2016/11/7''; fetchMails($gmail, $q); } else { $redirect_uri = ''http://'' . $_SERVER[''HTTP_HOST''] . ''/gmail-api/oauth2callback.php''; header(''Location: '' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); }


Hagamos un pequeño experimento. Me he enviado dos mensajes a mí mismo. Uno con un archivo adjunto y otro sin él.

Solicitud:

GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=2

Respuesta:

{ "messages": [ { "id": "14fe21fd6b3fb46f", "threadId": "14fe21fd6b3fb46f" }, { "id": "14fe21f9341ed73c", "threadId": "14fe21f9341ed73c" } ], "nextPageToken": "08943597140129624594", "resultSizeEstimate": 3 }

Solo pido la carga útil, ya que ahí es donde están todas las partes relevantes:

fields = payload GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21fd6b3fb46f?fields=payload GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21f9341ed73c?fields=payload

Correo sin adjunto:

{ "payload": { "parts": [ { "partId": "0", "mimeType": "text/plain", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/plain; charset=UTF-8" } ], "body": { "size": 22, "data": "aGVjaz8gTm8gYXR0YWNobWVudD8NCg==" } }, { "partId": "1", "mimeType": "text/html", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/html; charset=UTF-8" } ], "body": { "size": 43, "data": "PGRpdiBkaXI9Imx0ciI-aGVjaz8gTm8gYXR0YWNobWVudD88L2Rpdj4NCg==" } } ] } }

Correo con archivo adjunto:

{ "payload": { "parts": [ { "mimeType": "multipart/alternative", "filename": "", "headers": [ { "name": "Content-Type", "value": "multipart/alternative; boundary=001a1142e23c551e8e05200b4be0" } ], "body": { "size": 0 }, "parts": [ { "partId": "0.0", "mimeType": "text/plain", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/plain; charset=UTF-8" } ], "body": { "size": 9, "data": "V293IG1hbg0K" } }, { "partId": "0.1", "mimeType": "text/html", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/html; charset=UTF-8" } ], "body": { "size": 30, "data": "PGRpdiBkaXI9Imx0ciI-V293IG1hbjwvZGl2Pg0K" } } ] }, { "partId": "1", "mimeType": "image/jpeg", "filename": "feelthebern.jpg", "headers": [ { "name": "Content-Type", "value": "image/jpeg; name=/"feelthebern.jpg/"" }, { "name": "Content-Disposition", "value": "attachment; filename=/"feelthebern.jpg/"" }, { "name": "Content-Transfer-Encoding", "value": "base64" }, { "name": "X-Attachment-Id", "value": "f_ieq3ev0i0" } ], "body": { "attachmentId": "ANGjdJ_2xG3WOiLh6MbUdYy4vo2VhV2kOso5AyuJW3333rbmk8BIE1GJHIOXkNIVGiphP3fGe7iuIl_MGzXBGNGvNslwlz8hOkvJZg2DaasVZsdVFT_5JGvJOLefgaSL4hqKJgtzOZG9K1XSMrRQAtz2V0NX7puPdXDU4gvalSuMRGwBhr_oDSfx2xljHEbGG6I4VLeLZfrzGGKW7BF-GO_FUxzJR8SizRYqIhgZNA6PfRGyOhf1s7bAPNW3M9KqWRgaK07WTOYl7DzW4hpNBPA4jrl7tgsssExHpfviFL7yL52lxsmbsiLe81Z5UoM", "size": 100446 } } ] } }

Estas respuestas corresponden a las $parts en su código. Como puede ver, si tiene suerte, $parts[0][''body'']->data le dará lo que desea, pero la mayoría de las veces no lo hará.

Generalmente hay dos enfoques para este problema. Podrías implementar el siguiente algoritmo (eres mucho mejor en PHP que en mí, pero este es el resumen general):

  1. Atraviese payload.parts y compruebe si contiene una part que tenga el cuerpo que estaba buscando (ya sea text/plain o text/html ). Si es así, ha terminado con su búsqueda. Si estaba analizando un correo como el anterior sin adjunto, esto sería suficiente.
  2. Vuelva a realizar el paso 1, pero esta vez con las parts encuentran dentro de las parts que acaba de verificar, de forma recursiva. Eventualmente encontrarás tu part . Si estaba analizando un correo como el de arriba con un archivo adjunto, eventualmente encontraría su body .

El algoritmo podría parecerse a lo siguiente (ejemplo en JavaScript):

var response = { "payload": { "parts": [ { "mimeType": "multipart/alternative", "filename": "", "headers": [ { "name": "Content-Type", "value": "multipart/alternative; boundary=001a1142e23c551e8e05200b4be0" } ], "body": { "size": 0 }, "parts": [ { "partId": "0.0", "mimeType": "text/plain", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/plain; charset=UTF-8" } ], "body": { "size": 9, "data": "V293IG1hbg0K" } }, { "partId": "0.1", "mimeType": "text/html", "filename": "", "headers": [ { "name": "Content-Type", "value": "text/html; charset=UTF-8" } ], "body": { "size": 30, "data": "PGRpdiBkaXI9Imx0ciI-V293IG1hbjwvZGl2Pg0K" } } ] }, { "partId": "1", "mimeType": "image/jpeg", "filename": "feelthebern.jpg", "headers": [ { "name": "Content-Type", "value": "image/jpeg; name=/"feelthebern.jpg/"" }, { "name": "Content-Disposition", "value": "attachment; filename=/"feelthebern.jpg/"" }, { "name": "Content-Transfer-Encoding", "value": "base64" }, { "name": "X-Attachment-Id", "value": "f_ieq3ev0i0" } ], "body": { "attachmentId": "ANGjdJ_2xG3WOiLh6MbUdYy4vo2VhV2kOso5AyuJW3333rbmk8BIE1GJHIOXkNIVGiphP3fGe7iuIl_MGzXBGNGvNslwlz8hOkvJZg2DaasVZsdVFT_5JGvJOLefgaSL4hqKJgtzOZG9K1XSMrRQAtz2V0NX7puPdXDU4gvalSuMRGwBhr_oDSfx2xljHEbGG6I4VLeLZfrzGGKW7BF-GO_FUxzJR8SizRYqIhgZNA6PfRGyOhf1s7bAPNW3M9KqWRgaK07WTOYl7DzW4hpNBPA4jrl7tgsssExHpfviFL7yL52lxsmbsiLe81Z5UoM", "size": 100446 } } ] } }; // In e.g. a plain text message, the payload is the only part. var parts = [response.payload]; while (parts.length) { var part = parts.shift(); if (part.parts) { parts = parts.concat(part.parts); } if(part.mimeType === ''text/html'') { var decodedPart = decodeURIComponent(escape(atob(part.body.data.replace(//-/g, ''+'').replace(//_/g, ''/'')))); console.log(decodedPart); } }

La opción mucho más fácil es obtener los datos sin procesar del correo y dejar que una biblioteca ya escrita haga el trabajo por usted:

Solicitud:

format = raw fields = raw GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21fd6b3fb46f?format=raw&fields=raw

Respuesta:

{ "raw": "TUlNRS1WZXJzaW9uOiAxLjANClJlY2VpdmVkOiBieSAxMC4yOC45OS4xOTYgd2l0aCBIVFRQOyBGcmksIDE4IFNlcCAyMDE1IDEzOjIzOjAxIC0wNzAwIChQRFQpDQpEYXRlOiBGcmksIDE4IFNlcCAyMDE1IDIyOjIzOjAxICswMjAwDQpEZWxpdmVyZWQtVG86IGVtdGhvbGluQGdtYWlsLmNvbQ0KTWVzc2FnZS1JRDogPENBRHNaTFJ5eGk2UGt0MHZnUS1iZHd1N2FNLWNHRmZKcEwrRHYyb3ZKOGp4SGN4VWhfQUBtYWlsLmdtYWlsLmNvbT4NClN1YmplY3Q6IFdoYXQgZGENCkZyb206IEVtaWwgVGhvbGluIDxlbXRob2xpbkBnbWFpbC5jb20-DQpUbzogRW1pbCBUaG9saW4gPGVtdGhvbGluQGdtYWlsLmNvbT4NCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOyBib3VuZGFyeT0wMDFhMTE0NjhmMTY1YzUwNDUwNTIwMGI0YzYxDQoNCi0tMDAxYTExNDY4ZjE2NWM1MDQ1MDUyMDBiNGM2MQ0KQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVURi04DQoNCmhlY2s_IE5vIGF0dGFjaG1lbnQ_DQoNCi0tMDAxYTExNDY4ZjE2NWM1MDQ1MDUyMDBiNGM2MQ0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgNCg0KPGRpdiBkaXI9Imx0ciI-aGVjaz8gTm8gYXR0YWNobWVudD88L2Rpdj4NCg0KLS0wMDFhMTE0NjhmMTY1YzUwNDUwNTIwMGI0YzYxLS0=" }

El mayor inconveniente del segundo método es que si obtiene el mensaje sin procesar, descargará todos los datos adjuntos de inmediato, lo que podría ser una gran cantidad de datos para su caso de uso.

No soy bueno en PHP, ¡pero this parece prometedor si quieres elegir la segunda solución! ¡Buena suerte!


Para aquellos que estén interesados, mejoré enormemente mi última respuesta, ¡haciendo que funcione con texto / html (y respaldo a texto / plano si es necesario) y transformando las imágenes como archivos adjuntos de base64 que se cargarán automáticamente cuando se impriman como HTML completo!

El código no es perfecto en absoluto y es demasiado largo para explicarlo en detalle, pero está funcionando para mí.

Siéntase libre de tomarlo y adaptarlo (quizás corregirlo / mejorarlo si es necesario).

// Authentication things above /* * Decode the body. * @param : encoded body - or null * @return : the body if found, else FALSE; */ function decodeBody($body) { $rawData = $body; $sanitizedData = strtr($rawData,''-_'', ''+/''); $decodedMessage = base64_decode($sanitizedData); if(!$decodedMessage){ $decodedMessage = FALSE; } return $decodedMessage; } $client = getClient(); $gmail = new Google_Service_Gmail($client); $list = $gmail->users_messages->listUsersMessages(''me'', [''maxResults'' => 1000]); try{ while ($list->getMessages() != null) { foreach ($list->getMessages() as $mlist) { $message_id = $mlist->id; $optParamsGet2[''format''] = ''full''; $single_message = $gmail->users_messages->get(''me'', $message_id, $optParamsGet2); $payload = $single_message->getPayload(); $parts = $payload->getParts(); // With no attachment, the payload might be directly in the body, encoded. $body = $payload->getBody(); $FOUND_BODY = FALSE; // If we didn''t find a body, let''s look for the parts if(!$FOUND_BODY) { foreach ($parts as $part) { if($part[''parts''] && !$FOUND_BODY) { foreach ($part[''parts''] as $p) { if($p[''parts''] && count($p[''parts'']) > 0){ foreach ($p[''parts''] as $y) { if(($y[''mimeType''] === ''text/html'') && $y[''body'']) { $FOUND_BODY = decodeBody($y[''body'']->data); break; } } } else if(($p[''mimeType''] === ''text/html'') && $p[''body'']) { $FOUND_BODY = decodeBody($p[''body'']->data); break; } } } if($FOUND_BODY) { break; } } } // let''s save all the images linked to the mail''s body: if($FOUND_BODY && count($parts) > 1){ $images_linked = array(); foreach ($parts as $part) { if($part[''filename'']){ array_push($images_linked, $part); } else{ if($part[''parts'']) { foreach ($part[''parts''] as $p) { if($p[''parts''] && count($p[''parts'']) > 0){ foreach ($p[''parts''] as $y) { if(($y[''mimeType''] === ''text/html'') && $y[''body'']) { array_push($images_linked, $y); } } } else if(($p[''mimeType''] !== ''text/html'') && $p[''body'']) { array_push($images_linked, $p); } } } } } // special case for the wdcid... preg_match_all(''/wdcid(.*)"/Uims'', $FOUND_BODY, $wdmatches); if(count($wdmatches)) { $z = 0; foreach($wdmatches[0] as $match) { $z++; if($z > 9){ $FOUND_BODY = str_replace($match, ''image0'' . $z . ''@'', $FOUND_BODY); } else { $FOUND_BODY = str_replace($match, ''image00'' . $z . ''@'', $FOUND_BODY); } } } preg_match_all(''/src="cid:(.*)"/Uims'', $FOUND_BODY, $matches); if(count($matches)) { $search = array(); $replace = array(); // let''s trasnform the CIDs as base64 attachements foreach($matches[1] as $match) { foreach($images_linked as $img_linked) { foreach($img_linked[''headers''] as $img_lnk) { if( $img_lnk[''name''] === ''Content-ID'' || $img_lnk[''name''] === ''Content-Id'' || $img_lnk[''name''] === ''X-Attachment-Id''){ if ($match === str_replace(''>'', '''', str_replace(''<'', '''', $img_lnk->value)) || explode("@", $match)[0] === explode(".", $img_linked->filename)[0] || explode("@", $match)[0] === $img_linked->filename){ $search = "src=/"cid:$match/""; $mimetype = $img_linked->mimeType; $attachment = $gmail->users_messages_attachments->get(''me'', $mlist->id, $img_linked[''body'']->attachmentId); $data64 = strtr($attachment->getData(), array(''-'' => ''+'', ''_'' => ''/'')); $replace = "src=/"data:" . $mimetype . ";base64," . $data64 . "/""; $FOUND_BODY = str_replace($search, $replace, $FOUND_BODY); } } } } } } } // If we didn''t find the body in the last parts, // let''s loop for the first parts (text-html only) if(!$FOUND_BODY) { foreach ($parts as $part) { if($part[''body''] && $part[''mimeType''] === ''text/html'') { $FOUND_BODY = decodeBody($part[''body'']->data); break; } } } // With no attachment, the payload might be directly in the body, encoded. if(!$FOUND_BODY) { $FOUND_BODY = decodeBody($body[''data'']); } // Last try: if we didn''t find the body in the last parts, // let''s loop for the first parts (text-plain only) if(!$FOUND_BODY) { foreach ($parts as $part) { if($part[''body'']) { $FOUND_BODY = decodeBody($part[''body'']->data); break; } } } if(!$FOUND_BODY) { $FOUND_BODY = ''(No message)''; } // Finally, print the message ID and the body print_r($message_id . ": " . $FOUND_BODY); } if ($list->getNextPageToken() != null) { $pageToken = $list->getNextPageToken(); $list = $gmail->users_messages->listUsersMessages(''me'', [''pageToken'' => $pageToken, ''maxResults'' => 1000]); } else { break; } } } catch (Exception $e) { echo $e->getMessage(); }

Salud.


ACTUALIZACIÓN: es posible que desee consultar mi segunda respuesta debajo de esta para obtener un código más completo.

Finalmente, trabajé hoy, así que aquí está la respuesta del código completo para encontrar el cuerpo, gracias a @Tholle :

// Authentication things above /* * Decode the body. * @param : encoded body - or null * @return : the body if found, else FALSE; */ function decodeBody($body) { $rawData = $body; $sanitizedData = strtr($rawData,''-_'', ''+/''); $decodedMessage = base64_decode($sanitizedData); if(!$decodedMessage){ $decodedMessage = FALSE; } return $decodedMessage; } $client = getClient(); $gmail = new Google_Service_Gmail($client); $list = $gmail->users_messages->listUsersMessages(''me'', [''maxResults'' => 1000]); try{ while ($list->getMessages() != null) { foreach ($list->getMessages() as $mlist) { $message_id = $mlist->id; $optParamsGet2[''format''] = ''full''; $single_message = $gmail->users_messages->get(''me'', $message_id, $optParamsGet2); $payload = $single_message->getPayload(); // With no attachment, the payload might be directly in the body, encoded. $body = $payload->getBody(); $FOUND_BODY = decodeBody($body[''data'']); // If we didn''t find a body, let''s look for the parts if(!$FOUND_BODY) { $parts = $payload->getParts(); foreach ($parts as $part) { if($part[''body'']) { $FOUND_BODY = decodeBody($part[''body'']->data); break; } // Last try: if we didn''t find the body in the first parts, // let''s loop into the parts of the parts (as @Tholle suggested). if($part[''parts''] && !$FOUND_BODY) { foreach ($part[''parts''] as $p) { // replace ''text/html'' by ''text/plain'' if you prefer if($p[''mimeType''] === ''text/html'' && $p[''body'']) { $FOUND_BODY = decodeBody($p[''body'']->data); break; } } } if($FOUND_BODY) { break; } } } // Finally, print the message ID and the body print_r($message_id . " : " . $FOUND_BODY); } if ($list->getNextPageToken() != null) { $pageToken = $list->getNextPageToken(); $list = $gmail->users_messages->listUsersMessages(''me'', [''pageToken'' => $pageToken, ''maxResults'' => 1000]); } else { break; } } } catch (Exception $e) { echo $e->getMessage(); }

Como puede ver, mi problema era que, a veces, el cuerpo no se puede encontrar en la carga útil-> partes, sino directamente en la carga útil-> cuerpo . (Además, agrego el bucle para varias partes).

Espero que esto ayude a alguien más.