TUIなTwitterクライアント「nekome」を作った
どんなの?
※ アイコンや配色はデフォルトからカスタマイズしています
↑ こんなの
タブ形式でページを複数開くことができ、 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 モード
アカウント・設定ファイルの操作のほか、ツイートの投稿ができます。
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 などスタイルの指定も可能です。
開発時につまずいたこと
文字の表示幅
絵文字などの一部の文字を表示すると画面が乱れるという問題がありました。
これは曖昧文字幅と呼ばれる「表示幅が決まっていない文字」が原因でした。
この件についてはすでに 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 に変更することで解決しました。
実装自体はすべてここに残してあります。何かの参考になれば…
Twitter API v2
nekome を作る前に twnyan という CLI ベースの Twitter クライアントを開発していました。
これには Twitter API v1.1 を使用しており v2 が主要 API になった今、v1.1 がいつまでサポートされるのかが分からず、そう遠くない未来に使えなくなってしまう可能性がありました。
このような経緯があり v2 を採用したのですが、開発を進めていくうちにいくつかの問題に直面しました。
対象のツイートを自分がすでにいいね・RT しているかどうかが取得できない
v1.1 ではツイートを表すレスポンス内に favorited
や retweeted
というフィールドがあったのですが、v2 には ありません。
リファレンスや開発者コミュニティの投稿に片っ端から目を通しましたが、どこにも見当たりませんでした。
なんで??????????????
メディアをアップロードするエンドポイントがない
v1.1 ではメディアをアップロードする media/upload
というエンドポイントが用意されていたのですが、v2 にはありませんでした。
ただし、こちらは COMING SOON となっており ロードマップ にも記載されているので、いずれは移行されるものと思います。
nekome では media/upload
をたたく処理を自前で実装し対応しました。
Tweet caps
Tweet caps とは、v2 の一部エンドポイントからのツイート取得数を月単位で制限するというものです。
1 つのプロジェクトでひと月に 200 万ツイートまで取得可能です。
しかし、基本的にプロジェクトは 1 つしか作成できない ため、この制限を超えてしまうと自身の管理するすべてのアプリケーションが対象のエンドポイントにアクセスできなくなってしまいます。
nekome では、仮に制限を超えてしまってもユーザー側で対処できるよう、コンシューマーキーを自由に差し替えられるようにしています。
また、ひと月最大で 1000 万ツイートまで取得可能な Elevated+ というアクセスレベルが用意されるようですので、今後の動きを注視していきたいです。
さいごに
ターミナルで動く Twitter クライアントはすでにいろいろ存在していますが、Twitter API v2 に対応している TUI なクライアントは探した限りでは見当たりませんでした。
「ターミナルからツイートしたい!」「ターミナルに引き篭もりたい!」という方、ぜひお試しいただけたらなと思います…!
Discussion