如何使用Gmail API检索我的Gmail邮件?

11

我想要实现的目标:


我正在使用Gmail API,基本上我想连接到我的GMail帐户以读取我的电子邮件(收件箱类别),并获取每个消息的基本信息(标题/主题、发件人、收件人、日期和发送方)。

问题:


我正在尝试将Google在C#中编写的 这个示例适应到自己的需求中,我正在寻找C#或Vb.Net的解决方案,不管哪个。

(请注意,Google根据不同国家/地区的用户显示不同的代码示例,因此该网页的代码可能对于每个人都不相同,这是Google的逻辑真的很烂。)

我对下面的代码有以下问题:

  • lblInbox.MessagesTotal属性返回空值。
  • msgItem.Raw属性始终为空。
  • 我尚未发现如何仅解析在收件箱类别内的邮件。
  • 我还没有发现如何确定邮件是已读还是未读。
  • 我还没有发现如何确定邮件的基本信息(主题、发件人、收件人、日期和发送方)。

这是我尝试过的内容,注意,在调整Google的示例时,我假设"user"参数应该是Gmail用户帐户名("MyEmail@GMail.com"),但我不确定是否应该是这样。

Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
Imports Google.Apis.Gmail
Imports Google.Apis.Gmail.v1
Imports Google.Apis.Gmail.v1.Data
Imports Google.Apis.Gmail.v1.UsersResource

Public Class Form1 : Inherits Form

    Private Async Sub Test() Handles MyBase.Shown
        Await GmailTest()
    End Sub

    Public Async Function GmailTest() As Task
        Dim credential As UserCredential
        Using stream As New FileStream("C:\GoogleAPIKey.json", FileMode.Open, FileAccess.Read)
            credential = Await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
                                                                           {GmailService.Scope.MailGoogleCom},
                                                                           "MyEmail@GMail.com",
                                                                           CancellationToken.None)
        End Using

        ' Create the service.
        Dim service As New GmailService(New BaseClientService.Initializer() With {
             .HttpClientInitializer = credential,
             .ApplicationName = "What I need to put here?"
        })

        ' Get the "INBOX" label/category.
        Dim lblReq As UsersResource.LabelsResource.ListRequest = service.Users.Labels.List("me")
        Dim lblInbox As Data.Label = lblReq.Execute().Labels.Where(Function(lbl) lbl.Name = "INBOX").Single
        Dim msgCount As Integer? = lblInbox.MessagesTotal

        MsgBox("Messages Count: " & msgCount)

        If (msgCount <> 0) Then

            ' Define message parameters of request.
            Dim msgReq As UsersResource.MessagesResource.ListRequest = service.Users.Messages.List("me")

            ' List messages of INBOX category.
            Dim messages As IList(Of Data.Message) = msgReq.Execute().Messages
            Console.WriteLine("Messages:")
            If (messages IsNot Nothing) AndAlso (messages.Count > 0) Then
                For Each msgItem As Data.Message In messages
                    MsgBox(msgItem.Raw)
                Next
            End If

        End If

    End Function

End Class

问题:


我想要了解最重要的需求(但欢迎任何帮助解决其他提到的问题):

  • 在C#或VB.Net中,如何获取一个集合来遍历所有在收件箱组中的电子邮件?

更新:

这是我目前正在使用的代码,意图是检索指定邮箱标签的所有Message的集合,问题在于newMsg对象的PayloadBody成员为空,所以我无法阅读电子邮件。

我做错了什么吗?。

Public Async Function GetMessages(ByVal folder As Global.Google.Apis.Gmail.v1.Data.Label) As Task(Of List(Of Global.Google.Apis.Gmail.v1.Data.Message))

    If Not (Me.isAuthorizedB) Then
        Throw New InvalidOperationException(Me.authExceptionMessage)
    Else
        Dim msgsRequest As UsersResource.MessagesResource.ListRequest = Me.client.Users.Messages.List("me")
        With msgsRequest
            .LabelIds = New Repeatable(Of String)({folder.Id})
            .MaxResults = 50
            '.Key = "YOUR API KEY"
        End With

        Dim msgsResponse As ListMessagesResponse = Await msgsRequest.ExecuteAsync()

        Dim messages As New List(Of Global.Google.Apis.Gmail.v1.Data.Message)
        Do While True

            For Each msg As Global.Google.Apis.Gmail.v1.Data.Message In msgsResponse.Messages
                Dim msgRequest As UsersResource.MessagesResource.GetRequest = Me.client.Users.Messages.Get("me", msg.Id)
                msgRequest.Format = MessagesResource.GetRequest.FormatEnum.Full

                Dim newMsg As Message = Await msgRequest.ExecuteAsync()
                messages.Add(newMsg)
            Next msg

            If Not String.IsNullOrEmpty(msgsResponse.NextPageToken) Then
                msgsRequest.PageToken = msgsResponse.NextPageToken
                msgsResponse = Await msgsRequest.ExecuteAsync()
            Else
                Exit Do
            End If

        Loop

        Return messages

    End If

