OSDN Git Service

ツイート文字数の280文字への上限緩和 (weightedLength) に対応
[opentween/open-tween.git] / OpenTween / Twitter.cs
index f3f0951..340b270 100644 (file)
@@ -149,6 +149,7 @@ namespace OpenTween
 
         public TwitterApi Api { get; }
         public TwitterConfiguration Configuration { get; private set; }
+        public TwitterTextConfiguration TextConfiguration { get; private set; }
 
         delegate void GetIconImageDelegate(PostClass post);
         private readonly object LockObj = new object();
@@ -181,6 +182,7 @@ namespace OpenTween
         {
             this.Api = api;
             this.Configuration = TwitterConfiguration.DefaultConfiguration();
+            this.TextConfiguration = TwitterTextConfiguration.DefaultConfiguration();
         }
 
         public TwitterApiAccessLevel AccessLevel
@@ -280,8 +282,7 @@ namespace OpenTween
         {
             this.CheckAccountState();
 
-            if (mediaIds == null &&
-                Twitter.DMSendTextRegex.IsMatch(postStr))
+            if (Twitter.DMSendTextRegex.IsMatch(postStr))
             {
                 await this.SendDirectMessage(postStr)
                     .ConfigureAwait(false);
@@ -313,8 +314,7 @@ namespace OpenTween
 
             using (var origImage = item.CreateImage())
             {
-                MemoryImage newImage;
-                if (alphaPNGWorkaround && this.AddAlphaChannelIfNeeded(origImage.Image, out newImage))
+                if (alphaPNGWorkaround && this.AddAlphaChannelIfNeeded(origImage.Image, out var newImage))
                 {
                     using (var newMediaItem = new MemoryImageMediaItem(newImage))
                     {
@@ -603,8 +603,7 @@ namespace OpenTween
             }
 
             var minimumId = this.CreatePostsFromJson(statuses, MyCommon.WORKERTYPE.Timeline, tab, read);
-
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -627,8 +626,7 @@ namespace OpenTween
             }
 
             var minimumId = this.CreatePostsFromJson(statuses, MyCommon.WORKERTYPE.Reply, tab, read);
-
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -663,7 +661,7 @@ namespace OpenTween
 
             var minimumId = CreatePostsFromJson(statuses, MyCommon.WORKERTYPE.UserTimeline, tab, read);
 
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -842,11 +840,11 @@ namespace OpenTween
             post.RetweetedBy = post.RetweetedBy != null ? string.Intern(post.RetweetedBy) : null;
 
             //Source整形
-            var source = ParseSource(sourceHtml);
-            post.Source = string.Intern(source.Item1);
-            post.SourceUri = source.Item2;
+            var (sourceText, sourceUri) = ParseSource(sourceHtml);
+            post.Source = string.Intern(sourceText);
+            post.SourceUri = sourceUri;
 
-            post.IsReply = post.ReplyToList.Contains(_uname);
+            post.IsReply = post.RetweetedId == null && post.ReplyToList.Contains(_uname);
             post.IsExcludeReply = false;
 
             if (post.IsMe)
@@ -879,8 +877,7 @@ namespace OpenTween
                 var match = Twitter.StatusUrlRegex.Match(url);
                 if (match.Success)
                 {
-                    long statusId;
-                    if (long.TryParse(match.Groups["StatusId"].Value, out statusId))
+                    if (long.TryParse(match.Groups["StatusId"].Value, out var statusId))
                         yield return statusId;
                 }
             }
@@ -997,7 +994,7 @@ namespace OpenTween
 
             var minimumId = CreatePostsFromJson(statuses, MyCommon.WORKERTYPE.List, tab, read);
 
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -1077,8 +1074,7 @@ namespace OpenTween
                 .Concat(Twitter.ThirdPartyStatusUrlRegex.Matches(text).Cast<Match>());
             foreach (var _match in ma)
             {
-                Int64 _statusId;
-                if (Int64.TryParse(_match.Groups["StatusId"].Value, out _statusId))
+                if (Int64.TryParse(_match.Groups["StatusId"].Value, out var _statusId))
                 {
                     if (relPosts.ContainsKey(_statusId))
                         continue;
@@ -1140,7 +1136,7 @@ namespace OpenTween
 
             var minimumId = this.CreatePostsFromSearchJson(searchResult, tab, read, more);
 
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -1316,7 +1312,7 @@ namespace OpenTween
 
             var minimumId = this.CreateFavoritePostsFromJson(statuses, read);
 
-            if (minimumId != null && minimumId.Value < tab.OldestId)
+            if (minimumId != null)
                 tab.OldestId = minimumId.Value;
         }
 
@@ -1596,10 +1592,10 @@ namespace OpenTween
         /// <summary>
         /// Twitter APIから得たHTML形式のsource文字列を分析し、source名とURLに分離します
         /// </summary>
-        public static Tuple<string, Uri> ParseSource(string sourceHtml)
+        internal static (string SourceText, Uri SourceUri) ParseSource(string sourceHtml)
         {
             if (string.IsNullOrEmpty(sourceHtml))
-                return Tuple.Create<string, Uri>("", null);
+                return ("", null);
 
             string sourceText;
             Uri sourceUri;
@@ -1626,7 +1622,7 @@ namespace OpenTween
                 sourceUri = null;
             }
 
-            return Tuple.Create(sourceText, sourceUri);
+            return (sourceText, sourceUri);
         }
 
         public async Task<TwitterApiStatus> GetInfoApi()
@@ -1714,12 +1710,12 @@ namespace OpenTween
         {
             var matchDm = Twitter.DMSendTextRegex.Match(postText);
             if (matchDm.Success)
-                return this.GetTextLengthRemainInternal(matchDm.Groups["body"].Value, isDm: true);
+                return this.GetTextLengthRemainDM(matchDm.Groups["body"].Value);
 
-            return this.GetTextLengthRemainInternal(postText, isDm: false);
+            return this.GetTextLengthRemainWeighted(postText);
         }
 
-        private int GetTextLengthRemainInternal(string postText, bool isDm)
+        private int GetTextLengthRemainDM(string postText)
         {
             var textLength = 0;
 
@@ -1744,10 +1740,54 @@ namespace OpenTween
                 textLength += shortUrlLength - url.Length;
             }
 
-            if (isDm)
-                return this.Configuration.DmTextCharacterLimit - textLength;
-            else
-                return 140 - textLength;
+            return this.Configuration.DmTextCharacterLimit - textLength;
+        }
+
+        private int GetTextLengthRemainWeighted(string postText)
+        {
+            var config = this.TextConfiguration;
+            var totalWeight = 0;
+
+            var urls = TweetExtractor.ExtractUrlEntities(postText).ToArray();
+
+            var pos = 0;
+            while (pos < postText.Length)
+            {
+                var urlEntity = urls.FirstOrDefault(x => x.Indices[0] == pos);
+                if (urlEntity != null)
+                {
+                    totalWeight += config.TransformedURLLength * config.Scale;
+
+                    var urlLength = urlEntity.Indices[1] - urlEntity.Indices[0];
+                    pos += urlLength;
+
+                    continue;
+                }
+
+                var codepoint = char.ConvertToUtf32(postText, pos);
+                var weight = config.DefaultWeight;
+
+                foreach (var weightRange in config.Ranges)
+                {
+                    if (codepoint >= weightRange.Start && codepoint <= weightRange.End)
+                    {
+                        weight = weightRange.Weight;
+                        break;
+                    }
+                }
+
+                totalWeight += weight;
+
+                var isSurrogatePair = codepoint > 0xffff;
+                if (isSurrogatePair)
+                    pos += 2; // サロゲートペアの場合は2文字分進める
+                else
+                    pos++;
+            }
+
+            var remainWeight = config.MaxWeightedTweetLength * config.Scale - totalWeight;
+
+            return remainWeight / config.Scale;
         }
 
 
@@ -1978,6 +2018,10 @@ namespace OpenTween
                 MyCommon.TraceOut(ex);
                 return;
             }
+            catch (XmlException)
+            {
+                MyCommon.TraceOut("XmlException (StatusArrived): " + line);
+            }
             catch(NullReferenceException)
             {
                 MyCommon.TraceOut("NullRef StatusArrived: " + line);
@@ -2029,8 +2073,7 @@ namespace OpenTween
             evt.Username = eventData.Source.ScreenName;
             evt.IsMe = evt.Username.ToLowerInvariant().Equals(this.Username.ToLowerInvariant());
 
-            MyCommon.EVENTTYPE eventType;
-            eventTable.TryGetValue(eventData.Event, out eventType);
+            eventTable.TryGetValue(eventData.Event, out var eventType);
             evt.Eventtype = eventType;
 
             TwitterStreamEvent<TwitterStatusCompat> tweetEvent;
@@ -2075,9 +2118,8 @@ namespace OpenTween
 
                     var tabinfo = TabInformations.GetInstance();
 
-                    PostClass post;
                     var statusId = tweet.Id;
-                    if (!tabinfo.Posts.TryGetValue(statusId, out post))
+                    if (!tabinfo.Posts.TryGetValue(statusId, out var post))
                         break;
 
                     if (eventData.Event == "favorite")