プラグインの作り方
Janetterの動作の模式図
ウィンドウはメイン画面(タイムライン画面)、設定画面、プロフィール画面、通知ポップアップ画面の4種類存在し、すべてを同時に表示することも可能。また、各画面は基本的に同時表示されるのは1つだけだが、プロフィール画面についてはTwitterのユーザーごとに1つ表示されるため、複数表示することもできる。
JanetterSrv.exeへのアクセスはapi.jsにあるAPI系関数を利用する。
上記のようにサーバー上のHTMLを表示する形になっているため、Janetter内のHTMLはローカル実行
ではないことに注意。ローカル実行なら回避できるJSON呼び出しなどの各種制限がかかる。
Ajaxを使ってTwitterや他のWebサービスにアクセスする場合は、janet.json(jn.json)またはjanet.xml(jn.xml)を利用する。jQuery.ajaxではクロスサイトの制限に引っかかるので注意。なお、上記の図ではJanetterSrvを迂回しているように描いているが、実際にはjanet.jsonやjanet.xmlはJanetterSrvを経由する。
プラグインやテーマのJavaScriptからは、janet.webViewAction関数を使うことで、ローカルのアプリケーションを実行することもできる。
必要なもの
- プラグインにしたいアイデア
- HTMLの知識
- JavaScriptの知識(+jQueryの知識)
- CSSの知識
- jsファイルを作成するためのエディタなど(テキストエディタでOK)
- 画像を作成するツール(※画像が必要な場合)
- Windows環境
- 既存のプラグイン(参考用)
- 粘り強くがんばれる心
プラグインでは不可能なこと
- ローカルファイルの出力
画像の自動ダウンロードなど、とにかくローカルにファイルを保存することは、セキュリティ上の理由から基本的にできない。
BlobなどFile APIを使ったファイル出力も切られている。
ただし、テキストファイルの出力のみ、「data:application/octet-stream,」とjanet.execUrl()の組み合わせでブラウザに引き渡すことで可能と言えば可能。
この場合、ブラウザでいきなりダウンロードが実行されるように見えるほか、拡張子も含めてファイル名の指定ができないため、メッセージダイアログなどでユーザーに注意を喚起する必要がある。
- ローカルファイルの読み込み
出力と同様に、File APIを使ったファイル読み込みは、セキュリティ上の理由から一切できない。
つまり、何らかのデータをバックアップ/リストアするプラグインは作れない。
- Janetterの終了、再起動
模式図からもわかるように、Janetterはブラウザ、プラグインはJavaScriptという関係なので、ブラウザ(=Janetterの本体ウィンドウ)に干渉するようなことはできない。
- HTTPヘッダを変更した上での通信
Webサービスで必要になる場合があるHTTPヘッダの指定は、jQueryのajax()のようにはできない。
(プラグイン作者が要望を上げているため、今後対応されるかも知れない)
- TwitterのAPIで提供されていない情報の表示
例えば、API 1.1では自分がしたRTの情報は提供されていないので、自分のRTだけを表示するタイムラインが見たい場合は自分のツイートのタイムラインにフィルタをかける必要がある。
また、タイムラインに表示されているツイートがどれだけ「お気に入り」に追加されたかを知るための情報は提供されていないので、お気に入りに追加された数はAPIからは取得できない。
自分のツイートが誰にお気に入りに追加されたりRTされたりしたかといった情報も取得できない。
APIで提供されていない情報が必要な場合には、関連するWebサービスから取得する方法が考えられるが、これもそのサービスが情報をAPIなどで提供しているか否かに左右される。
APIで提供されていない情報をスクレイピングで取得するようなことをした場合、User Agentなどで撥ねられた場合に利用できなくなるので要注意。
- Chrome 18以降で実装された機能(Windows版 4.2系)
Windows 版のJanetterはバージョン4.2の現在、Chrome 17相当をベースとしているため、Chrome 18以降で実装された機能は利用できない。
不可能と言うほどではないけどプラグインにはあまり向いていないこと
- 画面の外観をがらっと変える
どちらかと言えば、外観の変更はテーマかカスタムCSSで、機能の追加はプラグインで行う方がベター。
(設定で切り替えるような挙動を期待する場合にはプラグインの方が良い)
プラグインでやらない方がいいこと
- 派手な画面エフェクト
CSSを駆使すれば、画面エフェクトをリッチに使用することもできるが、Janetter自体が全体的に動きが重くなりやすい。よって、よほど高いスペックを要求できる場合でもない限り、エフェクトを多用するプラグインを作ることはあまりお勧めできない。
プラグイン作成の準備
- Janetterをデバッグモードで起動する
※Windows版の場合
デスクトップのJanetterのショートカットをコピーして、右クリックから「プロパティ」を開き、「リンク先」の末尾に半角スペースを空けて「--debug」を追記する。
これで、このショートカットを実行するとデバッグモードから起動できるようになる。
- JanetterSrvをデバッグモードで起動する
※Windows版で、Chromeからデバッグする場合。通常のデバッグモード起動をする場合には必要ない
次の場合、トークンが必要になるので、JanetterSrv.exe を --debug オプション付きで単体で立ち上げる(これによりトークンが不要になる)- ブラウザでAPIの結果を確認する(ポップアップ)
- アプリ外でJS書いて動きを確認する
- jsファイルを作成する
これと言って決まった書き方はないが、次のようにするのがおすすめ。
既存の他のプラグインを参考にするのも良い。
(function($, jn) {
//ここにプラグインの処理を記述する
})(jQuery, janet);
- 作成したjsファイルをJanetterのプラグインフォルダに置いて確認する
Janetterのプラグインフォルダは、Windowsの場合「(Janetterのインストール先)\Theme\Common\js\plugins」となる。
jsファイルを作成しているフォルダに、このプラグインフォルダへのショートカットを置いておくと、ショートカットにjsファイルをドラッグ&ドロップするだけでファイルを移動できるので便利。
- デバッグモードで起動中のJanetterのメインウィンドウで、タイトルバーを右クリックして「Test」を選択するとJanetterが再起動する
jsファイルを置いただけでは反映されないので再起動が必要。
ただし、プロフィールウィンドウや設定ダイアログなど特定の画面でしか実行されないプラグインの場合は、メインウィンドウを再起動しなくてもその画面を開くだけで良い。
- 同じくJanetterの各ウィンドウでタイトルバーを右クリックして「Developer Tools」を選択すると、デバッグに使える開発者ツールのウィンドウが開く
この開発者ツールは、Google Chromeの「デベロッパー ツール」と同等。
デバッグ時にはjsファイルに「console.log((ログ出力したい内容));」を記述しておくと、開発者ツールのConsoleに出力されるので便利。
また、開発者ツールのElementsでは、Janetterで表示中のHTMLの各要素について、スタイルシートの適用内容やサイズなどの諸元、プロパティやイベントリスナなどが参照できる。ここで表示されるHTML要素は、JavaScriptによって生成されたものを含むため、出力処理の結果を実際の表示として確認でき、非常に便利。
なお、開発者ツールは開いたときの元ウィンドウの内容のみ表示されるので、プロフィールウィンドウや設定ダイアログなどではまた別にそれぞれ開く必要がある。
作る上での注意事項
- *.jsファイルおよび*.cssファイル(もし作る場合)は、文字コードをUTF-8にして作成すること。
これは、Janetter本体が国際化対応を考慮してUTF-8で作られているため。
- Janetterの各ウィンドウに表示される内容は、HTML+CSSになっている。
これは設定画面も例外ではない。また、このため、jQueryを利用してDOMを操作し、新しい設定項目を追加することができる。
- Developer toolsのコンソール上でJanetter関連の変数を参照する場合は、janet.~と入力する。
一方、Janetterのjsファイルや、上記のように準備したプラグインファイルではjn.~で参照可能。
これは、jQueryの宣言のfunctionに与える引数で置き換えているため。
- Janetter本体の関数の処理を変更する場合、その関数が他で使用されている可能性は十分に考慮すること。
後述するように、関数の変更時にはできるだけ元の関数も実行して処理を追加する形にすることが望ましい。
- Janetter本体のevent.jsでdocumentに指定されているイベントが多数存在する。
特定のノードに対してイベントを追加する場合(特にクリックイベント)、バブリングによりdocumentのイベントが走ってしまうことがあるので要注意。
document側のイベントの実行を止める場合は、クリックイベントの処理を実行した上で、イベントに対してstopPropagation()してバブリングを止めること。
- Janetter本体のjsファイルは、既存の処理がどのように行われているかを知る上で非常に重要な手がかりとなる。また、既存の処理をプラグイン作成時に転用することもできる。
次のファイルは特に読み込んでおくことをお勧めする(読み込んでおかないまでも、何をしているのかは大まかに把握しておいた方が良い)。- action.js:
様々な操作を行ったときに実行される基幹関数
- api.js:
Twitterとの通信など、外部とのやりとりに使用される関数群
- common.js:
オブジェクトの拡張など、基本的な処理。自分で実装しようとしたものが既にここにあるケースが多い
- contextmenu.js:
右クリックなど、各種メニュー項目関連。「オプション」ボタンやツイートのアイコンから表示されるメニューもすべてこのjsファイル内で記述されている。メニュー項目を追加/変更する場合や、新規にメニューを追加する場合はここの処理を参照のこと
- event.js:
各種イベントの指定。非常に範囲が広い。なお、一部タイムライン上のキーイベントはtimeline.jsに移動したのでそちらも参照のこと
- function.js:
janet.webViewActionで引き渡されたコマンドの処理など。assignToが地味に使える
- janet.js:
変数janetのプロパティ等。どのようなデータを持っているかはここで確認できる
- message.js:
DM周りの処理。timeline.jsのtimelineControllerをベースにしているので、そちらも参照のこと
- msgdialog.js:
メッセージダイアログを表示するための関数群。ここにあるものを参考に、カスタムしたダイアログを作成することもできる
- timeline.js:
タイムライン周りの表示
- tweet.js:
ツイート周りの処理。ツイートの表示に関するプラグインを作る場合はここを操作する必要あり
- tweetedit.js:
ツイート入力欄の処理
- ui.js:
画面のUI制御周り。タイムラインの追加や移動といった操作を行う場合は特に重要
- profile.js:
プロフィール画面の処理。なお、処理のすべてをこのファイルでまかなっているわけではなく、上記のファイル他のjsファイルも利用している。
内部では「janet.profileDialog」が「pf」になっているので要注意。また、この「janet.profileDialog」自体にも活用しやすい変数を持っているので要チェック
- config.js:
設定画面の処理。設定画面を変更するプラグインを作る場合には、これを良く読むこと
内部では「janet.configDialog」が「cf」になっているので要注意
- notice.js:
通知ポップアップ画面の処理。通知ポップアップ画面を変更するプラグインを作る場合には、これを良く読むこと
内部では「janet.notice」が「nt」になっているので要注意
- api.jsにはTwitterから情報を取得するためのURLが取得できる関数が多数含まれており、janet.websocket.send(jn.websocket.send)と組み合わせて使うことで情報取得を手軽に行うことができる。
一方で、ここで取得したURLはJanetterの内部的な処理を行うサーバー(JanetterSrv)と通信するもので、TwitterのREST APIを直接叩く場合とは挙動が異なることがある。
例えば、TwitterのREST APIを直接叩く場合には省略できるパラメータが、サーバーから情報を取得する場合には省略できない場合がある。
例:「block/create」は、REST APIではuser_idかscreen_nameのどちらかがあれば良いが、サーバーの場合はuser_idが必須になっている。
- Janetterが利用しているライブラリは「Theme\Common\js\base」にある。
プラグイン作成においては、時としてこのライブラリの内容を把握する必要もある。
次のファイルには目を通しておくことをお勧めする。- jarty.js:
PHP用のテンプレートエンジンSmartyのJavaScript移植版。テーマを利用した表示に使用されている
- jquery.jeegoocontext.js:
コンテキストメニューの制御周り。コンテキストメニューを追加したり項目を追加したりする分には特に読まなくても既存の記述を参考にすることで実装できるが、$.fn.jeegoocontextvisibledや$.fn.jeegoocontexthideといった有用な関数があることは憶えておいても損はない。
- jquery.ui.autocomplete.js:
@ユーザーやハッシュタグのオートコンプリートで使用されている。これも、オートコンプリートを実装する上では特に目を通す必要はないが、既存のオートコンプリートの動きを変えるには目を通しておいた方が良い。
- jquery.ui.dialog.js:
メッセージダイアログの制御周り。Janetter本体のmsgdialog.jsは、このライブラリをベースにtypicalなダイアログパターンをあらかじめ用意している。自前でオリジナルのダイアログを作る場合には、こちらも目を通しておいた方が良い。
- Twitterからデータを取得する都合上、Ajaxで非同期に情報を取得している関係で、HTMLの基本的なDOMの構築が終わった段階でも必要なデータを取得することができない場合がある。
この場合、(確実に)データ取得後に実行される関数を乗っ取って処理を行うと良い。
例えば、プロフィール画面で言えばjn.profileDialog.dispLoading(false)やjn.profileDialog.miniLoading(false)が利用しやすい。
- Janetterで設定を取得(janet.getConfig)または保存(janet.setConfig)する場合、内部のサーバー(JanetterSrv)と通信を行う。
この通信にはわずかながらタイムラグが発生する程度の時間がかかるため、特に設定保存の前後で設定情報(janet.confの各プロパティ)を取得しようとする場合には、注意が必要。
タイムラグにより、保存前のデータを取得してしまう場合がある。
これを避けるには、タイマーで設定変更を検知するか、内部のサーバーから情報を取得するタイプの関数(その状況下で無害なもの)を実行してdoneで必要な処理を実行するようにする。
- 設定情報janet.conf(jn.conf)のデータは、ウィンドウごとに保持している。
例えばメイン画面に加えてプロフィール画面を開いた場合、開いた時点でのjanet.confのデータは基本的に同一だが、プロフィール画面を開いた後にメイン画面でjanet.confの一部を変更した場合は食い違いが起こる。
また、この状態でメイン画面でjanet.setConfig(jn.setConfig)した場合は、サーバーに保存されている設定情報とプロフィール画面で保持している設定情報が食い違うことになる。
さらに、プロフィール画面でjanet.setConfigすると、古い設定情報が書き込まれてしまうことにもなる。
この問題を回避するには、設定の変更時にjanet.webViewActionで変更後の設定をウィンドウ間でやりとりするのが良い。
なお、設定画面を開いたときにプロフィール画面は閉じられてしまうが、設定画面を開いた状態でプロフィール画面を開くことはできてしまうため、設定情報のやりとりはメイン画面だけでなくプロフィール画面も考慮することが望ましい。
- (Janetterのインストールフォルダ)\bin\localeには文字列のデータが含まれている。
iniファイルはウィンドウ本体、jsonファイルはHTMLやJavaScriptで使用するデータになる。
jsonに含まれている文字列はjanet.conf.lang(jn.conf.lang)に合わせてjanet.msg[(項目名)]に格納される。
プラグインで文字列を使用する場合、既にあるものを利用するのも一つの手。
多言語対応については後述。
- Janetterは複数のアカウントの使い分けができるクライアントなので、アカウント(juidやアカウントID)を指定する処理がある場合、複数のアカウントを使っている場合と、単独のアカウントを使っている場合の両方について考慮する必要がある。
特にUIの設計時やテストの際には注意すること。
テスト用に複数のアカウントを用意しておくことが望ましい。
- アカウントIDまたはプロフィールを対象に何か操作をするプラグインの場合に考慮すべきこと
- 自分のアカウントIDかどうか
- フォローしている人のアカウントIDかどうか(または、フォロー申請中のアカウントIDかどうか)
- フォローされている人のアカウントIDかどうか
- 相互フォローしている人のアカウントIDかどうか
- ブロックしている人のアカウントIDかどうか
- ブロックされている人のアカウントIDかどうか(明確に判別する手段はないが、ブロックしていなくて、相手のツイートが見られないなど)
- 非公開アカウントIDかどうか(また、フォローしている人かどうか)
- ツイートやその発言者を対象に何か操作をするプラグインの場合に考慮すべきこと
- 自分のツイートかどうか
- フォローしている人のツイートかどうか
- 相互フォローしている人のツイートかどうか
- リプライかどうか(メンションと明確に区別)
- 誰かのリツイートかどうか
- 非公開アカウントかどうか
- DMかどうか。またそれが自分の発言か、相手の発言か
- Webサービスから情報を取得する場合、janet.json(jn.json)またはjanet.xml(jn.xml)が利用できるが、janet.xmlを利用するとHTMLを取得してスクレイピング的な処理もできる。
- メッセージダイアログを表示する場合、Janetter本体のmsgdialog.jsにある関数が利用できるほか、msgdialog.jsの関数を参考にカスタムしたダイアログを作成することができる。
ただし、このダイアログでは既に設定されているイベントによって操作が阻害される場合がある。特に、メッセージダイアログにtextareaを追加する場合には注意が必要。
- メッセージダイアログを表示して、そのdoneの中でメッセージダイアログを表示させたい場合、後から表示させるメッセージダイアログは引数にforcedの指定が必要になる。
janet.confirm(jn.confirm)など、forcedの指定がないものは、janet.messageDialog(jn.messageDialog)を使用すること。
- janet.webViewAction(jn.webViewAction)を経由して別のウィンドウに引数を渡す場合、引数には配列やオブジェクトは利用できない。
また、数値(Number型)やブール値(Boolean型)の値は、受け取り側では文字列(String型)になっている。
引数で受け取った値は想定される型に補正して使用すること。
- JanetterがTwitterからツイートデータを受け取った段階で、文字コードで「\u000B\u000B」というよくわからない文字データがツイート文字列に紛れ込んでいることがある。
ツイートを利用するプラグインを作る場合、ツイート文字列を取得したらautolink.jsに含まれているdeleteNazomoji()というString型用のメソッドでこの文字列を削除しておいた方が良い。
- jQueryを使っていると、setIntervalやsetTimeoutでの関数指定が通常のJavaScriptと同じように利用できない場合がある(起きたり起きなかったりする)。
これを回避するには、無名関数でwrapすると良い(この場合、クォーテーションで囲む必要はない)。
例:hogehoge()→function(){hogehoge()}
- 既存のプラグインには、プラグイン開発のノウハウが詰まっている。
幸いなことにプラグインはJavaScriptファイルでソースが公開されているようなものなので、処理の内容を全て実際に見て確かめることができる。
参考にしたい処理がある場合や、うまくいかない場合には、先人たちがどのように解決しているのかを参照するのもひとつの手。
オブジェクト
Janetterはオブジェクト指向で作られています。
プラグインで利用しそうな、代表的なオブジェクトを下に示します。
オブジェクト |
説明 |
メイン画面 |
プロフィール画面 |
設定画面 |
通知ポップアップ |
janet(jn) |
Janetterの基本オブジェクト |
○ |
○ |
○ |
○ |
janet.accounts |
ユーザーのアカウント情報 |
○ |
○ |
○ |
○ |
janet.action |
ユーザーの操作時に実行される関数 |
○ |
○ |
※1 |
○ |
janet.conf |
設定情報 |
○ |
○ |
○ |
○ |
janet.configDialog |
設定画面関連の関数群 |
- |
- |
○ |
- |
janet.controllers |
タイムラインのコントローラ |
○ |
- |
- |
○ |
janet.contextMenu |
コンテキストメニューを生成する関数群 |
○ |
○ |
○ |
○ |
janet.drafts |
ツイートの下書き |
○ |
- |
- |
- |
janet.editor |
ツイートの入力欄。 選択中のツイート用アカウントの他、写真やツイートしようとしている内容(リプライの対象など)、DM対象のユーザーのリストなどが格納されている。 |
○ |
- |
- |
- |
janet.hashedWords |
自身が直近のツイートで使用したハッシュタグ(配列)。 オートコンプリートで利用するハッシュタグは、これにjn.getTimelineHasnList()したものを加えて、uniqueContcatして生成する。 |
○ |
- |
- |
- |
janet.lists |
リスト。 リストに含まれるメンバーのIDなどは含まれない。 |
○ |
○ |
- |
○ |
jn.mentionedUsers |
自身が直近のツイートで言及したアカウントID(配列)。 オートコンプリートで利用するアカウントIDは、これにjn.getTimelineUserList()したものを加えて、uniqueContcatして生成する。 |
○ |
- |
- |
- |
janet.msg |
文字列リソース。 jn.conf.langで指定した内容の文字列が翻訳済みの状態で格納されている。 |
○ |
○ |
○ |
○ |
janet.ngWords |
ミュート設定 |
○ |
- |
○ |
- |
janet.notice |
通知ポップアップ関連の関数群 |
- |
- |
- |
○ |
janet.profileDialog |
プロフィール画面関連の関数群(+固有の変数など) |
- |
○ |
- |
- |
janet.themes |
テーマ |
○ |
- |
○ |
- |
○:利用可能
-:利用できない、または存在するが有用な情報が格納されていない
※1:janet.configDialog.actionを使う
イベント
Janetterの各処理のタイミングで、下記のようなイベントが発生する。
イベントに合わせて処理を記述することで、適切なタイミングで処理が行われるようにできる。
http://jbbs.livedoor.jp/bbs/read.cgi/internet/8173/1291895275/661
説明 |
コード |
備考 |
初期化処理の終了時 |
janet.onInitializeDone(); |
|
設定を取得した時 |
janet.onGetConfig(success, data); |
|
設定を変更した時 |
janet.onSetConfig(success, data); |
|
翻訳情報を取得した時 |
janet.onGetMessages(success, data); |
|
アカウント情報を取得した時 |
janet.onGetAccounts(success, data); |
|
タイムライン一覧を取得した時(起動直後) |
janet.onLoadTimelines(success, data); |
4.0.0.0bで実質廃止 |
タイムライン一覧を取得した時 |
janet.onGetTimelines(success, data); |
4.0.0.0bで実質廃止 |
リスト一覧を取得した時 |
janet.onGetLists(success, data); |
|
ツイート欄を開こうとした時(return false; でキャンセル) |
janet.onTweetBoxShowBegin(expanded); |
|
ツイート欄を開いた時 |
janet.onTweetBoxShowEnd(expanded); |
|
ツイート欄を閉じた時 |
janet.onCompactTweetBox(); |
|
ツイート送信後 |
janet.onTweeted(successSendTweet); |
|
DM送信後 |
janet.onMessaged(successSendMessage); |
|
RT送信後 |
janet.onRetweeted(successSendRetweet); |
|
テーマ用? |
janet.onLoadEnd(); |
|
新着ツイート(画面表示前) |
janet.onReceiveNewStatusesBefore(ツイートオブジェクトの配列); |
4.0.0.0b2で新規に追加。trueと判定される値を返却するとその後の処理を行わない |
新着ツイート(画面表示完了後) |
janet.onReceiveNewStatusesAfter(); |
4.0.0.0b2で新規に追加 |
新着ツイート通知(画面表示前) |
janet.onReceiveNewTweetsBefore(ツイートオブジェクトの配列); |
4.0.0.0b2で新規に追加。trueと判定される値を返却するとその後の処理を行わない |
新着ツイート通知(画面表示完了後) |
janet.onReceiveNewTweetsAfter(); |
4.0.0.0b2で新規に追加 |
イベント通知(画面表示前) |
janet.onReceiveNewEventBefore(イベント内容オブジェクト); |
4.0.0.0b2で新規に追加。trueと判定される値を返却するとその後の処理を行わない |
イベント通知(画面表示後) |
janet.onReceiveNewEventAfter(); |
4.0.0.0b2で新規に追加 |
http://jbbs.livedoor.jp/bbs/read.cgi/internet/8173/1341802282/38
~Beforeの関数では、trueと判定される値を返却するとその後の処理を行わない(つまりツイートや通知を表示しない)ようにできます
※janet.onInitialize()はプラグインの読み込み前に実行されるため、プラグインからは利用できない。
記述例
プラグイン間で競合したら困るからこんな風に書いてくれると助かります
(function($, jn){
var origin_onGetConfig = jn.onGetConfig;
jn.onGetConfig = function(success, data){
origin_onGetConfig && origin_onGetConfig(success, data);
// ↓こっから処理書いたり
//
//
};
})(jQuery, janet);
プラグイン内でMainWindowなのかProfileなのか調べたいときは、次のように書くといいですよ。
http://jbbs.livedoor.jp/bbs/read.cgi/internet/8173/1291895275/666
switch(_Janetter_Window_Type){
case 'main':
// ↓メイン画面の処理書いたり
//
//
break;
case 'profile':
// ↓プロファイル画面の処理書いたり
//
//
break;
case 'config':
// ↓設定画面の処理書いたり
//
//
break;
case 'notice':
// ↓ポップアップ画面の処理書いたり
//
//
break;
default:
break;
}
コードサンプル
//イベントタイミング確認用 UTF-8にて保存すること
(function ($, jn) {
//初期化処理が終了時
//janet.onInitializeDone();
var origin_onInitializeDone = jn.onInitializeDone;
jn.onInitializeDone = function () {
origin_onInitializeDone && origin_onInitializeDone();
console.log('初期化処理が終了時 janet.onInitializeDone();');
};
//設定を取得した時
//janet.onGetConfig(success, data);
var origin_onGetConfig = jn.onGetConfig;
jn.onGetConfig = function (success, data) {
origin_onGetConfig && origin_onGetConfig(success, data);
console.log('設定を取得した時 janet.onGetConfig(success, data);');
};
//設定を変更した時
//janet.onSetConfig(success, data);
var origin_onSetConfig = jn.onSetConfig;
jn.onSetConfig = function (success, data) {
origin_onSetConfig && origin_onSetConfig(success, data);
console.log('設定を変更した時 janet.onSetConfig(success, data);');
};
//翻訳情報を取得した時
//janet.onGetMessages(success, data);
var origin_onGetMessages = jn.onGetMessages;
jn.onGetMessages = function (success, data) {
origin_onGetMessages && origin_onGetMessages(success, data);
console.log('翻訳情報を取得した時 janet.onGetMessages(success, data);');
};
//アカウント情報を取得した時
//janet.onGetAccounts(success, data);
var origin_onGetAccounts = jn.onGetAccounts;
jn.onGetAccounts = function (success, data) {
origin_onGetAccounts && origin_onGetAccounts(success, data);
console.log('アカウント情報を取得した時 janet.onGetAccounts(success, data);');
};
//タイムライン一覧を取得した時(起動直後)
//janet.onLoadTimelines(success, data);
var origin_onLoadTimelines = jn.onLoadTimelines;
jn.onLoadTimelines = function (success, data) {
origin_onLoadTimelines && origin_onLoadTimelines(success, data);
console.log('タイムライン一覧を取得した時(起動直後) janet.onLoadTimelines(success, data);');
};
//タイムライン一覧を取得した時
//janet.onGetTimelines(success, data);
var origin_onGetTimelines = jn.onGetTimelines;
jn.onGetTimelines = function (success, data) {
origin_onGetTimelines && origin_onGetTimelines(success, data);
console.log('タイムライン一覧を取得した時 janet.onGetTimelines(success, data);');
};
//リスト一覧を取得した時
//janet.onGetLists(success, data);
var origin_onGetLists = jn.onGetLists;
jn.onGetLists = function (success, data) {
origin_onGetLists && origin_onGetLists(success, data);
console.log('リスト一覧を取得した時 janet.onGetLists(success, data);');
};
//ツイート欄を開こうとした時(return false; でキャンセル)
//janet.onTweetBoxShowBegin(expanded);
var origin_onTweetBoxShowBegin = jn.onTweetBoxShowBegin;
jn.onTweetBoxShowBegin = function (expanded) {
origin_onTweetBoxShowBegin && origin_onTweetBoxShowBegin(expanded);
console.log('ツイート欄を開こうとした時 janet.onTweetBoxShowBegin(expanded);');
return true; //コレがないと開かない
};
//ツイート欄を開いた時
//janet.onTweetBoxShowEnd(expanded);
var origin_onTweetBoxShowEnd = jn.onTweetBoxShowEnd;
jn.onTweetBoxShowEnd = function (expanded) {
origin_onTweetBoxShowEnd && origin_onTweetBoxShowEnd(expanded);
console.log('ツイート欄を開いた時 janet.onTweetBoxShowEnd(expanded);');
};
//ツイート欄を閉じた時
//janet.onCompactTweetBox();
var origin_onCompactTweetBox = jn.onCompactTweetBox;
jn.onCompactTweetBox = function () {
origin_onCompactTweetBox && origin_onCompactTweetBox();
console.log('ツイート欄を閉じた時 janet.onCompactTweetBox();');
};
//ツイート送信後
//janet.onTweeted(successSendTweet);
var origin_onTweeted = jn.onTweeted;
jn.onTweeted = function (successSendTweet) {
origin_onTweeted && origin_onTweeted(successSendTweet);
jn.notice(successSendTweet); //通知バーの処理は自前でする必要があるが、かぶったらどうなるんだろうね?
console.log('ツイート送信後 janet.onTweeted(successSendTweet);');
};
//DM送信後
//janet.onMessaged(successSendMessage);
var origin_onMessaged = jn.onMessaged;
jn.onMessaged = function (successSendMessage) {
origin_onMessaged && origin_onMessaged(successSendMessage);
jn.notice(successSendMessage); //通知バーの処理は自前でする必要がある、かぶったらどうなるんだろうね?
console.log('DM送信後 janet.onMessaged(successSendMessage);');
};
//RT送信後
//janet.onRetweeted(successSendRetweet);
var origin_onRetweeted = jn.onRetweeted;
jn.onRetweeted = function (successSendRetweet) {
origin_onRetweeted && origin_onRetweeted(successSendRetweet);
console.log('RT送信後 janet.onRetweeted(successSendRetweet);');
};
})(jQuery, janet);
既存の関数の処理を乗っ取る場合
イベントと同様に、他のプラグインでも利用している場合があるため、次のように書くことを推奨
例1:jn.action(switch で処理を切り替えているものの場合)
var action_original = jn.action;
jn.action = function(options){
var act = options.act,
elem = options.element,
event = options.event;
switch(act){
// 追加する処理
case 'additionalAct':
break;
// 元の処理へ遷移
default:
action_original.apply(this,arguments);
break;
}
};
※switchで分岐した中で既存の処理を乗っ取る場合、完全に置き換える必要がない場合にはaction_original.apply(this,arguments);で元の処理に戻るようにした方が良い。同じ処理を利用する他のプラグインとの競合を避けることができる。
例2:jn.configDialog.buildAdvanced(switch がないものの場合)
var buildAdvanced_original = jn.configDialog.buildAdvanced;
jn.configDialog.buildAdvanced = function(){
// オリジナルより前に実行しておきたい処理をここに記述
// オリジナルを実行
buildAdvanced_original && buildAdvanced_original.apply(this,arguments);
// オリジナルより後に実行したい処理をここに記述
};
通知ポップアップで通知される項目
通知項目 |
備考 |
新着ツイート |
ホームタイムラインの新着分 |
リプライ/メンション |
@による言及 |
ダイレクトメッセージ |
|
リツイート |
された時のみ。リツイート解除は通知されない(※) |
フォロー |
フォローのみ。フォロー解除は通知されない(※) |
お気に入り |
追加のみ。お気に入りの解除は通知されない(※) |
リストがフォローされた |
自分のリストがフォローされた場合 |
リストに追加された |
自分が誰かのリストに追加された場合 |
※APIで情報が取れない
コンテキストメニューの追加
設定メニューやアイコンメニューなど、メニュー項目の追加を行う場合には、イベントと同様にエントリポイントが用意されているので、そのポイントで追加の操作を行う。
説明 |
コード |
設定メニュー |
janet.onContextMemuOptionsBuildStarted(accounts); |
顔メニュー |
janet.onContextMemuAllBuildStarted(accounts); |
Gearメニュー |
janet.onContextMemuGearBuildStarted(accounts); |
返信メニュー |
janet.onContextMemuReplyBuildStarted(accounts); |
☆メニュー |
janet.onContextMemuFavBuildStarted(accounts); |
RTメニュー |
janet.onContextMemuRTBuildStarted(accounts); |
@userメニュー |
janet.onContextMemuAtUserBuildStarted(accounts); |
アカウント選択メニュー |
janet.onContextMemuSelectAccountBuildStarted(accounts); |
タイムラインメニュー |
janet.onContextMemuTimeLineBuildStarted(accounts); |
リンクメニュー |
janet.onContextMemuLinkBuildStarted(accounts); |
文字列選択メニュー |
janet.onContextMemuSelectTextBuildStarted(accounts); |
#hashメニュー |
janet.onContextMemuHashBuildStarted(accounts); |
viaメニュー |
janet.onContextMemuViaBuildStarted(accounts); |
Draftメニュー |
janet.onContextMemuDraftBuildStarted(accounts); |
トレンドメニュー |
janet.onContextMemuTrendsBuildStarted(); |
通常のイベントと同様に、競合が発生しうるため、既存の処理を実行させた上で新しい処理を追加する形で記述すること。
コンテキストメニューの追加を janet.onInitializeDone() などイベントのタイミングで行うと実行タイミングの都合で表示されないケースが起こりうるため、イベントに合わせる必要がない限り、イベント外で追加を行うこと。
なお、アカウントのサブメニューを追加する場合は、各イベントに引き渡される accounts を janet.contextMenu.createAccountSubMenu に引き渡すか、これを参考にして新しい処理を作成する。
コンテキストメニュー関連の次の処理は 3.4.3.0 で次のように分離されているので、処理の変更を行う場合には必要に応じて乗っ取ること。
- メニュー生成:build~
- 表示時の処理:onShow~(※)
- 選択時の処理:onSelect~
(※チェックマークや disable 処理など、状態に応じて処理が変わる箇所はここで操作する)
コンテキストメニューに関する注意事項
- コンテキストメニューに使用する<ul>のidは、「contextmenu-xxxx」の形式で付ける必要がある。
このxxxxを他の要素のactionと同じにすると、そのactionが実行される要素の右クリックでコンテキストメニューを表示することができる。
言い換えると、コンテキストメニューのidの「xxxx」はactionに使用する項目と重複しないように注意する必要がある。
- オプションメニュー(contextmenu-options)のonShowやonSelectを変更する場合、必ずトレンドメニューのonShowやonSelectも同一の内容で変更する必要がある。
具体的には、onShowを変更する場合はonShowOptionsとonShowTrends、onSelectを変更する場合はonSelectOptionsとonSelectTrendsの両方を用意して、両方に同じ内容を記述する。
これは、トレンドが更新されたときにオプションメニューのリビルドがかかるが、この更新時に、メニュー表示に使用しているjeegoocontextにはトレンドの方のonShowやonSelectが引き渡されるため。
特に、onShowを指定しているのに突然オプションメニューの表示が反映されなくなる現象はここが原因なので注意しよう。
ウィンドウタイプ
Janetterには複数のウィンドウがあり、プラグインではウィンドウのタイプを指定して処理をすることができます。
- ウィンドウタイプごとに処理を変えたい場合、各ウィンドウタイプの識別には、変数「_Janetter_Window_Type」が利用できる(定数ではないことに注意)。
変数に格納されている値は以下のとおり。
値 |
説明 |
main |
メイン画面 |
profile |
プロフィール画面 |
config |
設定画面 |
notice |
通知ポップアップ画面 |
- 他のウィンドウへのコマンド送信や、特殊なダイアログの呼び出しなどにはjanet.webViewAction(jn.webViewAction)が利用できる。
これを使って呼べるコマンドは、ウィンドウタイプによって実装状況が異なり、ウィンドウタイプ次第では使えないものもある。
第一引数にウィンドウの識別子、第二引数以降に実行するコマンド(関数)の指定や引き渡す値を指定する。
ウィンドウの識別子は以下のとおり(※)。
識別子 |
説明 |
備考 |
mainJS |
メイン画面 |
|
profJS |
プロフィール画面 |
その時点で開いているすべてのプロフィール画面に対して処理が行われる。 メイン画面からのみ有効の模様。 |
confJS |
設定画面 |
|
noticeJS |
通知ポップアップ画面 |
|
- 通知ポップアップ画面関連の開発を行う場合、JanetterからはDeveloper Toolsが利用できない。
このため、Chromeを利用して開発する必要がある。
詳細はカスタムテーマを参照のこと。
Twitter REST API
REST APIの叩き方(推奨)
http://jbbs.livedoor.jp/bbs/read.cgi/internet/8173/1340690924/138
jn.websocket.send({
action: 'direct',
data: {
param: 'value', // APIが要求するパラメータ類
method: 'GET', // APIが要求するメソッドに合わせる
path: '/any/api/path' // TwitterのAPIのURLパス(「/」で始まる)
},
done: function(success, data, code){ // APIからの応答を受け取った後の処理。success:成否のBoolean、data:成功時に取得するデータ、code:httpの結果コード
}
});
methodは指定しないとPOSTになります
GETでアクセスする必要があるところでは失敗してしまうので注意してください
(pathの指定などパラメータに不備がないのにdoneのcodeで404が返る場合、たいていこのミスをしている)
janet.get/janet.postを利用したREST APIの叩き方(非推奨)
http://jbbs.livedoor.jp/bbs/read.cgi/internet/8173/1291895275/913-914
jn.get({ // APIが要求するメソッドに合わせる
url: jn.getBaseUrl() + '/janetter/2/twitter/direct/any/api/path', //← /janetter/2/twitter/direct/<uri> ※https://dev.twitter.com/docs/api に並んでるuri
data: {
juid: _juid, // 必ずjuidはユーザーのアカウントからjuidを取得して付ける
param: 'value' // APIが要求するパラメータ類
},
done: function(success, data, code){ // APIからの応答を受け取った後の処理。success:成否のBoolean、data:成功時に取得するデータ、code:httpの結果コード
}
});
※Twitter REST APIでは文字をURLエンコードする必要があるとなっているが、エンコード自体はJanetterSrvが行うため、日本語の文字列などはそのままパラメータに付与して良い。
※urlにformatの識別子を付ける必要はない。
例:users/show
×users/show.json
○users/show
Mac版対応
Windows版と異なり、Mac版にはデバッグモードで起動する方法がなく、またDeveloper Toolsに相当するものもない。
このため、基本的にはHTMLなどの基本的なデバッグをWindows版で行い、Macでの動作についてはある程度手探りになってしまう。
Mac版4.0.1.0での動作には、Windows版と異なる点として次のような特性がある。
- プラグインの読み込み完了前に、janet.onInitializeDoneが動作してしまう
onInitializeDoneで実行するつもりの初期化系処理は、この特性のためにWindows版と同じように実行することができない。
この問題に対応するためには、次のように記述する必要がある
onInitializeDoneが完了している場合、janet.temp.initialized(jn.temp.initialized)がtrueになっているので、これを判定に使用する。
fooInit = function(){
// ここに初期化時に実行される処理を書く
};
if (jn.temp.initialized){ // Mac版で通る処理
fooInit();
} else { // Windows版で通る処理
var initdone_foo_orig = jn.onInitializeDone;
jn.onInitializeDone = function(){
initdone_foo_orig && initdone_foo_orig.apply(this,arguments);
fooInit();
}
}
- メニューの生成処理、ツイートの生成処理などの上書きが、初期で実行されない
これは、プラグインの読み込みよりも先にJanetterの初期化処理の一部が動作し始めてしまうために発生する。
この問題に対応するためには、上記のfooInit()実行の後に、タイマーを回して処理を実行する必要がある。
次の例では、設定メニューのビルドを初期化後に実行している。
fooInit = function(){
// ここに初期化時に実行される処理を書く
};
// 設定メニューのみをビルドする場合の例
fooBuildMenu = function(){
var accounts = jn.accounts,
accountssubmenu = jn.contextMenu.createAccountSubMenu(accounts);
jn.contextMenu.buildOptionsMenu(accounts ,accountssubmenu);
jn.transMessage($('#contextmenu-options'));
};
if (jn.temp.initialized){ // Mac版で通る処理
fooInit();
var fooMenuBuildInterval = setInterval(function(){
if($('#contextmenu-options').length > 0 && $('#contextmenu-options li').length > 0){
clearInterval(fooMenuBuildInterval);
fooBuildMenu();
}
},1000);
} else { // Windows版で通る処理
var initdone_foo_orig = jn.onInitializeDone;
jn.onInitializeDone = function(){
initdone_foo_orig && initdone_foo_orig.apply(this,arguments);
fooInit();
}
}
なお、ツイートの生成処理については、生成時に行われる処理を、例えば次のような形で行う関数を用意する必要がある。
barTweetInit = function(){
$('div.tweet-container').each(function(){
// ここに、ツイートごとに行う処理を書く
// ツイートのデータを使う場合は、this.itemでデータが取得できる
});
};
プラグインの多言語対応
プラグインで表示する文字列を多言語対応させる場合、次の2点の対応が必要になる。
- 文字列をjanet.msg(jn.msg)に追加する
プラグイン側で文字列を各言語別(\Theme\Common\config\js\languagecodes.jsのコード別)に用意しておき、janet.conf.lang(jn.conf.lang)に合わせてjn.msgに登録する
登録時には連想配列として用意しておいてassignToを使うと楽
これはjanet.onInitializeDoneのタイミングで良い
- janet.onGetMessages(jn.onGetMessages)の実行時にも文字列をjanet.msg(jn.msg)に追加する
言語設定が変更されたときに、janet.getMessages(jn.getMessages)が実行される。
この中で実行されるjanet.onGetMessages(jn.onGetMessages)のタイミングで文字列を追加すれば良い。
実際の処理については、多言語対応している@ginlime氏のプラグインの記述を参照のこと。
多言語化に伴うTips
- janet.transMessage(jn.transMessage)を実行するとき、親エレメントを引数に指定することで、実行範囲を限定することができる。
コンテキストメニューの再ビルドや一部の項目の置き換えが発生する場合には、これを利用して範囲を限定したtransMessageを実行すると良い。
逆に、範囲を限定しない場合、HTML全体を対象としてしまうため、特にTLが多い場合にはすごく時間がかかることになるので要注意。
「プラグイン情報」への対応
公式ではないが、「プラグイン情報」プラグインへの対応を勧める。
プラグイン情報画面で情報表示ができるほか、JSON形式のテキストファイルをネット上の適当な場所(Dropboxのパブリックフォルダで良い)に置いておくだけで更新情報の配信ができるという、開発者にとってはありがたいメリットもある。
また、副次的な効果として、特定のプラグインの有無を判定条件にした処理を作成することもできるようになる(例えば、「プラグイン情報」プラグインがあるかどうかを条件にする場合、if(Boolean(jn.pluginInfo['pluginInfo']))で判定できる)。
書式や注意事項については「プラグイン情報」プラグインに同梱のテキストファイルに詳しく記載されており、また、「プラグイン情報」プラグイン自体にもコメントで詳しく解説されている。更新情報の配信についても同様。
更新情報の構造や内容については、各プラグインに記載されている「updateinfo」の URL を叩くと実際のJSON形式で確認できる。
参考になるページ
http://webapi.biz/twitter
TwitterのREST APIを始め、様々なweb APIのリファレンスが掲載されている。
ユーザー登録してTwitter認証するとページ上からAPIを叩くことも可能。
http://semooh.jp/jquery/
Janetter自体がjQueryを使っているため、プラグインを効率良く書くにはjQueryの知識が必須。
このリファレンスでかなり学習できる。
http://www.rexv.org/
オンラインで正規表現を確認できる。
文字列を抜き出して処理する場合の動作確認に最適。
http://json.parser.online.fr/
JSONのパーサ。プラグインの更新情報のJSON書式チェックに。
http://pnggauntlet.com/
PNGファイルの軽量化ツール。プラグインで使用する画像が24bitアルファ付きPNGの場合、このツールを利用して8bitアルファ付きPNGにすることで軽量化できる。