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