End Function

1
"user"被filedatastore用于更改存储凭据的名称。它可以是任意随机字符串。 - Linda Lawton - DaImTo
@DaImTo 感谢您提供有用的信息,但现在我完全误解了如何在哪里指定要访问的 Gmail 用户帐户。 - ElektroStudios
当用户验证应用程序时,他们会授予您访问权限,您并不真正指定它,而是通过验证您的代码来实现。 - Linda Lawton - DaImTo
6个回答

35

目前由于某种原因,许多属性从任何请求中返回为null。如果我们有电子邮件ID列表,仍然可以绕过这个问题。然后,我们可以使用这些电子邮件ID发送另一个请求来检索进一步的详细信息:fromdatesubjectbody@DalmTo也在正确的轨道上,但关于标题并不够接近,因为它最近发生了变化,需要进行更多的请求。

private async Task getEmails()
{
    try
    {
        UserCredential credential;
        using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
        {
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                // This OAuth 2.0 access scope allows for read-only access to the authenticated 
                // user's account, but not other types of account access.
                new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
                "NAME OF ACCOUNT NOT EMAIL ADDRESS",
                CancellationToken.None,
                new FileDataStore(this.GetType().ToString())
            );
        }

        var gmailService = new GmailService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = this.GetType().ToString()
        });

        var emailListRequest = gmailService.Users.Messages.List("EMAILADDRESSHERE");
        emailListRequest.LabelIds = "INBOX";
        emailListRequest.IncludeSpamTrash = false;
        //emailListRequest.Q = "is:unread"; // This was added because I only wanted unread emails...

        // Get our emails
        var emailListResponse = await emailListRequest.ExecuteAsync();

        if (emailListResponse != null && emailListResponse.Messages != null)
        {
            // Loop through each email and get what fields you want...
            foreach (var email in emailListResponse.Messages)
            {
                var emailInfoRequest = gmailService.Users.Messages.Get("EMAIL ADDRESS HERE", email.Id);
                // Make another request for that email id...
                var emailInfoResponse = await emailInfoRequest.ExecuteAsync();

                if (emailInfoResponse != null)
                {
                    String from = "";
                    String date = "";
                    String subject = "";
                    String body = "";
                    // Loop through the headers and get the fields we need...
                    foreach (var mParts in emailInfoResponse.Payload.Headers)
                    {
                        if (mParts.Name == "Date")
                        {
                            date = mParts.Value; 
                        }
                        else if(mParts.Name == "From" )
                        {
                            from = mParts.Value;
                        }
                        else if (mParts.Name == "Subject")
                        {
                            subject = mParts.Value;
                        }

                        if (date != "" && from != "")
                        {
                            if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
                            {
                                body = emailInfoResponse.Payload.Body.Data;
                            }
                            else
                            {
                                body = getNestedParts(emailInfoResponse.Payload.Parts, "");
                            }
                            // Need to replace some characters as the data for the email's body is base64
                            String codedBody = body.Replace("-", "+");
                            codedBody = codedBody.Replace("_", "/");
                            byte[] data = Convert.FromBase64String(codedBody);
                            body = Encoding.UTF8.GetString(data);                               

                            // Now you have the data you want...                         
                        }
                    }
                }                    
            }
        }           
    }
    catch (Exception)
    {
        MessageBox.Show("Failed to get messages!", "Failed Messages!", MessageBoxButtons.OK); 
    }
}

static String getNestedParts(IList<MessagePart> part, string curr)
{
    string str = curr;
    if (part == null)
    {
        return str;
    }
    else
    {
        foreach (var parts in part)
        {
            if (parts.Parts  == null)
            {
                if (parts.Body != null && parts.Body.Data != null)
                {
                    str += parts.Body.Data;
                }
            }
            else
            {
                return getNestedParts(parts.Parts, str);
            }
        }

        return str;
    }        
}

目前,此方法将检索所有电子邮件ID,并针对每个电子邮件ID获取每个电子邮件的主题发件人日期正文。该方法中有注释。如果您有什么不理解的,请告诉我。另外: 在发布此答案之前进行了再次测试


