🐈

TUIなTwitterクライアント「nekome」を作った

2022/08/13に公開

どんなの?

スクリーンショット
※ アイコンや配色はデフォルトからカスタマイズしています

↑ こんなの

タブ形式でページを複数開くことができ、 Vim っぽいキーバインドでさくさく使える TUI の Twitter クライアントです。

何ができるの?

以下のことができます。

  • ホーム・メンション・リストタイムラインの閲覧
  • アンケートの投票状況・ピン留めツイートの表示
  • ツイートの投稿・削除
  • ツイートに対してのいいね・RT・QT・リプライ
  • ユーザーのフォロー・ブロック・ミュート
  • アカウントの切り替え(マルチアカウント)

これに加えて最近、ストリームモード という Tweet Deck のようにツイートが流れる機能を実装しました!

ストリームモード

かなりアプローチは違いますが、今は亡き UserStream の雰囲気を味わうことができます。..

インストール

macOS で Homebrew 導入済みなら

brew tap arrow2nd/tap
brew install nekome

Windows で Scoop 導入済みなら

scoop bucket add arrow2nd https://github.com/arrow2nd/scoop-bucket.git
scoop install arrow2nd/nekome

Linux または上記以外の環境なら Releases からビルド済みバイナリをダウンロードできます。

使い方

初期設定

初回起動時は認証ページにアクセスして PIN コードを取得し、nekome に入力する必要があります。

ここで取得したアクセストークン等は ~/.config/nekome/.cred.toml 内に保存されます。

TUI モード

基本的にキーバインドとコマンドを使って操作します。

キーバインドのヘルプ

たとえば、キーバインドのヘルプページは ? を入力するか、:docs keybindings で開くことができます。

入力補完

また、コマンドは入力補完が効きます。
ちょっと便利です 👍

CLI モード

コマンドのヘルプ

アカウント・設定ファイルの操作のほか、ツイートの投稿ができます。

CLIモードでのツイート
omekasyで英字を変換してツイートする例

tweet コマンドは標準入力にも対応しているので「ツールの出力をそのままツイートする」みたいなことも可能です。

カスタマイズしたい

設定の編集

nekome edit

で任意の設定ファイルを開くことができます。

設定に関しての詳細は 設定ファイルについて をご覧ください。

環境設定

~/.config/nekome/preferences.toml を編集することで設定をカスタマイズできます。

設定できる項目は以下の通りです。

preferences.toml
[feature]
  # メインで使用するユーザ
  main_user = "user_name"
  # 1度に読み込むツイート数
  load_tweets_limit = 25
  # 1ページで蓄積する最大ツイート数
  accmulate_tweets_limit = 250
  # ツイート編集に外部エディタを使用するか
  use_external_editor = false
  # 実行環境のロケールが CJK かどうか
  is_locale_cjk = true
  # TUI 起動時に実行するコマンド
  startup_cmds = ["home", "mention --unfocus"]

# 確認モーダルを表示するか
[comfirm]
  block = true
  delete = true
  follow = true
  like = true
  mute = true
  quit = true
  retweet = true
  tweet = true
  unblock = true
  unfollow = true
  unlike = true
  unmute = true
  unretweet = true

[appearance]
  # 読み込むスタイル定義ファイル
  style_file = "style_default.toml"
  # 日付のフォーマット
  date_fmt = "2006/01/02"
  # 時刻のフォーマット
  time_fmt = "15:04:05"
  # ユーザ / BIO の最大行数
  user_bio_max_row = 3
  # ユーザ / プロフィール表示域の左右パディング
  user_profile_padding_x = 4
  # ユーザ / 詳細情報のセパレータ
  user_detail_separator = " | "
  # ツイート / ツイート間のセパレータを非表示
  hide_tweet_separator = false
  # ツイート / 引用ツイートのセパレータを非表示
  hide_quote_tweet_separator = false
  # グラフ / 表示に使用する文字
  graph_char = "█"
  # グラフ / 最大表示幅
  graph_max_width = 30
  # タブ / セパレータ
  tab_separator = "|"
  # タブ / 最大表示幅
  tab_max_width = 20

[layout]
  # ツイート表示
  tweet = "{annotation}\n{user_info}\n{text}\n{poll}\n{detail}"
  # アノテーション
  tweet_anotation = "{text} {author_name} {author_username}"
  # ツイート詳細
  tweet_detail = "{created_at} | via {via}\n{metrics}"
  # 投票表示
  tweet_poll = "{graph}\n{detail}"
  # 投票グラフ
  tweet_poll_graph = "{label}\n{graph} {per} {votes}"
  # 投票詳細
  tweet_poll_detail = "{status} | {all_votes} votes | ends on {end_date}"
  # ユーザプロフィール表示
  user = "{user_info}\n{bio}\n{user_detail}"
  # ユーザ詳細
  user_info = "{name} {username} {badge}"

[text]
  # いいねの単位
  like = "Like"
  # リツイートの単位
  retweet = "RT"
  # 読み込み中表示
  loading = "Loading..."
  # ツイート無し表示
  no_tweets = "No tweets ฅ^-ω-^ฅ"
  # タブ表示
  tab_home = "Home"
  tab_mention = "Mention"
  tab_list = "List: {name}"
  tab_user = "User: @{name}"
  tab_search = "Search: {query}"
  tab_likes = "Likes: @{name}"
  tab_docs = "Docs: {name}"

[icon]
  # 位置情報
  geo = "📍"
  # リンク
  link = "🔗"
  # ピン留めツイート
  pinned = "📌"
  # 認証済みバッジ
  verified = "✅"
  # 非公開バッジ
  private = "🔒"

