LinqToTwitter 列出自指定日期以来的 DM

LinqToTwitter List DMs since a specified date

是否可以在指定日期后列出私信?如果我有大量直接消息,并且必须翻阅许多结果,我将很快达到速率限制。我想跟踪上次查询 DirectMessageEventsType.List 的时间,并将下一次查询限制为仅在该日期后的 sent/received 消息。

您可能已经知道,Twitter API 中用于列出 DM 的唯一参数是 countcursor。也就是说,这里有一个可能很方便的解决方法。它涉及到 LINQ to Objects 查询之后的 LINQ to Twitter 查询:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using LinqToTwitter;
using LinqToTwitter.OAuth;
using System.Diagnostics;

namespace TwitterDMListDate
{
    class Program
    {
        static async Task Main()
        {
            TwitterContext twitterCtx = await GetTwitterContext();

            int count = 50; // set to a low number to demo paging
            string cursor = "";
            List<DMEvent> allDmEvents = new();

            bool isPastCreatedAt = false;
            DateTime lastCreatedAt = DateTime.UtcNow.AddHours(-1);

            // you don't have a valid cursor until after the first query
            DirectMessageEvents dmResponse =
                await
                    (from dm in twitterCtx.DirectMessageEvents
                     where dm.Type == DirectMessageEventsType.List &&
                           dm.Count == count
                     select dm)
                    .SingleOrDefaultAsync();

            isPastCreatedAt = CheckPastCreatedAt(dmResponse, lastCreatedAt);

            allDmEvents.AddRange(dmResponse?.Value?.DMEvents ?? new List<DMEvent>());
            cursor = dmResponse?.Value?.NextCursor ?? "";

            while (!string.IsNullOrWhiteSpace(cursor) && !isPastCreatedAt)
            {
                dmResponse =
                    await
                        (from dm in twitterCtx.DirectMessageEvents
                         where dm.Type == DirectMessageEventsType.List &&
                               dm.Count == count &&
                               dm.Cursor == cursor
                         select dm)
                        .SingleOrDefaultAsync();

                allDmEvents.AddRange(dmResponse?.Value?.DMEvents ?? new List<DMEvent>());
                cursor = dmResponse?.Value?.NextCursor ?? "";

                isPastCreatedAt = CheckPastCreatedAt(dmResponse, lastCreatedAt);
            }

            if (!allDmEvents.Any())
            {
                Console.WriteLine("No items returned");
                return;
            }

            Console.WriteLine($"Response Count: {allDmEvents.Count}");
            Console.WriteLine("Responses:");

            allDmEvents.ForEach(evt =>
            {
                DirectMessageCreate? msgCreate = evt.MessageCreate;

                if (evt != null && msgCreate != null)
                    Console.WriteLine(
                        $"DM ID: {evt.ID}\n" +
                        $"From ID: {msgCreate.SenderID ?? "None"}\n" +
                        $"To ID:  {msgCreate.Target?.RecipientID ?? "None"}\n" +
                        $"Message Text: {msgCreate.MessageData?.Text ?? "None"}\n");
            });
        }

        static bool CheckPastCreatedAt(DirectMessageEvents dmResponse, DateTime lastCreatedAt)
        {
            return
                (from dm in dmResponse.Value.DMEvents
                 where dm.CreatedAt <= lastCreatedAt
                 select dm)
                .Any();
        }

        static async Task<TwitterContext> GetTwitterContext()
        {
            var auth = new PinAuthorizer()
            {
                CredentialStore = new InMemoryCredentialStore
                {
                    ConsumerKey = "",
                    ConsumerSecret = ""
                },
                GoToTwitterAuthorization = pageLink =>
                {
                    var psi = new ProcessStartInfo
                    {
                        FileName = pageLink,
                        UseShellExecute = true
                    };
                    Process.Start(psi);
                },
                GetPin = () =>
                {
                    Console.WriteLine(
                        "\nAfter authorizing this application, Twitter " +
                        "will give you a 7-digit PIN Number.\n");
                    Console.Write("Enter the PIN number here: ");
                    return Console.ReadLine() ?? string.Empty;
                }
            };

            await auth.AuthorizeAsync();

            var twitterCtx = new TwitterContext(auth);
            return twitterCtx;
        }
    }
}

实现此功能的两个变量是 isPastCreatedAtlastCreatedAtisPastCreatedAt 标记一组 DM(来自大小为 count 的查询)包含一个或多个早于特定日期的 DM 的情况。符合 isPastCreatedAt 条件的日期是 lastCreatedAt。本质上,一旦我们有早于 lastCreatedAt 的推文,我们不想继续查询,因为后续查询保证 return 所有早于 lastCreatedAt.

的推文

该演示将 lastCreatedAt 设置为提前一个小时。请注意,它使用的是 UTC 时间,因为那是 Twitter 使用的时间。在您的应用程序中,您应该跟踪这个时间,将其重新设置为自上次查询以来收到的最旧推文的 CreatedAt 属性。

每次 LINQ to Twitter 查询 DM 后,都会调用 CheckPastCreatedAt。这是设置 isPastCreatedAt 的地方。该方法内部是一个 LINQ to Objects 查询。它查询 LINQ to Twitter 刚刚 returned 的 DM 列表,检查是否有任何 DM 包含早于 lastCreatedAt 日期的日期。它使用 CreatedAt 属性,顾名思义,这是此演示中变量和方法命名的原因,以确保查询不会过度浪费速率限制。它还使用了 Any 运算符,这比 Count 效率高得多,但我跑题了。

请注意,Main 中的 while 语句向您可能用来遍历游标的内容添加了一个附加条件:&& !isPastCreatedAt。这就是防止您走得太远和浪费速率限制的原因。

完成后您只需要再做一件事 - 过滤掉您已经收到的 DM 以避免重复。然而,我的直觉是你已经知道了。

虽然这不是您可能希望得到的答案,但它确实为使用 Twitter API 的任何人概述了一个重要模式,无论是 LINQ to Twitter 还是其他任何一个优秀的库.也就是说,使用 Twitter API 提供的内容进行查询,然后在本地过滤结果。虽然其他一些库提供了额外的抽象,有时感觉它们很有帮助,但 LINQ to Twitter 采用了一种更原始​​的方法来更接近 Twitter API 本身。在这种情况下,最好直观地了解什么是越线的,这样您就可以推断出满足您需求的其他解决方案。