5
在给我点踩的人,你能解释一下为什么要点踩吗?如果你不解释,这对任何人都没有益处。也许我有所遗漏,但是提供的答案已经很好地回答了问题并作了解释。如果你觉得还有不完善的地方,请告诉我。 - Trevor
1
@ElektroStudios 哦,我的错,忘记了那个函数,抱歉!我回家后会找到它的,因为我在工作时无法访问它。该函数会遍历嵌套的部件以获取主体... - Trevor
1
@ElektroStudios 除了缺少的函数外,其他东西你都恢复了吗? - Trevor
2
@ElektroStudios //emailListRequest.Q = "is:unread"; 请确保取消注释此行,因为这将仅提供尚未阅读的收件箱电子邮件。请确保您已经看到了 emailListRequest.LabelIds = "INBOX"; 代码,它在上面的注释中,并且应该只返回收件箱电子邮件。另外,我会在下班后回到我的机器上获取该函数,很抱歉,但很高兴听到您现在正在获得零件! - Trevor
1
很高兴能够帮忙。我会进一步研究这个问题,但是谷歌的API不太完整和规范。问题不在于API本身,而是调用返回的底层数据。如果我有新发现,我一定会更新并在这里发布。 - Trevor
显示剩余15条评论

13

抱歉,这不是一个答案。我无法对 Zaggler 的回答添加评论(我刚加入),所以我会将其作为新的回答发布。Zaggler 的答案非常好,但存在一个小问题。当电子邮件正文有多个部分时,Convert.FromBase64..... 在两个连接的 base64 字符串上无法工作。因此会出现异常。最好先将每个部分进行转换,然后再连接正文。

有人要求提供代码,这里是经过测试的完整代码。其中大部分代码都来自于 Zaggler,但我最终遇到了一些异常情况。因此我追踪到了上述问题。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace GmailTests
{
    class Program
    {
        // If modifying these scopes, delete your previously saved credentials
        // at ~/.credentials/gmail-dotnet-quickstart.json
        static string[] Scopes = { GmailService.Scope.GmailModify };
        static string ApplicationName = "Gmail API .NET Quickstart";

        static void Main(string[] args)
        {
            UserCredential credential;

            using (var stream =
                new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
            {
                string credPath = System.Environment.GetFolderPath(
                    System.Environment.SpecialFolder.Personal);
                credPath = Path.Combine(credPath, ".credentials/gmail-dotnet-quickstart2.json");

                credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    Scopes,
                    "user",
                    CancellationToken.None,
                    new FileDataStore(credPath, true)).Result;
                Console.WriteLine("Credential file saved to: " + credPath);
            }

            // Create Gmail API service.
            var service = new GmailService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            });


            var re = service.Users.Messages.List("me");
            re.LabelIds = "INBOX";
            re.Q = "is:unread"; //only get unread;

            var res = re.Execute();

            if (res != null && res.Messages != null)
            {
                Console.WriteLine("there are {0} emails. press any key to continue!", res.Messages.Count);
                Console.ReadKey();

                foreach (var email in res.Messages)
                {
                    var emailInfoReq = service.Users.Messages.Get("me", email.Id);
                    var emailInfoResponse = emailInfoReq.Execute();

                    if (emailInfoResponse != null)
                    {
                        String from = "";
                        String date = "";
                        String subject = "";
                        String body = "";
                        //loop through the headers and get the fields we need...
                        foreach (var mParts in emailInfoResponse.Payload.Headers)
                        {
                            if (mParts.Name == "Date")
                            {
                                date = mParts.Value;
                            }
                            else if (mParts.Name == "From")
                            {
                                from = mParts.Value;
                            }
                            else if (mParts.Name == "Subject")
                            {
                                subject = mParts.Value;
                            }

                            if (date != "" && from != "")
                            {
                                if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
                                    body = DecodeBase64String(emailInfoResponse.Payload.Body.Data);
                                else
                                    body = GetNestedBodyParts(emailInfoResponse.Payload.Parts, "");

                                //now you have the data you want....

                            }

                        }

                        //Console.Write(body);
                        Console.WriteLine("{0}  --  {1}  -- {2}", subject, date, email.Id);
                        Console.ReadKey();
                    }
                }
            }
        }

        static String DecodeBase64String(string s)
        {
            var ts = s.Replace("-", "+");
            ts = ts.Replace("_", "/");
            var bc = Convert.FromBase64String(ts);
            var tts = Encoding.UTF8.GetString(bc);

            return tts;
        }

        static String GetNestedBodyParts(IList<MessagePart> part, string curr)
        {
            string str = curr;
            if (part == null)
            {
                return str;
            }
            else
            {
                foreach (var parts in part)
                {
                    if (parts.Parts == null)
                    {
                        if (parts.Body != null && parts.Body.Data != null)
                        {
                            var ts = DecodeBase64String(parts.Body.Data);
                            str += ts;
                        }
                    }
                    else
                    {
                        return GetNestedBodyParts(parts.Parts, str);
                    }
                }

                return str;
            }
        }
    }
}

如果您稍微修改一下您的回答,我认为您的贡献会很好。您能否举个例子说明如何转换和连接身体部位? - Stef Geysels
感谢修复我遇到的异常。(y) - iltaf khalid
我们可以在Windows(控制台)应用程序中实现吗? - Prince Antony G

4

