はじめに
9/28 9:21に、私のアカウントtoshi_aが凍結された。 Twitterクライアントの開発はTwitterの細かな仕様と向き合い続けることにほかならない。Twitter APIのドキュメント化されていない(するまでもない)挙動やバグを知って知的好奇心を満たすことがTwitterクライアントの開発を続けるモチベーションの一つになっていることは否定できない。 そのため、今回実際に自分が一時的な凍結をされて、手元で一時的な凍結中のAPIレスポンスを受け取ることができたのは願ってもみない幸運であった。
この記事では、今回わかったことを、mikutterユーザ(=mikutterハッカー)や他のTwitterサードパーティアプリケーション開発者に共有したい。
対象とする読者・しない読者
対象: mikutterユーザ又はTwitterのサードパーティ開発者
mikutterを普段使っていて、アカウントの凍結がどういった技術的な影響をmikutterに与えるか知りたい人や、自分もTwitter APIを利用したアプリケーションを開発している・興味がある人のために書いた。
対象ではない: Twitterの今後が心配な人
一方、凍結はどのような基準で行われるのかとか、そもそも凍結とは何なのかといった話はここではメインでは扱わないため、満足できる情報はないと思われる。
今回起こったこと
sushi514に送ったリプライが、恐らくsushi514にスパム報告されたことをトリガーとしてtoshi_aが一時的な凍結(Temporarily Locked)をされた。
これが問題のツイート。
mikutterに数々の貢献をしている有名なmikutterハッカーsushi514のハンドルネームが「すし」であることは説明の必要がないが、彼曰くこれは私が命名したハンドルネームらしい。全く憶えがない。驚いた。今回の記事はこれが書きたかっただけなので、以下に続く文はすべてどうでもいい。 <!--
そして、憶えがないのに新しい名前を再び要求されたため、仕方なく命名してやったのが以上のツイートを行った経緯。
一般にはたんなる女性器の名称として用いられる単語だが、mikutterヘビーユーザの多くは女性器に縁がないため、全く異なる意味で使われることがある。
2015年4月1日、mikutterは事実上の死を迎えたことは記憶に新しい。mikutterの開発が私一人に強く依存していることはご存知の通りで、そのため私がモチベーションを失ったり、死亡するようなことがあればmikutterの開発は終了すると考えてまず間違いない。よって私が僅かとはいえ死の危険がある病に倒れたことはmikutterエコシステムに衝撃を与えた。手術が終わって意識を取り戻した私が最初にしたツイートがこれである。
したがってこの言葉はmikutterにとっては重い意味をもつようになり、永遠に続くmikutterプロジェクトを称える言葉として再定義された。私がそれをsushi514の新たなハンドルネームとして提案したことは、彼に深い驚きと畏れを与えたことだろうが、彼にはそれを名乗る資格があると私は考えた。
そのツイートに対してスパム報告を行うことを理解できない人もいるかもしれないが、スパム報告もまた特定の文脈では異なる意味を持つ行為だ。Krileの開発者karnoがまだ学生だった頃、彼は自ら作ったKrileの操作を誤り(と、彼は言っていた)私に全サブ垢でスパム報告を行ってしまう。弱みを握った私は京都駅で新幹線に飛び乗り、当時彼が住んでいた名古屋までわざわざ行って当時学生であった(大事なことなので二度)karnoに矢場とんで1000円ぶんの食事を奢らせるという事件があった*1。そのさい、まだかろうじて百合の事以外を考えることが出来ていたkarnoとTwitterクライアント開発者として有意義な情報交換を行ったことから、スパム報告も重い意味を持つようになった*2。
以上のことを理解していないと、sushi514改めまんこ氏が私のツイートをスパム報告した理由を誤解することになるだろう。決して、私が女性器の名前をツイートしたことでsushi514が憤り、死ねやカスと考えてスパム報告をしたというわけではない。
結果として、Twitterは(恐らく)スパム報告をトリガーとして、私のアカウントを一時的な凍結状態にしたというのが、今回のあらましであった。
とても大事なこと
一時的な凍結状態は、なりたいと思ってなれるわけではない。また、Twitterを拠り所にしているツイッター廃人からすれば、凍結状態にある間は精神的にかなり追い詰められており、間違っても「今API叩いたらどうなる?www」などと聞いて良いものではないから、最近は誰でも凍結されているにもかかわらず、その全容は謎に包まれていた。
したがって凍結に気づいたときには、凍結時のAPIレスポンスなどを検証する使命を自覚し、皆から注がれているであろう期待に押しつぶされそうになった。冷静に調査が出来ていたというと嘘になる。以下に書くのは実際に調査して得られた結果だが、事実と異なっている可能性もある。
心配された事案
Twitter APIにアクセスするためには、Twitter APIを利用するためのサードパーティアプリケーションの登録を行い、アプリケーションのConsumer Keyなどの情報を発行してもらう必要がある。これはTwitterアカウントを持っていれば誰でも簡単に行えるが、アプリケーションは申請したTwitterアカウントをオーナーとして持つ。したがって、mikutterのオーナーであるアカウントtoshi_aが停止された場合、mikutterのアプリケーションとしての権利が取り下げられ、そのConsumer Keyを使っているmikutterを利用できなくなる可能性が考えられた。
そうであれば、私のアカウントが凍結されるのは私だけの問題ではないということになる。
実際にはどうだったのか
利用できた。
しかし私が今回受けたのは一時的な凍結であり、最近話題になっている永久凍結とは違う。今後永久凍結された時に、Consumer Keyがどうなるかは、凍結された人が実験しなければ確かなことはわからない。
mikutterが利用できなくなるのか
できる。
前述の通り実際のところは不明だが、以下toshi_aの永久凍結がConsumer Keyの凍結に直結すると仮定して話を進める。
Twitter APIは最初、APIをリクエストする度にBasic認証でIDとパスワードを送る仕組みで、サードパーティアプリケーションを承認する現在のような仕組みは存在しなかった*3。Basic認証を廃止しOAuthに移行する時に一切は作られたが、Consumer Keyなどの情報をエンドユーザに公開すべきでないと考えた私は、オープンソースのTwitterクライアントの取るべき対応についてTwitterに問うた。
得た回答の原文は残っていないが、「別に公開しても構わないし、公開したくないなら、ユーザ全員に開発者登録してもらって、独自にConsumer Keyを取得させればいい」というものだった。ユーザ全員に開発者登録を強いるとかアホかと思ったことを強く記憶している。したがって、mikutterにかかれているConsumer Keyが無効になったとしても、ユーザは自分で取得したそれに置き換えてmikutterを利用することはTwitterに(少なくとも当時は)認められている。
尤も、あなたがOSSであるところのmikutterをフォークして独自のTwitterクライアントを作成する自由は侵すことはできないと思うので、これは無意味な理屈だ。
REST API
凍結されているユーザが受け取るTwitter APIレスポンス
Twitter APIは、リクエストが失敗すると、本来期待されるオブジェクトの代わりに、エラーオブジェクトをJSON形式で返すことがある。 具体的には以下のようなものだ
{ "errors": [ { "code": 63, "message": "User has been suspended." } ] }
例えば現在永久凍結を受けているTwitterアカウントの一つであるyukkuri_sinaiのuserオブジェクトをTwitter APIから直接得る次のようなmikutterコードは
(Service.primary/:users/:show).user(screen_name: 'yukkuri_sinai', cache: false).trap{|x| p x }
次のような結果を得る。
#<#<Class:0x00007eff1c6d4260>: User has been suspended.>
この無名クラスはMikuTwitter::TwitterErrorをスーパークラスとして持っていて、前述のエラーレスポンスの内容をもとに作られてraiseされる例外。codeメソッドでTwitterがエラーの種類ごとに付けているエラー番号を得ることが出来る。この場合は 63
という値が得られた。
さて、mikutterを利用しているアカウントが凍結されると、REST APIはすべてHTTPレスポンスコード403を返し、bodyは次のようになっている。
{ "errors": [ { "code": 326, "message": "To protect our users from spam and other malicious activity, this account is temporarily locked. Please log in to https://twitter.com to unlock your account." } ] }
326
は、mikutterでは未定義となっていたので、今回追加することにした。また、 message
キーの値から、この状態をTemporarily Locked、一時的な凍結状態と呼ぶことにした。
エラーコードは Response Codes — Twitter Developers にまとまっているのだが、すべてのエラーコードが網羅されているわけではない。そのためサードパーティのアプリケーション開発者は未知のエラーコードを蒐集する趣味をたいてい持っていて、私もその一人なのだ。コード326はdocumentedだがmikutterにとっては未知で、恐らく私が最後にドキュメントをあたったときには書かれていないものだったと思われる(または、凍結なんてレニウム以外されないだろうと考えて、読み飛ばしていたのだろうか)。
いずれにせよ、自分でエラーコード326を受け取ったのは初めての体験だったので、大きな収穫だったといえる。
レートリミット
Twitter API 1.1からはエンドポイント毎に1時間単位あたりのリクエスト可能回数(レートリミット)が設定されており、専用のAPIで一括して最大数、残り回数、リセット時刻を取得できるほか、毎回のレスポンスのHTTPレスポンスヘッダにもそれらの情報が記載されている(Rate Limiting — Twitter Developers)。
面白かったのは、毎回エラーコード326を返してくるが、レスポンスヘッダを見る限り残り回数はリクエストする度にデクリメントされていたこと。結局レスポンスを受け取れていないので意味はないかと思いきや、凍結が解除されても規制されてしまったらリセット時刻になるまではAPIリクエストができない。Twitter APIに直接リクエストするプラグインを作るときにはリトライ処理について慎重になっておかないと、場合によっては凍結が解除されても一部の機能がちゃんと動いていないという事態になりかねない。Twitter APIを直接利用するプラグインは少ないと思うが、プラグイン開発者は改めて注意しよう。
Streaming API
コネクションを確立することができ、ツイートの受信やイベントの通知を受け取ることが出来た。しかし何やら挙動があやしく、コネクションは切れていないのにイベントが一切受信されなくなることがあった。mikutterがUser Streamに再接続すると、またしばらくは受信できた。
凍結されているユーザの情報をAPIで得ようとした場合
全く影響なくできる。
Twitter公式クライアントでは、以下のように警告されるが、ツイートを見ることはできる。
一方サードパーティむけAPIでは、あるユーザが一時的な凍結を受けているかを知る直接的な方法はなさそうだった。
凍結中のmikutterの振る舞い
このごろはTwitterアカウントの凍結は珍しいことでも無くなってきた。その時にmikutterを使っているとどうなるか気になる。
ホームタイムライン・メンション
REST APIがエラーコード326で失敗するため、ツイートが表示されない。しかしUser Streamを使ってツイートを受信できるので、起動後に投稿されたツイートに関しては受信できる。
プロフィール
プロフィールは、1ユーザにつき1つのタブが生成され、タブが生成・復元される時に毎回APIリクエストを明示的に行っている。 生成というのは、ユーザのプロフィールを明示的に開いた時のことで、復元というのは、mikutterを起動した直後に、前回終了時に開いていたプロフィールが再度開かれる処理のこと。
復元のときには前回のデータを使いそうなものだが、現在の実装では最新の情報を表示するために毎回APIリクエストし、失敗すれば、登録されている他のアカウントでリクエストしていって、遂に取得できなければキャッシュされている情報を頼る。キャッシュとはAPIレスポンスのディスクキャッシュ、ユーザをキャッシュするサードパーティプラグインが提供するキャッシュ(現在はそういうプラグインは存在しないかも)、そしてUser Streamから得られた情報から生成されたUserオブジェクトのいずれか。
そうしてタブの生成に成功すれば、user fragment(プロフィールタブの中のタブ)が生成され、以下の情報の取得をはじめる。
- ユーザタイムライン(そのユーザの直近のツイート)
- フォロイー・フォロワー
- リストの追加状況
- そのユーザと登録アカウントがやり取りしたダイレクトメッセージ
これらはAPIリクエストを発生させるため、すべての取得に失敗する。
しかしユーザタイムラインに関しては、User Streamで受信できたツイートを監視して、そのタブに表示すべきツイートが見つかれば随時追加していく。mikutterではタイムラインの更新という概念はなく、常に最新の情報を表示することになっている。したがって少しでもツイートを受け取れるなら、その情報を使おうとする。
セーブドサーチ
セーブドサーチもAPIリクエストを行うので、どのような検索が保存されているかをそもそも取得できないが、mikutterを起動した後の最初の一回のリクエストはキャッシュのみを利用し、APIリクエストを発生させないように努め、あたかもリクエストが成功したかのように振る舞う。そのためセーブドサーチタブの復元自体には成功する。しかし肝心の検索は失敗するため、空のタイムラインが表示されることになる。
抽出タブ
過去のものについて表示できないのは同じ。 これはデータソースによるが、「受信した全ての投稿」はUser Streamで受信したものも対象となる。「ホームタイムライン」「メンション」などは一見動かなさそうだが、これもUser Streamを使って補われる。
リスト
セーブドサーチと同じで、起動後の最初のリクエストはキャッシュから作成されるため、プロフィールや抽出タブデータソースに表示されるリストの一覧は表示される。ただし、リストのメンバーはユーザIDのみキャッシュしているのか正しく復元できず、ユーザのプロフィールを見ても、いずれのリストにも入っていないような表示がされる。
当然、リストへ追加するのはAPIリクエストを伴うため、できない。
コーディング
前述のようにtrapブロックの引数にMikuTwitter::TwitterErrorのインスタンスが渡されるため、アカウントが凍結された時だけ異なる処理をすることは出来る。
ほか、具体的にどういったエラーコードが存在するかは、mikutterのソースコードの https://dev.mikutter.hachune.net/projects/mikutter/repository/revisions/master/entry/core/lib/mikutwitter/error.rb#L36 を見るとわかる。ここに記載のないエラーコードは、専用クラスではなく無名クラスが作られて代用される。前述のtrapブロックの例で無名クラスになっていたのは、意外にも63(User has been suspended.)がここに書かれていないためだった。 当然、やむを得ず分岐する必要がある場合は、エラー番号で分岐するよりも、用意されているクラスで分岐するほうがいい。そうすればマジックナンバーをコードに現れることが無くなるし、caseを使えば、TwitterErrorじゃない場合とかもフラットに書ける。
case exc when MikuTwitter::RateLimitError activity :system, '規制されてるよ' when MikuTwitter::RheniumError activity :system, 'あなたはレニウムだよ' when Exception activity :system, exc.message else activity :system, '予期せぬエラーが発生しました' end
どうでもいいこと
一時的な凍結と永久凍結の違い
どちらも前述したが、一時的な凍結(Temporariry Locked)は今回私が受けたもので、APIレスポンスの内容からそのように呼んでいる。 一方永久凍結は先程の例でいうとyukkuri_sinaiが受けたもので、同じようにAPIレスポンスから名付けるなら「サスペンド」あたりが妥当な気がするが、俗に言われている永久凍結という言葉を使って区別することにした。
まだ永久凍結はされていないので一時的な凍結しかわからないが、思いついただけでも以下のような違いがあるようだ。
一時的な凍結 | 永久凍結 | |
---|---|---|
他人が凍結アカウントを閲覧できる? | ○ | × |
User Streamを利用できる? | ○ | ? |
リストを利用できる? | × | ○ |
アカウントを復旧できる? | ○ | △ |
凍結された理由が説明される? | ○ | × |
以上のように、同じように凍結と言われるが、全く異なるものということがわかる。永久凍結については、前述のエラーレスポンスの例でも使ったアカウントyukkuri_sinaiの持ち主の記事が詳しい。
これを見る限り、凍結された後にアカウント復旧ウィザードに案内され、比較的すぐにアカウントが復旧できるのが一時的な凍結の特徴といえそうだ。
今回の凍結の原因の考察
アカウント復旧ウィザードは次のような感じだった。まだ見たこともない人も多いだろうが、多くの人が受けたものと私が受けたのは微妙に違ったので、面白いから共有する。
みんなと違うのは、この後に電話番号の入力だけを求められ、原因となったと思われるツイートの削除が求められなかったこと。
これは本当は墓まで持っていきたいくらい個人的には恥ずかしいことなのだが、ここまで読んで居る人などいないと信じて(そのために読むのが嫌になるような文体でこの記事を書いている)こっそり書いてしまうと、私はアカウントtoshi_aに電話番号を登録していなかったため、今回はそのことが間接的な原因になったのではないかと考えている。
言い訳をすると、現在Twitterは電話番号を登録することを推奨しているようで、アカウントを新規に登録したときにも促されるが、私がアカウントを取った頃にはそんなものはなかった。 半年に一回設定を全確認するためにTwitter Webにアクセスする習慣があるのだが、ある時電話番号の入力を求められたことはあった。この時、この情報を使って現実の知り合いにこのアカウントが通知されてしまうのではないかと邪推して一度拒否していた。その後設定を確認して、Twitterではそういったことをさせないプライバシー設定があることを知ったが、Twitterごときに電話番号のようなクリティカルな情報を教えたくないというのが当時思ったことだった。「Twitterごとき」というのは当時の感覚ではおかしくない考え方だったと思う。
かつて「つぶやき」と呼んでいたものがいつの間にか「ツイート」になったように、いつの間にか電話番号も登録しておくのが当たり前になっている。現在では電話番号を登録していないユーザの通知を表示しないといった設定があることから、電話番号を登録していないユーザは、しているユーザよりも(Twitterから)信頼されないようだ。
そんな中でぽつんと一件スパム報告受けたからか、今回でしきい値を越えたからかはわからないが、電話番号が登録されていない怪しいアカウントtoshi_aを念の為一時的に凍結して入力を強制したのは妥当な判断だったと思える。もし、まだ電話番号を入力していない人が居たら、一時凍結される前にしておいたほうが良いと思われる。
一時的な凍結をされたwhywaitaとの会話ログ
この記事を書いている最中に、丁度whywaitaが一時的な凍結を受け、話を聞くことができたので一時的な凍結について理解を深めることが出来た。これはSlackチームd250g2 http://slackin-d250g2.herokuapp.com/ でのやりとりだが、このSlackチームは誰でも入れるようになっていて、generalでのやり取りは一般に公開されている。しかしログは永久に保存されるものではないので、後学のために以下に残しておく。
whywaita
対象ツイートを消せと言われたので消したところ時計が進み始めた
あと4時間ぐらい
toshi_a
今回の件はブログにまとめる予定
時計が進むとは?
whywaita
対象ツイートを削除してから12時間経ったら規制解除らしい
toshi_a
ほほお
whywaita
メールが来てから凍結までの猶予期間ではない
toshi_a
実は俺の規制、他の人とはちょっと違ったんよ
ツイートの削除を求められなかった
whywaita
まじかー
「しばくぞ」はセーフなのか
toshi_a
なのですしが俺を殺したと思われているが実は違って、原因となったツイートが存在しない
whywaita
直接Twitterガイドラインに違反するツイートがなかっただけで、ヘイト値が溜まっていたのでは?
toshi_a
普通の凍結だと即時復活は無理なのか
whywaita
永久凍結宣告者が対象ツイートを削除したら即時復帰という話もあり、対応もそれぞれ結構違いそう
今私のプロフィール見ても凍結とは外からはわからんし
toshi_a
これは恥ずかしい話で書くのもはばかられることなんだが、恐らく電話番号の登録を怠っていたことが原因なのではないかと思ってる
わいわいたは電話番号登録してるでしょ?
whywaita
アー
してたっけ
toshi_a
まあ覚えてないよね
whywaita
してたわ
toshi_a
俺、Twitter登録が昔過ぎたからな
あーそうだ
whywaita
Twitter登録というより開発者登録?
toshi_a
凍結解除のとき、電話番号に対してコード送られてきて認証とかあった?
whywaita
なかった
toshi_a
おお!やはりか
whywaita
まあまだ解除されてないけどね
きてないんじゃないかな
toshi_a
俺はそれがあって、登録してないのにどうやって証明すんねんと思ったんだが、どうやら電話番号を登録させること自体が目的だったようだ
whywaita
はーん
そっちのガイドライン違反か
多分裏ガイドラインなのだろうけど
toshi_a
すしがr4sしたのは事実で、電話番号登録がないから一応凍結されたという仮設が現実味帯びてきたな
ガイドラインではないと思うが
whywaita
あーなるほど
toshi_a
電話番号登録がない垢を信用しない文化みたいなのはあるっぽくて
whywaita
前科者に善良になってもらうために発信機つけて釈放するみたいなもんやね
toshi_a
電話番号がないアカウントからのリプライなどの通知を無効にするみたいなオプションが提供されてたりする
whywaita
へー
信用されたかったらちゃんと認証しろという話になるのか
toshi_a
一応電話番号なくても使えるが、ちゃんと使うなら身元教えろというのがスタンスなんだろうな。まあ筋は通ってる
whywaita
確かに
toshi_a
登録したのに逆に検索にも通知にも出てこなくするのはどうかと思うがこれこそタイムラグと信じたい
ありがとう、この件について確信が持てた