LoginSignup
0
0

More than 5 years have passed since last update.

Tunnelblick を JXA で操作する

Last updated at Posted at 2018-08-20

SSL-VPN クライアント Tunnelblick は AppleScript で操作可能です。

私は AppleScript がよく分からないため、JXA の出番です。とはいえ、スクリプトエディタのライブラリ(ヘルプ)は読みづらい。AppleScript のヘルプを JavaScript 用に自動変換しているからとはいえ、悩ましいですね。とはいえ JXA も通常の JavaScript とは違う独特さがあり、JXA でも試行錯誤がつきものです。

JXA でできる Tunnelblick の操作方法をメモします。

試した環境:

  • *Tunnelblick: OS X 10.13.6; Tunnelblick 3.7.6a (build 5080); prior version 3.7.6 (build 5060)

アプリケーションオブジェクト

JXA お約束の Application(アプリ名) でアプリケーションオブジェクトを取得します。

let tb = Application("Tunnelblick");

インスタンス名はなんでもいいですが、この記事では tb にします。

設定確認

tb.configurations です。

tb.configuraitons() を伴ってメソッド呼び出しをすると配列を返します。

let configurations = tb.configurations();
console.log(configurations.length); // 設定の数

osascript -i -l JavaScript インタラクティブ実行で試してみるとこんな感じ。

>> conf = tb.configurations()
=> [Application("Tunnelblick").configurations.byName("xxx"), Application("Tunnelblick").configurations.byName("yyy"), Application("Tunnelblick").configurations.byName("zzz")]
>> conf.length
=> 3

xxx といった設定名は仮のもの。この設定名は、Tunnelblick に入れた設定の設定名そのものです。

しかし、この配列の各要素を取り出して、設定オブジェクトのメソッドを呼び出したりと行ったことはできませんでした。 上記出力にあるような byName() も使えず。 tb.configurations() 呼び出しは、設定の数を tb.configurations().length として把握するために使用する以外の活用法が無いような…。

そのかわり、 tb.configurations[i] といった [i] で添字を伴った配列のような呼び出しをすることで、各オブジェクトが取れます。実際に tb.configurations()[0].name() はエラーになりますが、 tb.configurations[0].name() は設定名を返すことで確認ができると思います。なお tb.configurations[i] のように配列っぽくアクセスできますが、 tb.configurations.length といったプロパティへのアクセスはできません。つまり純粋な配列 (Array) ではないということです。

console.log("設定一覧");
let tb = Application("Tunnelblick");
let confs = tb.configurations;
for (let i = 0; i < confs().length; i++) { // confs.length だとエラー
    let c = confs[i]; // Configuration オブジェクト
    let name = c.name();
    let state = c.state();
    console.log(name + " : " + state);
}

tb.configurations の返り値を入れた confs 変数の扱いが独特ですね。JXA、こういうところが普通の JavaScript っぽくなくて、悩むことが多いです。

マニュアルを読むと、上記のようにして取り出した Configuration オブジェクトには以下のゲッターメソッドがあるようです。なお、全てリードオンリー (r/o) です。

メソッド名 概要
name 設定名を返却する。
state 接続状態を返却する。
autoconnect 自動接続をするかを返却する。
bytesin クライアントの受信バイト数を返却する。
bytesout クライアントの送信バイト数を返却する。

state の値について、ドキュメントに載っているものは EXITING と CONNECTED のみですが、接続途中にはいくつかの表示があるようです。見つけたら追記します。

意味
EXITING 切断状態
CONNECTED 接続状態
WAIT 待機中
AUTH 認証中
GET_CONFIG 設定取得中
CONNECTING 接続中
RECONNECTING 再接続中
SLEEP .

流れは EXITING → CONNECTING → AUTH → GET_CONFIG → CONNECTED だと思いますが、確実な情報ではありません。

autoconnect の値については以下。

意味
LAUNCH Tunnelblick 起動時に接続する
START OS起動時に接続する
NO 自動接続は行わない

