本日2017/3/31からTwitterの新仕様が適用されたようです。リプライでスクリーンネーム分のテキスト(@hoge の部分) が文字数としてカウントされなくなる新仕様について、どのようなアルゴリズムで判定されているのかを、Twitter for Androidで実験して調べてみました。取り急ぎ結果をまとめておきます。

正確な情報である保証はありませんので、もしも違う部分がありましたらご指摘願います。

2017/3/31 21:34 空リプについて書き忘れていたので追加しました。
23:22 auto_populate_reply_metadataについて、手順1など数カ所に追記しました。これに伴い元の手順4を削除しました。教えてくれた@java_shitさんありがとうございました。
2017/4/1 2:20 auto_populate_reply_metadata について勘違いがあったため訂正し、手順2.に詳細を記述しました。
2017/4/3 2:30 スクリーンネームが変更された場合の挙動について報告があったので分析しました。結果は本記事の最後に追加しました。@java_shitさんいつもご協力ありがとうございます。

目次

判断のために必要なもの

リプライ対象のツイート(返信を送られる側)と、リプライ(返信を送る側)ツイートの情報です。前者はAPIを叩くことで得られます。後者は自分で送るツイートです。以下ではリプライ対象のツイートを「リプ先」、リプライツイートを「リプ」と呼称します。

手順

0.
前提として status パラメーターの中身は140文字以内である必要があります。Twitterでは、文頭の @hoge 部分、status部分、文の終わりのURL部分とを合わせて本文となります。

1.
リプのパラメーターに in_reply_to_status_id がついているかを見ます。ついていない場合はこれまでと同様の文字数カウントです。

2.
リプのパラメーターの auto_populate_reply_metadata が true になっているかどうかを見ます。
auto_populate_reply_metadata が
true のとき、リプ先のツイートに含まれる @hoge すべて(entities の user_mentions で確認できます)と、リプ先のユーザーのスクリーンネームを合わせたものが文頭に付与されます。この付与された分が、文字数カウントから外れる仕組みです。ただし、リプ先が自分の場合は、自分のスクリーンネームは自動付与されません。

つまり、140文字の制限を回避するためには、巻き込みリプを強制されることになります。

なお、 status の中身がない、あるいは半角スペースや全角スペースなどの場合(いわゆる空リプとなる場合)はツイートできません。

手順3.以降と例は、status に直接 @hoge を記述した場合の文字数カウント方法です(というより display_text_range に示される始点がどこにくるかを判断するアルゴリズムです。display_text_range などは tweet_mode=extended にすることで確認できます)。ツイート時の文字数制限には関係しないのですが、内部でこのような処理が行われている可能性が高いという参考にしてください。巻き込みリプ回避のためには使えるかもしれません。

3.
リプの status の文頭に列挙された @hoge をリストアップします。これをリスト1とします。文頭以外の場所の @hoge はすべて文字数としてカウントされます。このとき、@hoge 同士の間は半角スペースか全角スペースが許されます。ほかは確認していません。
「@bbb @ccc テスト @ddd あいうえお」を@aaaさんに送る場合は、bbb,cccがリスト1になります。

4.
in_reply_to_status_id で示される先のツイート(すなわちリプ先)のスクリーンネームと、リプ先のツイートの本文に含まれるすべての @hoge をリストアップします。これをリスト2とします。これはツイートの entities の user_mentions にまとまっていますので、そこを参照すれば良いでしょう。

5.
リスト1に含まれたスクリーンネームがリスト2に含まれているか、文頭から順番にチェックします。最後までチェックしてすべてリスト2に含まれていたら6に進んでください。
リスト2に含まれていない場合はそのSNの @hoge とそれ以降のテキストすべてが文字数カウントの対象です。含まれていた分はカウントから除外されます。

6.
@hoge 以外のテキストがあるかチェックします。テキストがある場合、@hoge の部分はカウントから除外されます。テキストが無い場合(いわゆる空リプの場合)はこれまでと同じ文字数カウントとなります。

