Franz 5 のレシピに関するメモpost

Franz 5 用のレシピを作る際にいろいろソース読んだり調べたりしたのでそれのメモです。

現状 Franz 5 は、まだまだベータバージョンです。 なので仕様が変わることがあるため、ここに書かれている内容と違う動きをする場合もあります。

そもそも Franz とはなんぞや?

簡単にいうと Franz は、各種 SNS をタブでまとめて管理できるデスクトップアプリです。

Franz – a free messaging app for Slack, Facebook Messenger, WhatsApp, Telegram and more からダウンロードできますが、利用するにはアカウント登録が必要です。

特徴として

  • レシピ(=拡張)を追加することで様々な SNS などの Webサービスに対応可能
  • レシピごとに複数のアカウントを割り当て可能(=マルチアカウント対応)
  • クロスプラットフォームなデスクトップアプリ

などがあります。

まあ、仕組みとしては Webで提供されているページをタブで表示しているのでブラウザで表示できるページであれば基本はなんでも表示できます。

Recipe の作り方

レシピの作り方を超簡単、ざっくりと説明

  1. Franz でサポートしたいSNS等を決める
  2. Franz Integration Documentation をじっくりよく読む
  3. 既存のレシピのソースを眺め参考にしながら対象のSNSを表示するように実装
  4. デバッグは Franz Recipe Documentation / Overview - Installation を参考に
  5. 完成したら New Issue - meetfranz/plugins からレシピのデプロイを要望しましょう

ほら簡単!

webview.js の exports 関数の第2引数

webview.jsmodule.exports で公開する関数の引数は通常

      :
