У меня возникли проблемы с API-интерфейсом Gmail.
Я хочу получить содержимое тела электронной почты, но я могу получить его только для электронных писем, у которых есть вложения! Вопрос в том, почему?
Вот мой код:
// 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 GOT 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; } }
Второй вариант получения содержимого, который я знаю, – это использовать фрагмент, расположенный в части заголовков , но он извлекает только 50 первых символов или около того, что не очень полезно.
Давайте проведем небольшой эксперимент. Я отправил два сообщения самому себе. Один с приложением, а один без.
Запрос:
GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=2
Отклик:
{ "messages": [ { "id": "14fe21fd6b3fb46f", "threadId": "14fe21fd6b3fb46f" }, { "id": "14fe21f9341ed73c", "threadId": "14fe21f9341ed73c" } ], "nextPageToken": "08943597140129624594", "resultSizeEstimate": 3 }
Я только запрашиваю полезную нагрузку, так как именно там находятся все соответствующие части:
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
Почта без приложения:
{ "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==" } } ] } }
Почта с приложением:
{ "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 } } ] } }
Эти ответы соответствуют $parts
вашего кода. Как вы можете видеть, если вам повезет, данные в $parts[0]['body']->data
предоставят вам то, что вы хотите, но большую часть времени это не будет.
Как правило, существует два подхода к этой проблеме. Вы можете реализовать следующий алгоритм (вы намного лучше на PHP, чем я, но это общая схема):
payload.parts
по payload.parts
и проверьте, содержит ли она part
, в которой находится тело, которое вы искали ( text/plain
или text/html
). Если это так, вы закончите поиск. Если бы вы разобрали почту, как выше, без прикрепления, этого было бы достаточно. parts
найденными внутри parts
вы только что проверили, рекурсивно. В конечном итоге вы найдете свою part
. Если вы разобрали почту, подобную приведенной выше, с приложением, это в конечном итоге найдет ваше body
. Алгоритм может выглядеть примерно следующим образом (пример в 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 eg 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); } }
UPDATE: вы можете проверить мой второй ответ ниже этого для более полного кода.
Наконец, я работал сегодня, так что вот полный код для поиска тела – благодаря @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(); }
Как вы можете видеть, моя проблема заключалась в том, что иногда тело не может быть найдено в частях полезной нагрузки, а непосредственно в теле полезной нагрузки ! (плюс я добавляю цикл для нескольких частей).
Надеюсь, это поможет кому-то другому.
Для тех, кто заинтересован, я значительно улучшил свой последний ответ, сделав его работающим с текстом / html (и при необходимости возвращаясь к тексту / plain) и преобразуя изображения в качестве вложений base64, которые будут автоматически загружаться при печати в виде полного HTML!
Код совсем не идеален и слишком длинный, чтобы подробно объяснять, но он работает для меня.
Не стесняйтесь принимать его и приспосабливать (возможно, исправлять / улучшать его, если необходимо).
// 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(); }
Приветствия.
Я написал этот код в качестве улучшения ответа @ F3L1X79 , так как он правильно фильтрует html-ответ.
<?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)); }