1 // OpenTween - Client of Twitter
2 // Copyright (c) 2023 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;
28 using System.Threading.Tasks;
29 using System.Xml.Linq;
30 using System.Xml.XPath;
31 using OpenTween.Api.DataModel;
33 namespace OpenTween.Api.GraphQL
35 public class TimelineTweet
37 public const string TypeName = nameof(TimelineTweet);
39 public XElement Element { get; }
41 public TimelineTweet(XElement element)
43 var typeName = element.Element("itemType")?.Value;
44 if (typeName != TypeName)
45 throw new ArgumentException($"Invalid itemType: {typeName}", nameof(element));
47 this.Element = element;
50 public TwitterStatus ToTwitterStatus()
54 var resultElm = this.Element.Element("tweet_results")?.Element("result") ?? throw CreateParseError();
55 return this.ParseTweetUnion(resultElm);
57 catch (WebApiException ex)
59 ex.ResponseText = JsonUtils.JsonXmlToString(this.Element);
60 MyCommon.TraceOut(ex);
65 private TwitterStatus ParseTweetUnion(XElement tweetUnionElm)
67 var tweetElm = tweetUnionElm.Element("__typename")?.Value switch
69 "Tweet" => tweetUnionElm,
70 "TweetWithVisibilityResults" => tweetUnionElm.Element("tweet") ?? throw CreateParseError(),
71 _ => throw CreateParseError(),
74 return this.ParseTweet(tweetElm);
77 private TwitterStatus ParseTweet(XElement tweetElm)
79 var tweetLegacyElm = tweetElm.Element("legacy") ?? throw CreateParseError();
80 var userElm = tweetElm.Element("core")?.Element("user_results")?.Element("result") ?? throw CreateParseError();
81 var userLegacyElm = userElm.Element("legacy") ?? throw CreateParseError();
82 var retweetedTweetElm = tweetLegacyElm.Element("retweeted_status_result")?.Element("result");
84 static string GetText(XElement elm, string name)
85 => elm.Element(name)?.Value ?? throw CreateParseError();
87 static string? GetTextOrNull(XElement elm, string name)
88 => elm.Element(name)?.Value;
92 IdStr = GetText(tweetElm, "rest_id"),
93 Source = GetText(tweetElm, "source"),
94 CreatedAt = GetText(tweetLegacyElm, "created_at"),
95 FullText = GetText(tweetLegacyElm, "full_text"),
96 InReplyToScreenName = GetTextOrNull(tweetLegacyElm, "in_reply_to_screen_name"),
97 InReplyToStatusIdStr = GetTextOrNull(tweetLegacyElm, "in_reply_to_status_id_str"),
98 InReplyToUserId = GetTextOrNull(tweetLegacyElm, "in_reply_to_user_id_str") is string userId ? long.Parse(userId) : null,
99 Favorited = GetTextOrNull(tweetLegacyElm, "favorited") is string favorited ? favorited == "true" : null,
102 UserMentions = tweetLegacyElm.XPathSelectElements("entities/user_mentions/item")
103 .Select(x => new TwitterEntityMention()
105 Indices = x.XPathSelectElements("indices/item").Select(x => int.Parse(x.Value)).ToArray(),
106 ScreenName = GetText(x, "screen_name"),
109 Urls = tweetLegacyElm.XPathSelectElements("entities/urls/item")
110 .Select(x => new TwitterEntityUrl()
112 Indices = x.XPathSelectElements("indices/item").Select(x => int.Parse(x.Value)).ToArray(),
113 DisplayUrl = GetText(x, "display_url"),
114 ExpandedUrl = GetText(x, "expanded_url"),
115 Url = GetText(x, "url"),
118 Hashtags = tweetLegacyElm.XPathSelectElements("entities/hashtags/item")
119 .Select(x => new TwitterEntityHashtag()
121 Indices = x.XPathSelectElements("indices/item").Select(x => int.Parse(x.Value)).ToArray(),
122 Text = GetText(x, "text"),
126 ExtendedEntities = new()
128 Media = tweetLegacyElm.XPathSelectElements("extended_entities/media/item")
129 .Select(x => new TwitterEntityMedia()
131 Indices = x.XPathSelectElements("indices/item").Select(x => int.Parse(x.Value)).ToArray(),
132 DisplayUrl = GetText(x, "display_url"),
133 ExpandedUrl = GetText(x, "expanded_url"),
134 Url = GetText(x, "url"),
135 MediaUrlHttps = GetText(x, "media_url_https"),
136 Type = GetText(x, "type"),
137 AltText = GetTextOrNull(x, "ext_alt_text"),
143 Id = long.Parse(GetText(userElm, "rest_id")),
144 IdStr = GetText(userElm, "rest_id"),
145 Name = GetText(userLegacyElm, "name"),
146 ProfileImageUrlHttps = GetText(userLegacyElm, "profile_image_url_https"),
147 ScreenName = GetText(userLegacyElm, "screen_name"),
148 Protected = GetTextOrNull(userLegacyElm, "protected") == "true",
150 RetweetedStatus = retweetedTweetElm != null ? this.ParseTweetUnion(retweetedTweetElm) : null,
154 private static Exception CreateParseError()
155 => throw new WebApiException("Parse error on TimelineTweet");
157 public static TimelineTweet[] ExtractTimelineTweets(XElement element)
159 return element.XPathSelectElements($"//itemContent[itemType[text()='{TypeName}']][tweetDisplayType[text()='Tweet']]")
160 .Select(x => new TimelineTweet(x))