CLIでGoogleハングアウトを使うことのメモ

リバースエンジニアリングでハングアウトフィーチャーをサポートした hangups

hangouts_with_hangups.jpg

私だって LINE も使うけど、連絡には昔っから Google ハングアウト( Google Hangouts )をよく使っている。それは Google トークのころからで、 LINE が日本を席巻する以前よりのつきあいである。

ハングアウトは複数端末で使えるし、スマホクライアントのインターフェースは Google アカウントの切り替えが容易だから、完全なマルチアカウント対応と言え色々と都合がいい。それと、Gmailインターフェースに統合される形でトークの内容が自動保存されていて、この内容を検索できるものだからありがたい。そしてなにより、私の周りではハングアウトの利用者が多いのだ。

そういうことだからスマホだけでなく端末エミュレータでも使えればもっと便利ということで、 TUI (Text User Interface) の XMPP クライアントを使っていた。しかしやや不安定というか、そもそもオンライン時のやり取りしか参照できないのはちょっとつらい。

ところが最近 hangups という冗談のような名前のハングアウトクライアントの話をきいて使ってみたところ、これが便利だったのでちょっとメモしておく。

hangups とは

ご多分にもれず、私もインスタントメッセンジャー(IM)をユビキタスなリアルタイムコミュニケーションツールとして常用している。そして、スマホだけでなく常用コンソールの端末エミュレータからこれが使えると、都合が良いことが多い。そこで以前は XMPP 準拠のいわゆる Jabber クライアント( MCabber とか emacs-jabber とか)を利用していた。前述のように私は Google のハングアウトをよく使うけど、 XMPP でもハングアウトに接続できないということはないのだ。

しかしそれらによるメッセージ履歴はオンライン時のログ範囲なら表示されるものの、他のクライアントからの発信やオフライン時のやりとりは取得できない。 GUI のマルチプロトコルクライアント Pidgin でも同様で、 Chrome や Android なんかの Google オリジナルクライアントのように、会話履歴の同期はなされないのである。

ようするに仕組みの違いによるわけだが、殊にGoogleハングアウトがプロプライエタリな仕様であることによる。オープンソースの XMPP を除いて、普通の IM はプロトコルも含め仕様を公開しないのが通例である。

そこらへんをリバース・エンジニアリングで補い実装したのが hangups で、ターミナルでハングアウトに接続する TUI クライアントおよびライブラリのセットとなっている。

既にこのライブラリを利用したプロジェクトがいくつも立っており、ボットの類を中心に賑わいを見せている。README によれば、hangups はまだ開発の初期にあるのでAPIの変更もありうるとしている。

ここでは主に hangups の TUI クライアントについてとりあげる。

インストール

pip でインストールする。 hangups は Python 3.5 以上対応なので、 Python2/3 併存環境では pip3 を使う。

$ sudo pip3 install hangups
$ sudo pip install hangups

また、 Arch Linux では AUR に登録があるのでこちらを利用するのもいい。

$ yaourt -S hangups

起動

hangups コマンドで起動する。

最初の起動時に Google アカウントでログインする。

メールアドレスとパスワード、また必要であればベリファイコードも入力するとログインできる。すると会話履歴リストとともにインターフェースが立ち上がる。

$ hangups
Sign in with your Google account:
Email: hogefuga@gmail.com
Password:

画面の最上段にタブが表示され、最初は Conversations という会話リストのタブのみだが、そこから会話を選ぶと新しくタブでその会話内容を表示してくれる。

トークンはデフォルトで ~/.cache/hangups/refresh_token.txt に保存されている。このファイルを削除すれば再びログイン要求のところから始められる。また、 --token-path オプションで別のトークンを指定し、他のアカウントでログインできる。複数のhangupsを別々のトークンで同時起動できるから、マルチアカウントでの利用が可能である。

$ hangups --token-path ~/.cache/hangups/myaccount1
$ hangups --token-path ~/.cache/hangups/myaccount2

なお hangups でのログイン状況は、 Google の 最近使用した端末 では iOS と表現される。

操作

会話リスト

会話のリストタブ( “Conversations” )では j k で上下、 ENTER で選択するとその会話のタブが開く。

j / k 1行下/上
C-f 次のページ
C-b 前のページ
C-d 次のタブ
C-u 前のタブ
C-w タブを閉じる
C-e 終了

会話タブ

会話のタブを開くとプロンプトで文字入力が可能となる。また、操作キーは以下のようになる。

C-d 次のタブ
C-u 前のタブ
C-w タブを閉じる
C-n メニュー
<up> <down> 履歴をスクロール
<pagedown> <pageup> 履歴をページ単位スクロール
C-f C-b 履歴をページ単位スクロール
C-e 終了

履歴のスクロールは <up> を一度押すことで開始する。また最終行にて <down> を押すことで履歴スクロールを終了する。

プロンプトでの操作は readlike に依存しているとのことで Emacs のような感じを想像したのだが、ちょっと事情が違っていてそのままでは違う操作にあてられているキーがあり、うっかり余計な動作をさせてしまいそうだ。

例えば C-e は上述の通り終了にあてられており、行末移動のつもりでうっかり押すと不意に終了してしまう。 また C-fC-b は vim ライクなページめくりにあてられているからカーソル左右移動ができないことと、 C-d は次のタブなので文字削除に使えないということもある。かろうじて C-aC-h が行頭移動とバックスペースとして使えるものの、これでは癖で Emacs キーバインドをやったらちょっとつらいことがある。

