diff --git a/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/CountsDemos.cs b/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/CountsDemos.cs new file mode 100644 index 00000000..955e0166 --- /dev/null +++ b/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/CountsDemos.cs @@ -0,0 +1,103 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LinqToTwitter; +using System.Collections.Generic; +using System.Diagnostics; +using LinqToTwitter.Common; + +namespace ConsoleDemo.CSharp +{ + public class CountsDemos + { + internal static async Task RunAsync(TwitterContext twitterCtx) + { + char key; + + do + { + ShowMenu(); + + key = Console.ReadKey(true).KeyChar; + + switch (key) + { + case '0': + Console.WriteLine("\n\tGetting recent counts...\n"); + await DoCountsRecentAsync(twitterCtx); + break; + case '1': + Console.WriteLine("\n\tGetting all counts...\n"); + await DoCountsAllAsync(twitterCtx); + break; + case 'q': + case 'Q': + Console.WriteLine("\nReturning...\n"); + break; + default: + Console.WriteLine(key + " is unknown"); + break; + } + + } while (char.ToUpper(key) != 'Q'); + } + + static void ShowMenu() + { + Console.WriteLine("\nCounts Demos - Please select:\n"); + + Console.WriteLine("\t 0. Recent Counts"); + Console.WriteLine("\t 1. All Counts"); + Console.WriteLine(); + Console.Write("\t Q. Return to Main menu"); + } + + static async Task DoCountsRecentAsync(TwitterContext twitterCtx) + { + string searchTerm = "\"LINQ to Twitter\" OR Linq2Twitter OR LinqToTwitter OR JoeMayo"; + //searchTerm = "Twitter"; + + Counts? countsResponse = + await + (from count in twitterCtx.Counts + where count.Type == CountType.Recent && + count.Query == searchTerm && + count.Granularity == Granularity.Day + select count) + .SingleOrDefaultAsync(); + + if (countsResponse?.CountRanges != null) + countsResponse.CountRanges.ForEach(range => + Console.WriteLine( + $"\nStart: {range.Start}" + + $"\nEnd: {range.End}" + + $"\nTweet: {range.TweetCount}")); + else + Console.WriteLine("No entries found."); + } + + static async Task DoCountsAllAsync(TwitterContext twitterCtx) + { + string searchTerm = "\"LINQ to Twitter\" OR Linq2Twitter OR LinqToTwitter OR JoeMayo"; + //searchTerm = "Twitter"; + + Counts? countsResponse = + await + (from count in twitterCtx.Counts + where count.Type == CountType.All && + count.Query == searchTerm && + count.Granularity == Granularity.Day + select count) + .SingleOrDefaultAsync(); + + if (countsResponse?.CountRanges != null) + countsResponse.CountRanges.ForEach(range => + Console.WriteLine( + $"\nStart: {range.Start}" + + $"\nEnd: {range.End}" + + $"\nTweet: {range.TweetCount}")); + else + Console.WriteLine("No entries found."); + } + } +} diff --git a/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/Program.cs b/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/Program.cs index bd95a184..a47143a8 100644 --- a/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/Program.cs +++ b/Samples/LinqToTwitter6/Console/ConsoleDemo.CSharp/ConsoleDemo.CSharp/Program.cs @@ -150,6 +150,11 @@ static async Task DoDemosAsync() Console.WriteLine("\n\tRunning Like Demos...\n"); await LikeDemos.RunAsync(twitterCtx); break; + case 'm': + case 'M': + Console.WriteLine("\n\tRunning Counts Demos...\n"); + await CountsDemos.RunAsync(twitterCtx); + break; case 'q': case 'Q': Console.WriteLine("\nQuitting...\n"); @@ -188,6 +193,7 @@ static void ShowMenu() Console.WriteLine("\t J. Tweet Demos"); Console.WriteLine("\t K. Compliance Demos"); Console.WriteLine("\t L. Like Demos"); + Console.WriteLine("\t M. Counts Demos"); Console.WriteLine(); Console.Write("\t Q. End Program"); } \ No newline at end of file diff --git a/src/LinqToTwitter6/LinqToTwitter.Tests/CountsTests/CountsRequestProcessorTests.cs b/src/LinqToTwitter6/LinqToTwitter.Tests/CountsTests/CountsRequestProcessorTests.cs new file mode 100644 index 00000000..fea92099 --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter.Tests/CountsTests/CountsRequestProcessorTests.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using LinqToTwitter; +using LinqToTwitter.Common; +using LinqToTwitter.Provider; +using LinqToTwitter.Tests.Common; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace LinqToTwitter.Tests.CountsTests +{ + [TestClass] + public class CountsRequestProcessorTests + { + const string BaseUrl2 = "https://api.twitter.com/2/"; + + public CountsRequestProcessorTests() + { + TestCulture.SetCulture(); + } + + [TestMethod] + public void GetParametersTest() + { + var target = new CountsRequestProcessor(); + + var endTime = new DateTime(2020, 8, 30); + var startTime = new DateTime(2020, 8, 1); + Expression> expression = + count => + count.Type == CountType.All && + count.EndTime == endTime && + count.Granularity == Granularity.Day && + count.NextToken == "abc" && + count.Query == "LINQ to Twitter" && + count.SinceID == "123" && + count.StartTime == startTime && + count.UntilID == "525"; + + var lambdaExpression = expression as LambdaExpression; + + Dictionary queryParams = target.GetParameters(lambdaExpression); + + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.Type), ((int)CountType.All).ToString(CultureInfo.InvariantCulture)))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.EndTime), "08/30/2020 00:00:00"))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.Granularity), ((int)Granularity.Day).ToString()))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.NextToken), "abc"))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.Query), "LINQ to Twitter"))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.SinceID), "123"))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.StartTime), "08/01/2020 00:00:00"))); + Assert.IsTrue( + queryParams.Contains( + new KeyValuePair(nameof(Counts.UntilID), "525"))); + } + + [TestMethod] + public void BuildUrl_ForRecent_IncludesParameters() + { + const string ExpectedUrl = + BaseUrl2 + "tweets/counts/recent?" + + "query=LINQ%20to%20Twitter&" + + "end_time=2021-01-01T12%3A59%3A59Z&" + + "granularity=day&" + + "next_token=abc&" + + "since_id=123&" + + "start_time=2020-12-31T00%3A00%3A01Z&" + + "until_id=525"; + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + var parameters = + new Dictionary + { + { nameof(Counts.Query), "LINQ to Twitter" }, + { nameof(Counts.Type), CountType.Recent.ToString() }, + { nameof(Counts.EndTime), new DateTime(2021, 1, 1, 12, 59, 59).ToString() }, + { nameof(Counts.Granularity), Granularity.Day.ToString().ToLower() }, + { nameof(Counts.NextToken), "abc" }, + { nameof(Counts.SinceID), "123" }, + { nameof(Counts.StartTime), new DateTime(2020, 12, 31, 0, 0, 1).ToString() }, + { nameof(Counts.UntilID), "525" }, + }; + + Request req = countReqProc.BuildUrl(parameters); + + Assert.AreEqual(ExpectedUrl, req.FullUrl); + } + + [TestMethod] + public void BuildUrl_ForAll_IncludesParameters() + { + const string ExpectedUrl = + BaseUrl2 + "tweets/counts/all?" + + "query=LINQ%20to%20Twitter&" + + "end_time=2021-01-01T12%3A59%3A59Z&" + + "granularity=day&" + + "next_token=abc&" + + "since_id=123&" + + "start_time=2020-12-31T00%3A00%3A01Z&" + + "until_id=525"; + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + var parameters = + new Dictionary + { + { nameof(Counts.Query), "LINQ to Twitter" }, + { nameof(Counts.Type), CountType.All.ToString() }, + { nameof(Counts.EndTime), new DateTime(2021, 1, 1, 12, 59, 59).ToString() }, + { nameof(Counts.Granularity), Granularity.Day.ToString().ToLower() }, + { nameof(Counts.NextToken), "abc" }, + { nameof(Counts.SinceID), "123" }, + { nameof(Counts.StartTime), new DateTime(2020, 12, 31, 0, 0, 1).ToString() }, + { nameof(Counts.UntilID), "525" }, + }; + + Request req = countReqProc.BuildUrl(parameters); + + Assert.AreEqual(ExpectedUrl, req.FullUrl); + } + + [TestMethod] + public void BuildUrl_Throws_When_Parameters_Null() + { + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + + L2TAssert.Throws(() => + { + countReqProc.BuildUrl(null); + }); + } + + [TestMethod] + public void BuildUrl_Requires_Query() + { + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + var parameters = + new Dictionary + { + { nameof(Counts.Type), CountType.Recent.ToString() }, + { nameof(Counts.Query), null }, + }; + + ArgumentException ex = + L2TAssert.Throws(() => + countReqProc.BuildUrl(parameters)); + + Assert.AreEqual(nameof(Counts.Query), ex.ParamName); + } + + [TestMethod] + public void ProcessResults_Populates_CountRanges() + { + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + + List results = countReqProc.ProcessResults(CountResponse); + + Assert.IsNotNull(results); + Counts counts = results.SingleOrDefault(); + Assert.IsNotNull(counts); + List ranges = counts.CountRanges; + Assert.IsNotNull(ranges); + Assert.AreEqual(8, ranges.Count); + CountRange range = ranges[6]; + Assert.AreEqual(DateTime.Parse("2021-09-18").Date, range.End.Date); + Assert.AreEqual(DateTime.Parse("2021-09-17").Date, range.Start.Date); + Assert.AreEqual(2, range.TweetCount); + } + + [TestMethod] + public void ProcessResults_Populates_Meta() + { + var countReqProc = new CountsRequestProcessor { BaseUrl = BaseUrl2 }; + + List results = countReqProc.ProcessResults(CountResponse); + + Assert.IsNotNull(results); + Counts counts = results.SingleOrDefault(); + Assert.IsNotNull(counts); + CountsMeta meta = counts.Meta; + Assert.IsNotNull(meta); + Assert.AreEqual(5, meta.TotalTweetCount); + } + + [TestMethod] + public void ProcessResults_Populates_Input_Parameters() + { + var countReqProc = new CountsRequestProcessor + { + BaseUrl = BaseUrl2, + Type = CountType.Recent, + EndTime = new DateTime(2020, 12, 31), + Granularity = Granularity.Minute, + NextToken = "789", + Query = "JoeMayo", + SinceID = "1", + StartTime = new DateTime(2020, 1, 1), + UntilID = "901" + }; + + var countResult = countReqProc.ProcessResults(CountResponse); + + Assert.IsNotNull(countResult); + Assert.AreEqual(1, countResult.Count); + var count = countResult.Single(); + Assert.IsNotNull(count); + Assert.AreEqual(CountType.Recent, count.Type); + Assert.AreEqual(new DateTime(2020, 12, 31), count.EndTime); + Assert.AreEqual(Granularity.Minute, count.Granularity); + Assert.AreEqual("789", count.NextToken); + Assert.AreEqual("JoeMayo", count.Query); + Assert.AreEqual("1", count.SinceID); + Assert.AreEqual(new DateTime(2020, 1, 1), count.StartTime); + Assert.AreEqual("901", count.UntilID); + } + + #region CountResponse + + const string CountResponse = @"{ + ""data"": [ + { + ""end"": ""2021-09-12T00:00:00.000Z"", + ""start"": ""2021-09-11T02:15:42.000Z"", + ""tweet_count"": 0 + }, + { + ""end"": ""2021-09-13T00:00:00.000Z"", + ""start"": ""2021-09-12T00:00:00.000Z"", + ""tweet_count"": 3 + }, + { + ""end"": ""2021-09-14T00:00:00.000Z"", + ""start"": ""2021-09-13T00:00:00.000Z"", + ""tweet_count"": 0 + }, + { + ""end"": ""2021-09-15T00:00:00.000Z"", + ""start"": ""2021-09-14T00:00:00.000Z"", + ""tweet_count"": 0 + }, + { + ""end"": ""2021-09-16T00:00:00.000Z"", + ""start"": ""2021-09-15T00:00:00.000Z"", + ""tweet_count"": 0 + }, + { + ""end"": ""2021-09-17T00:00:00.000Z"", + ""start"": ""2021-09-16T00:00:00.000Z"", + ""tweet_count"": 0 + }, + { + ""end"": ""2021-09-18T00:00:00.000Z"", + ""start"": ""2021-09-17T00:00:00.000Z"", + ""tweet_count"": 2 + }, + { + ""end"": ""2021-09-18T02:15:42.000Z"", + ""start"": ""2021-09-18T00:00:00.000Z"", + ""tweet_count"": 0 + } + ], + ""meta"": { + ""total_tweet_count"": 5 + } +}"; + + #endregion + + } +} diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/CountRange.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/CountRange.cs new file mode 100644 index 00000000..945cb68f --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/CountRange.cs @@ -0,0 +1,17 @@ +using System; +using System.Text.Json.Serialization; + +namespace LinqToTwitter +{ + public record CountRange + { + [JsonPropertyName("end")] + public DateTime End { get; set; } + + [JsonPropertyName("start")] + public DateTime Start { get; set; } + + [JsonPropertyName("tweet_count")] + public int TweetCount { get; set; } + } +} \ No newline at end of file diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/CountType.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/CountType.cs new file mode 100644 index 00000000..92ff62a6 --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/CountType.cs @@ -0,0 +1,15 @@ +namespace LinqToTwitter +{ + public enum CountType + { + /// + /// Get counts for last seven days + /// + Recent, + + /// + /// Get full archive search count + /// + All + } +} diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/Counts.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/Counts.cs new file mode 100644 index 00000000..f4ef9774 --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/Counts.cs @@ -0,0 +1,73 @@ +using LinqToTwitter.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace LinqToTwitter +{ + public record Counts + { + // + // Input parameters + // + + /// + /// type of count + /// + public CountType Type { get; init; } + + /// + /// Date/Time to search to + /// + public DateTime EndTime { get; init; } + + /// + /// Comma-separated list of expansion fields + /// + public Granularity Granularity { get; init; } + + /// + /// Provide this, when paging, to get the next page of results + /// + public string? NextToken { get; init; } + + /// + /// search query + /// + public string? Query { get; init; } + + /// + /// Return tweets whose IDs are greater than this + /// + public string? SinceID { get; init; } + + /// + /// Date/Time to start search + /// + public DateTime StartTime { get; init; } + + /// + /// Return tweets whose ids are less than this + /// + public string? UntilID { get; init; } + + // + // Output results + // + + /// + /// Tweet data returned from the search + /// + [JsonPropertyName("data")] + public List? CountRanges { get; init; } + + /// + /// Count metadata returned from query + /// + [JsonPropertyName("meta")] + public CountsMeta? Meta { get; init; } + } +} diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/CountsMeta.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/CountsMeta.cs new file mode 100644 index 00000000..888d1251 --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/CountsMeta.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace LinqToTwitter +{ + public record CountsMeta + { + [JsonPropertyName("total_tweet_count")] + public int TotalTweetCount { get; set; } + } +} diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/CountsRequestProcessor.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/CountsRequestProcessor.cs new file mode 100644 index 00000000..d8af91b9 --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/CountsRequestProcessor.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Text.Json; +using System.Text.Json.Serialization; +using LinqToTwitter.Common; +using LinqToTwitter.Provider; + +namespace LinqToTwitter +{ + /// + /// processes search queries + /// + public class CountsRequestProcessor : IRequestProcessor, IRequestProcessorWantsJson + { + /// + /// base url for request + /// + public string? BaseUrl { get; set; } + + /// + /// type of count + /// + public CountType Type { get; set; } + + /// + /// Date/Time to search to + /// + public DateTime EndTime { get; set; } + + /// + /// Grouping of Day, Hour, or Minute - + /// + public Granularity Granularity { get; set; } + + /// + /// Provide this, when paging, to get the next page of results + /// + public string? NextToken { get; set; } + + /// + /// search query + /// + public string? Query { get; set; } + + /// + /// Return tweets whose IDs are greater than this + /// + public string? SinceID { get; set; } + + /// + /// Date/Time to start search + /// + public DateTime StartTime { get; set; } + + /// + /// Return tweets whose ids are less than this + /// + public string? UntilID { get; set; } + + /// + /// extracts parameters from lambda + /// + /// lambda expression with where clause + /// dictionary of parameter name/value pairs + public Dictionary GetParameters(LambdaExpression lambdaExpression) + { + var paramFinder = + new ParameterFinder( + lambdaExpression.Body, + new List { + nameof(Type), + nameof(EndTime), + nameof(Granularity), + nameof(NextToken), + nameof(Query), + nameof(SinceID), + nameof(StartTime), + nameof(UntilID) + }); + + return paramFinder.Parameters; + } + + /// + /// builds url based on input parameters + /// + /// criteria for url segments and parameters + /// URL conforming to Twitter API + public Request BuildUrl(Dictionary parameters) + { + if (parameters.ContainsKey(nameof(Type))) + Type = RequestProcessorHelper.ParseEnum(parameters[nameof(Type)]); + else + throw new ArgumentException($"{nameof(Type)} is required", nameof(Type)); + + string urlSegment = Type switch + { + CountType.All => "tweets/counts/all", + CountType.Recent => "tweets/counts/recent", + _ => throw new ArgumentException($"Unknown CountType: '{Type}'") + }; + + return BuildCountUrlParameters(parameters, urlSegment); + } + + /// + /// appends parameters for Count request + /// + /// list of parameters from expression tree + /// base url + /// base url + parameters + Request BuildCountUrlParameters(Dictionary parameters, string url) + { + var req = new Request(BaseUrl + url); + var urlParams = req.RequestParameters; + + + if (parameters.ContainsKey(nameof(Query)) && !string.IsNullOrWhiteSpace(parameters[nameof(Query)])) + { + Query = parameters[nameof(Query)]; + urlParams.Add(new QueryParameter("query", Query)); + } + else + { + throw new ArgumentNullException(nameof(Query), "Query filter in where clause is required."); + } + + if (parameters.ContainsKey(nameof(EndTime))) + { + EndTime = DateTime.Parse(parameters[nameof(EndTime)]); + urlParams.Add(new QueryParameter("end_time", EndTime.ToString(L2TKeys.ISO8601, CultureInfo.InvariantCulture))); + } + + if (parameters.ContainsKey(nameof(Granularity))) + { + Granularity = RequestProcessorHelper.ParseEnum(parameters[nameof(Granularity)]); + urlParams.Add(new QueryParameter("granularity", Granularity.ToString().ToLower())); + } + + if (parameters.ContainsKey(nameof(NextToken))) + { + NextToken = parameters[nameof(NextToken)]; + urlParams.Add(new QueryParameter("next_token", NextToken)); + } + + if (parameters.ContainsKey(nameof(SinceID))) + { + SinceID = parameters[nameof(SinceID)]; + urlParams.Add(new QueryParameter("since_id", SinceID)); + } + + if (parameters.ContainsKey(nameof(StartTime))) + { + StartTime = DateTime.Parse(parameters[nameof(StartTime)]); + urlParams.Add(new QueryParameter("start_time", StartTime.ToString(L2TKeys.ISO8601, CultureInfo.InvariantCulture))); + } + + if (parameters.ContainsKey(nameof(UntilID))) + { + UntilID = parameters[nameof(UntilID)]; + urlParams.Add(new QueryParameter("until_id", UntilID)); + } + + return req; + } + + /// + /// Transforms response from Twitter into List of Search + /// + /// Json response from Twitter + /// List of Search + public virtual List ProcessResults(string responseJson) + { + IEnumerable counts; + + if (string.IsNullOrWhiteSpace(responseJson)) + { + counts = new List { new Counts() }; + } + else + { + var countsResult = JsonDeserialize(responseJson); + counts = new List { countsResult }; + } + + return counts.OfType().ToList(); + } + + Counts JsonDeserialize(string responseJson) + { + var options = new JsonSerializerOptions + { + Converters = + { + new JsonStringEnumConverter(), + new TweetMediaTypeConverter() + } + }; + Counts? counts = JsonSerializer.Deserialize(responseJson, options); + + if (counts == null) + return new Counts() + { + Type = Type, + EndTime = EndTime, + Granularity = Granularity, + NextToken = NextToken, + Query = Query, + SinceID = SinceID, + StartTime = StartTime, + UntilID = UntilID, + }; + else + return counts with + { + Type = Type, + EndTime = EndTime, + Granularity = Granularity, + NextToken = NextToken, + Query = Query, + SinceID = SinceID, + StartTime = StartTime, + UntilID = UntilID, + }; + } + } +} \ No newline at end of file diff --git a/src/LinqToTwitter6/LinqToTwitter/Counts/Granularity.cs b/src/LinqToTwitter6/LinqToTwitter/Counts/Granularity.cs new file mode 100644 index 00000000..c94f099c --- /dev/null +++ b/src/LinqToTwitter6/LinqToTwitter/Counts/Granularity.cs @@ -0,0 +1,28 @@ +namespace LinqToTwitter +{ + /// + /// Frequency of Count query results + /// + public enum Granularity + { + /// + /// Unspecified, defaults to Day + /// + None, + + /// + /// Return results for every day of the time frame + /// + Day, + + /// + /// Return results for every hour of the time frame + /// + Hour, + + /// + /// Return results for every minute of the time frame + /// + Minute + } +} diff --git a/src/LinqToTwitter6/LinqToTwitter/TwitterContext.cs b/src/LinqToTwitter6/LinqToTwitter/TwitterContext.cs index 5f09fc6b..9739082f 100644 --- a/src/LinqToTwitter6/LinqToTwitter/TwitterContext.cs +++ b/src/LinqToTwitter6/LinqToTwitter/TwitterContext.cs @@ -517,6 +517,12 @@ protected internal IRequestProcessor CreateRequestProcessor(string request BaseUrl = BaseUrl2 }; break; + case nameof(Counts): + req = new CountsRequestProcessor() + { + BaseUrl = BaseUrl2 + }; + break; case nameof(DirectMessageEvents): req = new DirectMessageEventsRequestProcessor(); break; diff --git a/src/LinqToTwitter6/LinqToTwitter/TwitterContextEntities.cs b/src/LinqToTwitter6/LinqToTwitter/TwitterContextEntities.cs index 5c24507e..79e30560 100644 --- a/src/LinqToTwitter6/LinqToTwitter/TwitterContextEntities.cs +++ b/src/LinqToTwitter6/LinqToTwitter/TwitterContextEntities.cs @@ -48,6 +48,14 @@ public TwitterQueryable Compliance } } + public TwitterQueryable Counts + { + get + { + return new TwitterQueryable(this); + } + } + /// /// enables access to Direct Message Events, supporting Twitter chatbots ///