[keybinding]
  # アプリ全体
  [keybinding.global]
    quit = ["ctrl+q"]
  # メインビュー
  [keybinding.view]
    close_page = ["ctrl+w"]
    focus_cmdline = [":"]
    redraw = ["ctrl+l"]
    select_next_tab = ["l", "Right"]
    select_prev_tab = ["h", "Left"]
    show_help = ["?"]
  # 全ページ共通
  [keybinding.page]
    reload_page = ["."]
  # ホームタイムラインページ
  [keybinding.home_timeline]
    stream_mode_start = ["s"]
    stream_mode_stop = ["S"]
  # ツイートビュー
  [keybinding.tweet]
    copy_url = ["c"]
    cursor_bottom = ["G", "End"]
    cursor_down = ["j", "Down"]
    cursor_top = ["g", "Home"]
    cursor_up = ["k", "Up"]
    open_browser = ["o"]
    open_user_likes = ["I"]
    open_user_page = ["i"]
    quote = ["q"]
    reply = ["r"]
    scroll_down = ["ctrl+k", "PageDown"]
    scroll_up = ["ctrl+j", "PageUp"]
    tweet = ["n"]
    tweet_delete = ["D"]
    tweet_like = ["f"]
    tweet_retweet = ["t"]
    tweet_unlike = ["F"]
    tweet_unretweet = ["T"]
    user_block = ["x"]
    user_follow = ["w"]
    user_mute = ["u"]
    user_unblock = ["X"]
    user_unfollow = ["W"]
    user_unmute = ["U"]

配色設定

~/.config/nekome/style_default.toml にデフォルトの配色設定があります。

これを編集するか、preferences.toml 内の appearance.style_file に新しいファイル名を指定すれば自動でファイルが作成されます。

文字色の指定には tview の color tag がそのまま使用できるので、W3C の色名や 16 進数カラーコードが使えるほか、bold, italic などスタイルの指定も可能です。

開発時につまずいたこと

文字の表示幅

絵文字などの一部の文字を表示すると画面が乱れるという問題がありました。

これは曖昧文字幅と呼ばれる「表示幅が決まっていない文字」が原因でした。

https://github.com/rivo/tview/issues/693
https://github.com/mattn/go-runewidth/issues/59

この件についてはすでに issue が立って議論されていたので、様子をみたいと思います。
nekome では暫定的な対応として、Ctrl + l で画面を再描画できるようにしました。

OAuth 2.0 with PKCE

ブックマークや Spaces のエンドポイントを叩くためには OAuth 2.0 Authorization Code Flow with PKCE という認証方法を使う必要があります。

当初、nekome ではブックマーク周りの操作にも対応する予定だったので、この認証方法を採用しました。
しかし、ある程度実装し終わった段階で 複数端末でのログインできない ということに気がつきました。

コミュニティの投稿 にもあるように、「A でログインした後に B でログインすると、A のトークンが失効する」という挙動になってしまいます。

かなり悩んだのですが、複数端末でログインできないのは許容しがたいと判断したので、ブックマーク周りの機能は見送り、認証方法を OAuth 1.0a に変更することで解決しました。

https://github.com/arrow2nd/nekome/tree/oauth2.0

実装自体はすべてここに残してあります。何かの参考になれば…

Twitter API v2

nekome を作る前に twnyan という CLI ベースの Twitter クライアントを開発していました。

これには Twitter API v1.1 を使用しており v2 が主要 API になった今、v1.1 がいつまでサポートされるのかが分からず、そう遠くない未来に使えなくなってしまう可能性がありました。

このような経緯があり v2 を採用したのですが、開発を進めていくうちにいくつかの問題に直面しました。

対象のツイートを自分がすでにいいね・RT しているかどうかが取得できない

v1.1 ではツイートを表すレスポンス内に favoritedretweeted というフィールドがあったのですが、v2 には ありません。

https://developer.twitter.com/en/docs/twitter-api/data-dictionary/object-model/tweet

リファレンスや開発者コミュニティの投稿に片っ端から目を通しましたが、どこにも見当たりませんでした。

なんで??????????????

メディアをアップロードするエンドポイントがない

v1.1 ではメディアをアップロードする media/upload というエンドポイントが用意されていたのですが、v2 にはありませんでした。

https://developer.twitter.com/en/docs/twitter-api/migrate/twitter-api-endpoint-map

ただし、こちらは COMING SOON となっており ロードマップ にも記載されているので、いずれは移行されるものと思います。

nekome では media/upload をたたく処理を自前で実装し対応しました。

https://github.com/arrow2nd/nekome/blob/4ec129356832e740d30e9ae81c7fd0a211740b54/api/media.go

Tweet caps

Tweet caps とは、v2 の一部エンドポイントからのツイート取得数を月単位で制限するというものです。

https://developer.twitter.com/en/docs/twitter-api/tweet-caps

1 つのプロジェクトでひと月に 200 万ツイートまで取得可能です。

しかし、基本的にプロジェクトは 1 つしか作成できない ため、この制限を超えてしまうと自身の管理するすべてのアプリケーションが対象のエンドポイントにアクセスできなくなってしまいます。

nekome では、仮に制限を超えてしまってもユーザー側で対処できるよう、コンシューマーキーを自由に差し替えられるようにしています。

また、ひと月最大で 1000 万ツイートまで取得可能な Elevated+ というアクセスレベルが用意されるようですので、今後の動きを注視していきたいです。

さいごに

ターミナルで動く Twitter クライアントはすでにいろいろ存在していますが、Twitter API v2 に対応している TUI なクライアントは探した限りでは見当たりませんでした。

「ターミナルからツイートしたい!」「ターミナルに引き篭もりたい!」という方、ぜひお試しいただけたらなと思います…!

https://github.com/arrow2nd/nekome

あったらいいな〜な機能も募集しています!

GitHubで編集を提案

Discussion