// OpenTween - Client of Twitter // Copyright (c) 2024 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. #nullable enable using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Api.DataModel; using OpenTween.Api.GraphQL; using OpenTween.Connection; namespace OpenTween.Api.TwitterV2 { public class NotificationsMentionsRequest { public static readonly string EndpointName = "/2/notifications/mentions"; private static readonly Uri EndpointUri = new("https://twitter.com/i/api/2/notifications/mentions.json"); public int Count { get; set; } = 100; public string? Cursor { get; set; } public Dictionary CreateParameters() { var param = new Dictionary() { ["include_profile_interstitial_type"] = "1", ["include_blocking"] = "1", ["include_blocked_by"] = "1", ["include_followed_by"] = "1", ["include_want_retweets"] = "1", ["include_mute_edge"] = "1", ["include_can_dm"] = "1", ["include_can_media_tag"] = "1", ["include_ext_has_nft_avatar"] = "1", ["include_ext_is_blue_verified"] = "1", ["include_ext_verified_type"] = "1", ["include_ext_profile_image_shape"] = "1", ["skip_status"] = "1", ["cards_platform"] = "Web-12", ["include_cards"] = "1", ["include_ext_alt_text"] = "true", ["include_ext_limited_action_results"] = "true", ["include_quote_count"] = "true", ["include_reply_count"] = "1", ["tweet_mode"] = "extended", ["include_ext_views"] = "true", ["include_entities"] = "true", ["include_user_entities"] = "true", ["include_ext_media_color"] = "true", ["include_ext_media_availability"] = "true", ["include_ext_sensitive_media_warning"] = "true", ["include_ext_trusted_friends_metadata"] = "true", ["send_error_codes"] = "true", ["simple_quoted_tweet"] = "true", ["requestContext"] = "ptr", ["ext"] = "mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl", ["count"] = this.Count.ToString(CultureInfo.InvariantCulture), }; if (!MyCommon.IsNullOrEmpty(this.Cursor)) param["cursor"] = this.Cursor; return param; } public async Task Send(IApiConnection apiConnection) { var request = new GetRequest { RequestUri = EndpointUri, Query = this.CreateParameters(), EndpointName = EndpointName, }; using var response = await apiConnection.SendAsync(request) .ConfigureAwait(false); var responseBytes = await response.ReadAsBytes() .ConfigureAwait(false); ResponseRoot parsedObjects; XElement rootElm; try { parsedObjects = MyCommon.CreateDataFromJson(responseBytes); using var jsonReader = JsonReaderWriterFactory.CreateJsonReader( responseBytes, XmlDictionaryReaderQuotas.Max ); rootElm = XElement.Load(jsonReader); } catch (SerializationException ex) { var responseText = Encoding.UTF8.GetString(responseBytes); throw TwitterApiException.CreateFromException(ex, responseText); } catch (XmlException ex) { var responseText = Encoding.UTF8.GetString(responseBytes); throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText }; } ErrorResponse.ThrowIfError(rootElm); var tweetIds = rootElm.XPathSelectElements("//content/item/content/tweet/id") .Select(x => x.Value) .ToArray(); var statuses = new List(tweetIds.Length); foreach (var tweetId in tweetIds) { if (!parsedObjects.GlobalObjects.Tweets.TryGetValue(tweetId, out var tweet)) continue; var userId = tweet.UserId; if (!parsedObjects.GlobalObjects.Users.TryGetValue(userId, out var user)) continue; tweet.User = user; statuses.Add(tweet); } var tweets = TimelineTweet.ExtractTimelineTweets(rootElm); var cursorTop = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value; var cursorBottom = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value; return new(statuses.ToArray(), cursorTop, cursorBottom); } [DataContract] private record ResponseRoot( [property: DataMember(Name = "globalObjects")] ResponseGlobalObjects GlobalObjects ); [DataContract] private record ResponseGlobalObjects( [property: DataMember(Name = "users")] Dictionary Users, [property: DataMember(Name = "tweets")] Dictionary Tweets ); [DataContract] private class ResponseTweet : TwitterStatus { [DataMember(Name = "user_id")] public string UserId { get; set; } = ""; } public readonly record struct NotificationsResponse( TwitterStatus[] Statuses, string? CursorTop, string? CursorBottom ); } }