【问题标题】:How to retrieve my Gmail messages using Gmail API?如何使用 Gmail API 检索我的 Gmail 邮件?
【发布时间】:2016-07-26 16:23:12
【问题描述】:

我想要达到的目标:


我正在使用 Gmail API 基本上我想连接到我的 GMail 帐户来阅读我的电子邮件,收件箱类别,并获取每封邮件的基本信息(标题/主题, fromtodate 和发件人)。

问题:


我正在尝试使 this Google 示例(用 C# 编写)适应我自己的需求,我正在寻找 C# 或 Vb.Net 中的解决方案,无论如何。 p>

(请注意,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 中,如何获取一个集合来迭代所有收件箱组中的电子邮件?

更新:

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

我做错了什么?

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

【问题讨论】:

  • "user" 由 filedatastore 用于更改存储凭据的名称。它可以是任何随机字符串。
  • @DaImTo 感谢您提供有用的信息,那么现在我完全误解了如何以及在何处指定我想要访问的 Gmail 用户帐户。
  • 当用户对他们授予您访问权限的应用程序进行身份验证时,您并没有真正做到。您并没有通过验证您的代码来真正指定他们所做的事情。

标签: c# .net vb.net google-api gmail


【解决方案1】:

目前,出于某种原因,许多属性正在从任何请求中返回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 获取每封电子邮件的 subjectfromdatebody。整个方法都有 cmets。如果您有什么不明白的地方,请告诉我。另请注意:在将其作为答案发布之前再次进行了测试

【讨论】:

  • 在downvoter,请你解释为什么downvote?如果不这样做,它不会使任何人受益。也许我在这里遗漏了一些东西,但提供的答案正是 OP 需要的帮助和解释。如果您觉得这不完整,请告诉我。
  • @ElektroStudios 哦,我的错,忘记了这个功能,抱歉!我回家后会得到它,因为我下班时无法访问它。该函数通过嵌套部分来获取主体...
  • @ElektroStudios 除了缺少的功能,你能拿回其他东西吗?
  • @ElektroStudios //emailListRequest.Q = "is:unread"; 该行确保取消注释,因为这只会为您提供尚未阅读的收件箱电子邮件。请确保您在评论中看到了emailListRequest.LabelIds = "INBOX"; 代码,并且应该只返回inbox 电子邮件。另外,下班后我会在我的机器前获得该功能,对此我感到抱歉,但很高兴听到您现在正在拿到零件!
  • @ElektroStudios 抱歉花了一些时间,我更新了上面缺少该功能的代码。你现在应该可以走了。
【解决方案2】:

对不起,这不是答案,我无法对 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;
            }
        }
    }
}

【讨论】:

  • 我认为如果你稍微改写你的答案,你的贡献会很好。你能举个例子如何转换和加入身体部位吗?
  • 感谢您修复了我遇到的异常。 (y)
  • 我们可以在 windows(console) 应用程序中实现吗?
【解决方案3】:

首先:投票给@codexer 的答案。

其次,在他的代码中使用下面的函数来解码base64URL编码的body。 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);
    }

【讨论】:

    【解决方案4】:

    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})
    

    labelIds string 仅返回标签与所有指定标签 ID 匹配的消息。

    您似乎正在发送一个文件夹 ID。尝试使用user.lables.list,它返回列出用户邮箱中的所有标签

    【讨论】:

    • 谢谢你的回答,但在我的情况下,Payloads 属性总是 nul(一个 nul 引用)所以它的成员也是 nul,为什么?,然后我看不到电子邮件正文或任何东西但是,似乎响应完成了,因为至少我可以使用Item.Id 属性并且不为空,但是我遇到了与我解释的相同的问题,返回的成员的nul引用对象和空字符串响应,我无法访问任何内容,只能访问 Id 和其他数字属性。
    • @DalmTo 上面的代码只返回电子邮件列表,几乎只返回它们的 ID。然后,每个电子邮件 ID 都需要另一个请求来获取标头及其部分……这些部分包含该详细信息。
    • @zaggler 你能解释一下每条消息需要执行哪种请求吗?我快疯了。你能添加一个答案来扩展 DaImTo 的答案吗?
    • @ElektroStudios 我确实可以。我将完成我的外部工作,然后我会为您发布解决方案。
    • @zaggler 谢谢,在获得带有MessagesResource.ListRequest 的消息ID 集合后,我尝试执行MessagesResource.GetRequest 传递消息ID 并将格式参数设置为MessagesResource.GetRequest.FormatEnum.Full,但是这种request 似乎没有返回我想要的数据,因为Message.Payload member 在获得的 ne 消息中仍然为 null,或者我做错了什么。
    【解决方案5】:
    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 文件。

    【讨论】:

      猜你喜欢
      • 2020-09-27
      • 2018-11-10
      • 1970-01-01
      • 2022-12-12
      • 2020-02-05
      • 2019-10-16
      • 2022-10-25
      • 2017-03-07
      • 1970-01-01
      相关资源
      最近更新 更多