以下ではスクリーンネームはSNと書きます。

例1.

リプ(SN:bbb)「@0 @aaa @999 @1 FF外から失礼します」

リプ先(SN:aaa)「あいうえお @0 @1」

リスト1
0, aaa ,999, 1

リスト2
aaa, 0, 1

999がリスト2に含まれていないので、@aaa までは文字数カウントから除外され、@999以降がカウントされます。すなわち「@999 @1 FF外から失礼します」が本文の文字数となります。

例2.

リプ(SN:aaa)「@1 @0 自分に失礼します」

リプ先(SN:aaa)「あいうえお @0 @1」

リスト1
1, 0

リスト2
aaa, 0, 1

自分へのリプライの場合のカウント方法も同様です(ただし、返ってくるfull_textは異なります。手順1.の補足を参照してください)。
@0までが文字数カウントから除外されます。

例3.

リプ(SN:bbb)「@aaa @0」

リプ先(SN:aaa)「あいうえお @0 @1」

リスト1
aaa , 0

リスト2
aaa, 0, 1

リスト1のaaa,0はどれもリスト2に含まれていますが、それ以外の本文がない「空リプ」のため、これまでと同様の文字数カウントとなります。

例4.

リプ(SN:bbb)「@0 @aaa @999 @1」

リプ先(SN:aaa)「あいうえお @0 @1」

リスト1
0, aaa ,999, 1

リスト2
aaa, 0, 1

@aaa までは文字数カウントから除外され、@999以降、すなわち「@999 @1」が本文の文字数となります。一見すると空リプですが、このような例もあるため気をつけてください。

スクリーンネームが変わっていた場合

ここで説明するのはリプ先のユーザーのスクリーンネームが変わっていた場合の、Twitter公式での挙動です。マジカルな挙動です。
現在確認している挙動は以下の3つです。どれも複数のツイートを見て確認取っているわけではないので、もしかするともうなんかめちゃくちゃマジカルかもしれないです。めちゃくちゃマジカルだったときはTwitterが悪いのでよろしくお願いします。

パターン1

  • リプ先のスクリーンネームが変更
  • 変更前のスクリーンネームのアカウントが存在している

パターン2

  • リプ先のスクリーンネームが変更
  • 変更前のスクリーンネームのアカウントが存在している
  • 変更前のスクリーンネームのアカウント(ツイートのテキストに含まれているもの)と、存在しているアカウントのスクリーンネームに大文字小文字の違いがある

パターン3

  • リプ先のスクリーンネームが変更
  • 変更前のスクリーンネームのアカウントが存在していない

変更前のスクリーンネームがテキストに含まれているにもかかわらず表示上消えて、変更後のスクリーンが表示されます。

どういうことかというと、次の例のとおりです。

テキスト
リプ(SN:ccc)「@aaa もう夜だぞ」
リプ先(SN:bbb | aaaから変更)「おはよう!」

リプのアプリでの表示
返信先:@bbbさん
「もう夜だぞ」

となります。@aaaが消えます。不思議ですね。

実例がなくて申し訳ありません。

分析

個人的にはパターン2とパターン3がとてもマジカル挙動でキツかったんですが、その代わりこれらの裏で何が起きているかの予測が立てられるようになりました。あと闇が見えました。
in_reply_to_screen_name を最初に見て、次にテキストの文頭についている @hoge を entities を見ずにテキストから直接解析しているようです。
現在存在していないスクリーンネームは entities で確認することができませんので、パターン3で現在存在していないスクリーンネームの表示が消える理由としてはテキスト解析をしているというものが妥当だと思われます。何が起きているかわかりにくいので闇ですね。
また、実在するかどうかの判定では大文字小文字が区別されている可能性が高いです。これはパターン2からです。なんにせよ闇を感じますね。

おわり

わかりにくい説明で申し訳ありませんが、ひとまずここまでです。

今回実験に協力してくれた皆さま、ありがとうございました。