接続と切断

接続と設定はアプリケーションオブジェクトのメソッドから行います。なお、設定オブジェクトには接続と切断のメソッドはありません。

以下のように、特定のメソッドの第一引数に設定名の文字列を与えるようにして使います。

let tb = Application("Tunnelblick");
tb.disconnect("xxx"); // xxx は設定名
tb.connect("yyy"); // yyy は設定名

接続と切断に関するメソッドは以下。

メソッド名 概要
connect(CONFIG_NAME) 指定した設定名で接続を開始
disconnect(CONFIG_NAME) 指定した設定名の接続を終了
connectAll() 全ての接続を開始
disconnectAll() すべての接続を終了
disconnectAllExceptWhenComputerStarts() OS起動時に接続する設定以外の全ての接続を終了

マニュアルに載っているメソッドは上記が全てです。設定自体を作成したりといったコマンドはありませんが、通常 Tunnelblick を使用する上で欲しい機能は接続と切断の管理がほとんどだと思うので、だいたいの方が満足するかなと思います。

アプリケーションサンプル

JXA 全般の話題ですが、 #!/usr/bin/osascript -l JavaScript というシェバン行を書いておくと、当該スクリプトに実行環境を付与することで、パスの通ったところに置いてコマンドとして使用することができます。

引数を取る場合には run 関数が必要となります。Bash シェルスクリプトとして作成して、引数管理を行った後でシェルスクリプト内で動的生成した JXA スクリプトを実行する方法(私が場当たりスクリプトで好んでやる手法です)もありますが、以下では引数を取る場合には run 関数による手法を採用しています。

設定と接続状態を一覧

設定を取得することができるので、一覧できるコマンドを書いてみます。

tbconflist.js
#!/usr/bin/osascript -l JavaScript
'use strict';

let tb    = Application("Tunnelblick");
let confs = tb.configurations;
for (let i = 0, max_i = confs().length ; i < max_i ; i++) {
    let conf = confs[i];
    let name = conf.name();
    let state = conf.state();
    let autoconnect = conf.autoconnect();
    let [b_in, b_out] = [conf.bytesin(), conf.bytesout()];
    console.log(`${name}: ${state} (autoconnect: ${autoconnect}) [in: ${b_in} out: ${b_out}]`);
}

接続と切断の状態をトグルする

第1引数に設定名を取り、それが接続状態のときは切断、切断状態のときは接続をするスクリプトです。

tbtoggle.js
#!/usr/bin/osascript -l JavaScript
'use strict';

const DEBUG = false;
// 別に console オブジェクトにぶら下げなくても良いけれど
console.debug = function(string) {
    if ( DEBUG ) console.log("DEBUG: " + string);
};

function run(argv) {
    let config_name = argv[0];
    console.debug(`name is ${config_name}`);
    if ( !config_name ) {
        throw "give config name as 1st argument";
    }
    let tb = Application("Tunnelblick");
    let config = get_config_array(tb).find( (c) => { return c.name() === config_name; } );
    if ( !config ) {
        throw "config not found";
    }
    toggle_tb_connect_state(tb, config);
}

function get_config_array(tb) {
    let confs = tb.configurations;
    let config_array = [];
    for(let i = 0; i < confs().length; i++ ) {
        config_array.push(confs[i]);
    }
    return config_array;
}

function toggle_tb_connect_state(tb, config) {
    let config_state = config.state();
    let config_name  = config.name();
    console.debug(`state is ${config_state}`);
    if ( config_state === "EXITING" ) {
        console.debug(`connect(${config_name}) because config_state is ${config_state}`);
        tb.connect(config_name);
    } else if ( config_state === "CONNECTED" ) {
        console.debug(`disconnect(${config_name}) because config_state is ${config_state}`);
        tb.disconnect(config_name);
    } else {
        console.log(`config ${config_name} state is ${config_state}. exit.`);
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0