1 // OpenTween - Client of Twitter
2 // Copyright (c) 2024 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
25 using System.Collections.Generic;
26 using System.Globalization;
28 using System.Runtime.Serialization;
29 using System.Runtime.Serialization.Json;
31 using System.Threading.Tasks;
33 using System.Xml.Linq;
34 using System.Xml.XPath;
35 using OpenTween.Api.DataModel;
36 using OpenTween.Api.GraphQL;
37 using OpenTween.Connection;
39 namespace OpenTween.Api.TwitterV2
41 public class NotificationsMentionsRequest
43 public static readonly string EndpointName = "/2/notifications/mentions";
45 private static readonly Uri EndpointUri = new("https://twitter.com/i/api/2/notifications/mentions.json");
47 public int Count { get; set; } = 100;
49 public string? Cursor { get; set; }
51 public Dictionary<string, string> CreateParameters()
53 var param = new Dictionary<string, string>()
55 ["include_profile_interstitial_type"] = "1",
56 ["include_blocking"] = "1",
57 ["include_blocked_by"] = "1",
58 ["include_followed_by"] = "1",
59 ["include_want_retweets"] = "1",
60 ["include_mute_edge"] = "1",
61 ["include_can_dm"] = "1",
62 ["include_can_media_tag"] = "1",
63 ["include_ext_has_nft_avatar"] = "1",
64 ["include_ext_is_blue_verified"] = "1",
65 ["include_ext_verified_type"] = "1",
66 ["include_ext_profile_image_shape"] = "1",
67 ["skip_status"] = "1",
68 ["cards_platform"] = "Web-12",
69 ["include_cards"] = "1",
70 ["include_ext_alt_text"] = "true",
71 ["include_ext_limited_action_results"] = "true",
72 ["include_quote_count"] = "true",
73 ["include_reply_count"] = "1",
74 ["tweet_mode"] = "extended",
75 ["include_ext_views"] = "true",
76 ["include_entities"] = "true",
77 ["include_user_entities"] = "true",
78 ["include_ext_media_color"] = "true",
79 ["include_ext_media_availability"] = "true",
80 ["include_ext_sensitive_media_warning"] = "true",
81 ["include_ext_trusted_friends_metadata"] = "true",
82 ["send_error_codes"] = "true",
83 ["simple_quoted_tweet"] = "true",
84 ["requestContext"] = "ptr",
85 ["ext"] = "mediaStats,highlightedLabel,hasNftAvatar,voiceInfo,birdwatchPivot,superFollowMetadata,unmentionInfo,editControl",
86 ["count"] = this.Count.ToString(CultureInfo.InvariantCulture),
89 if (!MyCommon.IsNullOrEmpty(this.Cursor))
90 param["cursor"] = this.Cursor;
95 public async Task<NotificationsResponse> Send(IApiConnection apiConnection)
97 var request = new GetRequest
99 RequestUri = EndpointUri,
100 Query = this.CreateParameters(),
101 EndpointName = EndpointName,
104 using var response = await apiConnection.SendAsync(request)
105 .ConfigureAwait(false);
107 var responseBytes = await response.ReadAsBytes()
108 .ConfigureAwait(false);
110 ResponseRoot parsedObjects;
114 parsedObjects = MyCommon.CreateDataFromJson<ResponseRoot>(responseBytes);
116 using var jsonReader = JsonReaderWriterFactory.CreateJsonReader(
118 XmlDictionaryReaderQuotas.Max
121 rootElm = XElement.Load(jsonReader);
123 catch (SerializationException ex)
125 var responseText = Encoding.UTF8.GetString(responseBytes);
126 throw TwitterApiException.CreateFromException(ex, responseText);
128 catch (XmlException ex)
130 var responseText = Encoding.UTF8.GetString(responseBytes);
131 throw new TwitterApiException("Invalid JSON", ex) { ResponseText = responseText };
134 ErrorResponse.ThrowIfError(rootElm);
136 var tweetIds = rootElm.XPathSelectElements("//content/item/content/tweet/id")
137 .Select(x => x.Value)
140 var statuses = new List<TwitterStatus>(tweetIds.Length);
141 foreach (var tweetId in tweetIds)
143 if (!parsedObjects.GlobalObjects.Tweets.TryGetValue(tweetId, out var tweet))
146 var userId = tweet.UserId;
147 if (!parsedObjects.GlobalObjects.Users.TryGetValue(userId, out var user))
154 var tweets = TimelineTweet.ExtractTimelineTweets(rootElm);
155 var cursorTop = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Top']]/value")?.Value;
156 var cursorBottom = rootElm.XPathSelectElement("//content/operation/cursor[cursorType[text()='Bottom']]/value")?.Value;
158 return new(statuses.ToArray(), cursorTop, cursorBottom);
162 private record ResponseRoot(
163 [property: DataMember(Name = "globalObjects")]
164 ResponseGlobalObjects GlobalObjects
168 private record ResponseGlobalObjects(
169 [property: DataMember(Name = "users")]
170 Dictionary<string, TwitterUser> Users,
171 [property: DataMember(Name = "tweets")]
172 Dictionary<string, ResponseTweet> Tweets
176 private class ResponseTweet : TwitterStatus
178 [DataMember(Name = "user_id")]
179 public string UserId { get; set; } = "";
182 public readonly record struct NotificationsResponse(
183 TwitterStatus[] Statuses,