{
using (var twitter = new Twitter())
{
- Assert.Equal(140, twitter.GetTextLengthRemain(""));
- Assert.Equal(132, twitter.GetTextLengthRemain("hogehoge"));
+ Assert.Equal(280, twitter.GetTextLengthRemain(""));
+ Assert.Equal(272, twitter.GetTextLengthRemain("hogehoge"));
}
}
Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
+
+ // t.co に短縮される分の文字数を考慮
+ twitter.Configuration.ShortUrlLength = 20;
+ Assert.Equal(9971, twitter.GetTextLengthRemain("D twitter hogehoge http://example.com/"));
+
+ twitter.Configuration.ShortUrlLengthHttps = 21;
+ Assert.Equal(9970, twitter.GetTextLengthRemain("D twitter hogehoge https://example.com/"));
}
}
using (var twitter = new Twitter())
{
// t.co に短縮される分の文字数を考慮
- twitter.Configuration.ShortUrlLength = 20;
- Assert.Equal(120, twitter.GetTextLengthRemain("http://example.com/"));
- Assert.Equal(120, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
- Assert.Equal(111, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
-
- twitter.Configuration.ShortUrlLengthHttps = 21;
- Assert.Equal(119, twitter.GetTextLengthRemain("https://example.com/"));
- Assert.Equal(119, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
- Assert.Equal(110, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
+ twitter.TextConfiguration.TransformedURLLength = 20;
+ Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/"));
+ Assert.Equal(260, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
+
+ Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/"));
+ Assert.Equal(260, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
}
}
using (var twitter = new Twitter())
{
// t.co に短縮される分の文字数を考慮
- twitter.Configuration.ShortUrlLength = 20;
- Assert.Equal(120, twitter.GetTextLengthRemain("example.com"));
- Assert.Equal(120, twitter.GetTextLengthRemain("example.com/hogehoge"));
- Assert.Equal(111, twitter.GetTextLengthRemain("hogehoge example.com"));
+ twitter.TextConfiguration.TransformedURLLength = 20;
+ Assert.Equal(260, twitter.GetTextLengthRemain("example.com"));
+ Assert.Equal(260, twitter.GetTextLengthRemain("example.com/hogehoge"));
+ Assert.Equal(251, twitter.GetTextLengthRemain("hogehoge example.com"));
// スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
- Assert.Equal(130, twitter.GetTextLengthRemain("example.jp"));
+ Assert.Equal(270, twitter.GetTextLengthRemain("example.jp"));
// ただし、末尾にパスが続く場合は t.co に短縮される
- Assert.Equal(120, twitter.GetTextLengthRemain("example.jp/hogehoge"));
+ Assert.Equal(260, twitter.GetTextLengthRemain("example.jp/hogehoge"));
}
}
{
using (var twitter = new Twitter())
{
- Assert.Equal(139, twitter.GetTextLengthRemain("🍣"));
- Assert.Equal(133, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
+ Assert.Equal(278, twitter.GetTextLengthRemain("🍣"));
+ Assert.Equal(267, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
}
}
}
--- /dev/null
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~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 <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System.Runtime.Serialization;
+
+namespace OpenTween.Api.DataModel
+{
+ [DataContract]
+ public class TwitterTextConfiguration
+ {
+ [DataMember(Name = "version")]
+ public string Version { get; set; }
+
+ [DataMember(Name = "maxWeightedTweetLength")]
+ public int MaxWeightedTweetLength { get; set; }
+
+ [DataMember(Name = "scale")]
+ public int Scale { get; set; }
+
+ [DataMember(Name = "defaultWeight")]
+ public int DefaultWeight { get; set; }
+
+ [DataMember(Name = "transformedURLLength")]
+ public int TransformedURLLength { get; set; }
+
+ [DataContract]
+ public class CodepointRange
+ {
+ [DataMember(Name = "start")]
+ public int Start { get; set; }
+
+ [DataMember(Name = "end")]
+ public int End { get; set; }
+
+ [DataMember(Name = "weight")]
+ public int Weight { get; set; }
+ }
+
+ [DataMember(Name = "ranges")]
+ public CodepointRange[] Ranges { get; set; }
+
+ /// <exception cref="SerializationException"/>
+ public static TwitterTextConfiguration ParseJson(string json)
+ {
+ return MyCommon.CreateDataFromJson<TwitterTextConfiguration>(json);
+ }
+
+ public static TwitterTextConfiguration DefaultConfiguration()
+ {
+ // 参照: https://developer.twitter.com/en/docs/developer-utilities/twitter-text
+ return new TwitterTextConfiguration
+ {
+ Version = "2",
+ MaxWeightedTweetLength = 280,
+ Scale = 100,
+ DefaultWeight = 200,
+ TransformedURLLength = 23,
+ Ranges = new[]
+ {
+ new CodepointRange { Start = 0, End = 4351, Weight = 100 },
+ new CodepointRange { Start = 8192, End = 8205, Weight = 100 },
+ new CodepointRange { Start = 8208, End = 8223, Weight = 100 },
+ new CodepointRange { Start = 8242, End = 8247, Weight = 100 },
+ },
+ };
+ }
+ }
+}
<Compile Include="Api\DataModel\TwitterSearchResult.cs" />
<Compile Include="Api\DataModel\TwitterStatus.cs" />
<Compile Include="Api\DataModel\TwitterStreamEvent.cs" />
+ <Compile Include="Api\DataModel\TwitterTextConfiguration.cs" />
<Compile Include="Api\DataModel\TwitterUploadMediaResult.cs" />
<Compile Include="Api\DataModel\TwitterUser.cs" />
<Compile Include="Api\DataModel\TwitterApiAccessLevel.cs" />
更新履歴
==== Ver 1.4.1-dev(xxxx/xx/xx)
+ * NEW: ツイート文字数の280文字への上限緩和に対応しました
+ - Twitter公式クライアントなどと同様に、入力する文字種によって文字数の扱いが異なるものになります
+ - DMの送信時に行われる文字数のカウントは従来通りのままです
* CHG: 画像投稿の対応サービスから「img.ly」「yfrog」「ついっぷるフォト」を削除
- アップデート前にこれらのサービスを投稿先に選択していた場合は「Twitter」に変更されます
* CHG: 画像投稿のキャンセル時に選択中の画像表示を閉じないように動作を変更
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();
{
this.Api = api;
this.Configuration = TwitterConfiguration.DefaultConfiguration();
+ this.TextConfiguration = TwitterTextConfiguration.DefaultConfiguration();
}
public TwitterApiAccessLevel AccessLevel
{
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;
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;
}