module.exports = (Franz) => {
      :

と、このようになっています。

プラグインドキュメント frontend_api.md でも同様です。

この定義に、第2引数を追加し

      :
module.exports = (Franz, data) => {
      :

と、このようにすると

data = {
    customUrl: "",
    hasCrashed: false,
    hasCustomUploadedIcon: false,
    iconUrl: "",                                // アカウントで設定しているアイコンを指定
    id: "0ea52f93-9c9a-4d07-a40e-876aacabce81", // サービスの識別子
    isActive: true,                             // 現在アクティブ(表示されている)か?
    isAttached: true,
    isBadgeEnabled: true,                       // 通知バッジが有効?
    isEnabled: true,                            // サービスが有効?
    isIndirectMessageBadgeEnabled: true,        // DM用のバッチが有効?
    isMuted: false,                             // オーディオでの通知が無効?
    isNotificationEnabled: true,                // 通知が有効?
    name: "hoge fuga",                          // サービスの名称(ユーザーが自由に設定)
    order: 5,                                   // 並び順
    recipe: {...},                              // サービスの元となる recipe
    team: "",                                   // チーム名
    timer: 29,
    unreadDirectMessageCount: 0,                // 未読なDMの個数
    unreadIndirectMessageCount: 0,              // 未読な返信の個数
    webview: {...}
};

とこのような感じで色々情報が取得できるようです。 ただ、これらはコピーされた値のようで受け取った以後は一切更新がされないようです。 ちなみに、呼び出し元は /src/webview/plugin.jsinitializeRecipe イベントのリスナののようです。

Developper Tools

Franz には、Developper Tools が index.js 用と webview.js 用の2種類あります。

それぞれの動作をまとめました。

対象メニューReload Franz 時の動作Reload Service 時の動作
index.jsViewToggle Developper Tools全てのServiceで共通Preserve log オプションが非チェックだとログがクリアされる|影響なし
webview.jsViewToggle Service Developper ToolsそれぞれのServiceごとウインドウが破棄される|Preserve log オプションが非チェックだとログがクリアされる

通知とオーディオについて

Franz自体での通知の有効と無効は

// webview.js

const { ipcRenderer } = require('electron');

ipcRenderer.on('settings-update', (sender, settings) => {
  console.log(`isAppMuted = ${settings.isAppMuted}`);
  // isAppMuted = true  = ミュート状態
  // isAppMuted = false = ミュート解除状態
});

         :

と、することで確認可能。

ただし、 Franz v5.0.0 bata 18 以降実装が変わったのか、最初に一回呼び出されて以後一切呼ばれなくなります。なんとなくバグっぽい気もします。

Backend 側で window.franz.stores.services を定期的に参照し変化があった場合に Frontend 側に通知すれば自力で同様のことができるようです。

この辺 のソースを参考に、です。

個別のサービスの「通知を無効にする」や「オーディオの無効化」の状態は、exports する関数に2つ目の引数を追加することで...

      :
module.exports = (Franz, data) => {
    console.log(`isMuted = ${data.isMuted}`);
    console.log(`isNotificationEnabled = ${data.isNotificationEnabled}`);
      :

どうもイベント通知で送られて来るために引数は Deep Copy された値が渡されるようです。 そのため初期状態は取得できますが、変化があっても反映されることはありません。

ちなみに Franz.onNotify() は、Franz自体の設定で「通知とオーディオを無効化」したり、それぞれのサービスで個別に「通知を無効にする」としても必ず呼び出されます。

デスクトップ通知

デスクトップ通知は

    Franz.onNotify(notification => {
        // ToDo ...
        return notification;
    });

で実際にデスクトップに通知する直前に情報を取得できます。

参考:frontend_api.md#onnotifyfn

そして return false; とすることで通知を握りつぶすことができます。

    Franz.onNotify(notification => {
        // destroy the notification
        return false;
    });

サービスの識別子を取得する

webview.js の exports 関数の二つ目の引数で取得できます。

      :
module.exports = (Franz, data) => {
    console.log('id = "${data.id}"'); // id = "0ea52f93-9c9a-4d07-a40e-876aacabce81"

この値が、 recipe からインスタンスとして起動した service の識別子となります。

Backend API で受け取れる各種イベント

イベントを受け取るためにどんなイベントを受け取るかあらかじめ定義します。

module.exports = Franz => class HogeHoge extends Franz {

  constructor(...args) {
    let _temp;
    return _temp = super(...args), this.events = {
        // ここで受け取るイベントを定義
        'did-navigate': 'handleDidNavigate',
    }, _temp;
  }

  handleDidNavigate (event) {
      // 諸々の処理を行う
  }
                   :
};

受け取れる主なイベントはこんな感じ

イベント名概要
before-input-event
certificate-error
console-messageconsole.* で表示する内容
context-menu
crashed
cursor-changed
destroyed
devtools-closed開発者ツールが閉じられた
devtools-focused開発者ツールがにフォーカスが当たった
devtools-opened開発者ツールが表示された
devtools-reload-page
did-attach-webview
did-change-theme-color
did-fail-load
did-finish-load
did-frame-finish-load
did-get-redirect-request
did-get-response-details
did-navigate
did-navigate-in-page
did-start-loading
did-stop-loading
dom-readyコンテンツのDOMの準備ができた
found-in-page
login
media-paused
media-started-playing
new-window
page-favicon-updated
paint
plugin-crashed
select-bluetooth-device
select-client-certificate
update-target-url
will-attach-webview
will-navigate
will-prevent-unload

概要がブランクの部分はまだ調べきれていない部分です。 まあ、イベント名でなんとなく想像はできると思います。

Backend API から Frontend API へ

Backend API (index.js) から Frontend API (webview.js) へのデータの受け渡しの方法。

送り側:

// index.js

    // this は webview のインスタンスである必要があります。
    // コンストラクタの this では WebView のインスタンスへアクセスできないようです。
    this.send('test', { foo: 'bar' });

受け取り側:

// webview.js

const { ipcRenderer } = require('electron');

ipcRenderer.on('test', (sender, data) => {
    console.log(data); // { foo: 'bar' }
});

Frontend API から Backend API へ

Frontend から Backend への通知方法はまだ見つかっていません。 おそらく出来るとは思います...

参考


   /   変更履歴  /   Permalink  /  このエントリーをはてなブックマークに追加 
 カテゴリ: ブログ  /   タグ: Franz, Javascript