基本的には端末エミュレータのキーバインディングが使えるはずなのだが、上から別の機能があてられることで封じ込められてしまったんじゃないだろうか?というような妄想に従い、起動オプションでこれら C-f / C-b / C-e / C-d にあてられている機能を無効または他のキーへ再設定してみた。そうすると何れもカーソル移動、行末移動、文字削除に使えた。

$ hangups --key-page-down '' --key-page-up '' --key-quit 'ctrl x' --key-next-tab 'ctrl y'

なお終了は C-c でもできるのだが、飽くまで割り込みによる強制終了なのでよくない(エラーメッセージも表示される)と思い、空いている C-x にあてた。

設定

hangups は起動オプションも使えるが、設定ファイルに書くこともできる。デフォルトの設定ファイルは ~/.config/hangups/hangups.conf

設定内容は、設定ファイル等のロケーション、キーバインディング、日付フォーマット、カラースキームなど。

一例として、さきほどのキーを設定し、そしてテーマを Solarized Dark にして、日付と時刻の表記もちょっといじってみた。

# ~/.config/hangups/hagnups.conf

[General]
date-format: < %F %a >
time-format: (%T)

[Keybindings]
key-quit: ctrl x
key-page-down: ''
key-page-up: ''
key-next-tab: ctrl y

[Colors]
col-scheme: solarized-dark
col-inactive-tab-bg: black
col-active-tab-fg: black

設定書式は ConfigArgParse 準拠で、上では yaml 風に : 区切りで書いたが、 = や区切りなし、あるいは起動オプションと同じ -- 始まりの記述でもいけるはず(詳しくは ConfigArgParse · PyPI など参照のこと)。

以下オプションについて記す。なおこれらのオプションについてはヘルプ( --help )でみられる。

起動オプションのみに使える引数は以下のとおり。

  オプション デフォルト値
ヘルプ -h ( --help )
バージョン -v ( --version )
デバッグ詳細ログ -d ( --debug )
設定ファイル -c ( --config ) ~/.config/hangups/hangups.conf

これ以下は設定ファイルにも記せるオプション。

全般設定

ログ、トークンの保存先は次のとおり。

  オプション デフォルト値
ログ --log ~/.cache/hangups/log/hangups.log
トークン --token-path ~/.cache/hangups/refresh_token.txt

日時のフォーマットは以下のとおり。日付はタイムライン中で日付変更のところで出力され、時刻は各発言の左端に出力されるもの。フォーマットは date コマンドの書式指定と同じなので man date を参照のこと。

  オプション デフォルト値
日付フォーマット --date-format < %y-%m-%d >
時刻フォーマット --time-format (%I:%M:%S %p)

キーバインディング

キーの設定は以下の9項目。

  オプション デフォルト値 他のキー
次のタブ --key-next-tab ctrl d  
前のタブ --key-prev-tab ctrl u  
タブを閉じる --key-close-tab ctrl w  
メニュー --key-menu ctrl n  
1行上へ移動 --key-up k up
1行下へ移動 --key-down j down
1頁上へ移動 --key-page-up ctrl b page up
1頁下へ移動 --key-page-down ctrl f page down
終了 --key-quit ctrl e  

hangups の TUI は Urwid ライブラリで実現されており、キー設定やカラー設定は Urwid 仕様に準ずる。キー設定については User Input — Urwid を参照のこと。

ざっくりかくと、 Ctrl+xctrl xCtrl+Shift+tshift ctrl xAlt+xmeta x というように表記。 <space><enter><F5><up><pageup>' 'enterf5uppage up となる。

なお、端末エミュレータやシェルラッパーのキーが優先されるから、選択の幅は狭いかもしれない。

カラー

カラースキームは default (単色)と solaraized-dark 。また、表示要素ごとの色指定も可能であり、パレットも 16/88/256 色をサポートしている。

  オプション デフォルト値 とり得る値
カラースキーム --col-scheme default default solarized-dark
使用パレット数 --col-palette-colors 16 16 88 256

表示要素は以下の通り。デフォルト値は何れも None

オプション
タブ --col-active-tab-fg --col-active-tab-bg --col-inactive-tab-fg --col-inactive-tab-bg
タブ行 --col-tab-background-fg --col-tab-background-bg
日時 --col-msg-date-fg --col-msg-date-bg
名前 --col-msg-sender-fg --col-msg-sender-bg
名前 (自分) --col-msg-self-fg --col-msg-self-bg
--col-msg-text-fg --col-msg-text-bg
文 (自分) --col-msg-text-self-fg --col-msg-text-self-bg
ステータス --col-status-line-fg --col-status-line-bg

デフォルトの16色モードの色指定については Constants — Urwid 2.0.1 に記述がある。以下に可能な色指定をちょっと抜き出してみた。

black dark gray dark red light red dark green light green brown yellow dark blue light blue dark magenta light magenta dark cyan light cyan light gray white

また88/256色モードについては Display Attributes — Urwid 2.0.1 が参考になる。

通知

いわゆるデスクトップ通知に関する設定。 Dunst の場合通知タイプは default で大丈夫のようだった。

  オプション デフォルト値 とり得る値
通知タイプ --notification-type default default / apple / bell / dbus / none
簡易通知 --discreet-notifications false true / false