首先:赞同@codexer的答案。

其次,在他的代码中使用以下函数来解码base64URL编码的主体。Google不仅将主体进行了base64编码,还进行了URL编码:- /

/// <summary>
    /// Turn a URL encoded base64 encoded string into readable UTF-8 string.
    /// </summary>
    /// <param name="sInput">base64 URL ENCODED string.</param>
    /// <returns>UTF-8 formatted string</returns>
    private string DecodeURLEncodedBase64EncodedString(string sInput)
    {
        string sBase46codedBody = sInput.Replace("-", "+").Replace("_", "/").Replace("=", String.Empty);  //get rid of URL encoding, and pull any current padding off.
        string sPaddedBase46codedBody = sBase46codedBody.PadRight(sBase46codedBody.Length + (4 - sBase46codedBody.Length % 4) % 4, '=');  //re-pad the string so it is correct length.
        byte[] data = Convert.FromBase64String(sPaddedBase46codedBody);
        return Encoding.UTF8.GetString(data);
    }

2
GoogleWebAuthorizationBroker.AuthorizeAsync中的用户参数仅用于FileDatastore存储您的凭据。有关更多信息,请查看我的教程Google .net – FileDatastore demystified
我的VB.net很生疏,大约有6年没有使用了,但是在C#中,您可以这样做。
UsersResource.MessagesResource.ListRequest request = service.Users.Messages.List("Users email address");
var response = request.Execute();

foreach (var item in response.Messages) {
     Console.WriteLine(item.Payload.Headers);            
 }

MessageResource.ListRequest返回一个消息对象列表,您可以循环遍历它们。

Users.Messages 包含应该有主题、收件人和发件人的头信息。

我还有一个非常古老的关于gmail的C#教程,可能会有所帮助。

更新回答以回答您的更新:

当您移除时会发生什么:

.LabelIds = New Repeatable(Of String)({folder.Id})
字符串 只返回与所有指定标签ID匹配的标记的邮件。 看起来您正在发送文件夹ID。请尝试使用user.labels.list,该方法返回用户邮箱中的所有标签列表。请注意区分两者之间的差别。

谢谢您的回答,但在我的情况下,“Payloads”属性始终为null(空引用),因此其成员也为null,为什么呢?然后我无法看到电子邮件正文或任何内容,但是似乎响应已经完成了,因为至少我可以使用“Item.Id”属性并且不为空,但我遇到了我解释的同样的问题,返回响应的null引用对象和空字符串的成员,我无法访问任何内容,只能访问Id和其他数字属性。 - ElektroStudios
1
@Dalm对于上述代码,只返回电子邮件列表,基本上只有它们的ID。然后需要针对每个电子邮件ID发出另一个请求来获取标题和它们的部分... 这些部分包含了详细信息。 - Trevor
@zaggler,您能否解释一下每个消息需要进行哪种请求?我已经疯了。您能否添加一个答案来扩展DaImTo的答案? - ElektroStudios
@ElektroStudios 我确实可以。我会先完成我的外部工作,然后为您发布一个解决方案。 - Trevor
@zaggler 谢谢,我使用 MessagesResource.ListRequest 获取了一组消息 ID,然后尝试使用 MessagesResource.GetRequest 并将消息 ID 传递给它,并将 format 参数设置为 MessagesResource.GetRequest.FormatEnum.Full,但是这种请求似乎没有返回我想要的数据,因为在获取到的新消息中 Message.Payload 成员仍然为空,或者可能是我做错了什么。 - ElektroStudios
更新了你的更新,我几乎认为你可以将其制作成一个单独的问题。搜索标签。 - Linda Lawton - DaImTo

1
UsersResource.MessagesResource.GetRequest getReq = null;
Google.Apis.Gmail.v1.Data.Message msg = null;
getReq = gmailServiceObj.Users.Messages.Get(userEmail, MessageID);
getReq.Format = UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
msg = getReq.Execute();
string converted = msg.Raw.Replace('-', '+');
converted = converted.Replace('_', '/');

byte[] decodedByte = Convert.FromBase64String(converted);
converted = null;
f_Path = Path.Combine(m_CloudParmsObj.m_strDestinationPath,MessageID + ".eml");

if (!Directory.Exists(m_CloudParmsObj.m_strDestinationPath))
    Directory.CreateDirectory(m_CloudParmsObj.m_strDestinationPath);

// Create eml file
File.WriteAllBytes(f_Path, decodedByte);

我们可以通过以下方式获取包含所有邮件属性的.eml文件。

0
          codedBody = codedBody.Replace(" ", "+");
          codedBody = codedBody.Replace("=", "+");

为了解析正文部分,我建议添加以下代码行


目前你的答案写得不够清晰。请编辑并添加更多细节,以帮助其他人理解它如何回答所问的问题。你可以在帮助中心找到有关如何撰写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接