2013年12月18日

Reblog,Tombloo,Tombfix

(この記事は tumblr reblogging enviroment Advent Calendar 2013 18日目の記事です。)

Reblog

いつもReblogしてる。
マウスでReblogしてる。
マウスボタン1が j
マウスボタン2が k
Tombfix の設定、ショートカット(マウス)が LEFT_DOWN + RIGHT_DOWN になってて、
マウスボタン3押すと、左クリック + 右クリックされる。

この設定がTumblr用マウスプロファイルで、Firefox のTumblr Profileと連動して勝手に切り替わる

マウスのプロファイルはアプリ/コマンド別にいくつか定義してて、
コード書く時は Ctrl, Shift, Alt, BS, Enterボタン、Ctrl+S の定期実行 切り替えボタン、 あと function とかテンプレートでぽちぽちやってる。
マウスカーソル、クリック、キー操作のマクロは Lua で組んでるけど、クリップも含めマクロの管理がひどいのでどうにかしたい。
最近だと JavaScript でクリップボードマクロ書けるようにした けど、実行環境どうにかならないかなって探ってる (Nodeで実行したかった…。SpiderMonkey, NILScript でいけたかも…)。
こんな環境は参考にならないだろうし、どうせハードに依存するなら、脳波Reblog してみたい。

Tombloo

Reblog 始めたと同じくらいに Tombloo を知って、 なにこれすごい、どんだけサービス対応してるの!?
そん
なに
ってTombloo のソースコード読んでたら、楽しくて。いつの間にか JavaScript のことばかり考えるようになった。 以前から ECMAScript が好きで、mozilla のソースコードとか見てたりしていたけど、 やっぱり Tombloo で衝撃受けて、普段利用するサービスがどんどん増えて、 パッチという存在を知って、なんとか書けそう、Reblog 環境, もっとこうしたい、ああしたいと、 よくわからんパッチばかり書くようになって 気付いた時には Tombloo が自分にとって一つの開発環境みたいなものになってた。
いつの間にか道を外れて迷子になることが多い。

今でも、ライブラリとかコード読んでる時、最終的に Tombloo (Tombfix) を見てることが多い。
1個1個の小さな処理、reduce とかの流れや deferred-chain, addBefore/addAround とか、 XPCOM を constructor にして自然に new して違和感なく扱ってたり, 今でも学ぶことが多いし発見がある。コードを追っていて楽しい。
あれだけ多くのサービスに対応できる機能が詰まっていて、 extractors でページ内容抽出、models でサービスにPOST。
大雑把に言えば、メンテナンスのほとんどは models と extractors だけみてれば済むようになってる。 service, ui, action、構造。アーキテクチャがきれい。 今までほとんど 1つの処理、1つの区切り、小さな粒ばかり見てきてしまっていた気がする。
本を読んでたつもりが、好きな単語やフレーズを見つけて満足し、ストーリーはもう頭の中になかった、 そんな Quote みたいな。

SoundCloudPlayer

最近、SoundCloudPlayer という Firefox アドオン を作っていて、 ある程度動くようになったものの、全体をあらためて見た時あまりにもひどい作りで 勝手に自分でガッカリした。
単に1個の Object にどんどん関数つっこんでるだけで、デザインパターンも何もない。
"とりあえず動くようにしよう" 的な作り方を, 改善したい
今は 別のブランチ で書き直し中。
自分でもたまにトラック作っているから (とってもチープなものだけど…)、 作り手からすると、SoundCloud 上で聴いてもらって再生数とかLikeが増えるのが嬉しい (もちろん DL されてローカル再生も嬉しい)。 どことなく Tumblr notes に似てる。 SoundCloudPlayer はあくまでサブのプレーヤで、メイン画面 (アクティブな SoundCloud のタブ) へのショートカット的な、そんなサポート的な位置にいられたらなって思う。

Tombfix

Tombfix で対応したいことは、Issues の解決、できそうなのあるかなって見てはいるけど、 多少構造を変えないと対応できなさそうだったり、単に pull req して close。といかなそうなのが増えてきて、 たいした考えは浮かんでない。けど、できる限り関わっていきたい。
あと、パッチの整理。できれば統合したい。
何も考えずに ちょっとこうしたいなって思ったらなんでもパッチにしてたものだから、 数が多すぎて中身も冗長で自分で書いたのがよくわかんなくて、もう手がつけらんない。
当時使ってたサービスも最近は使ってなかったりとかあって、動作チェックまでたどり着いてなかったりするのも多い。
需要がありそうなもの等で優先しながら、本体に取り入れるか、pixiv パッチのように最小限で分離するか、 tombfix/patch に入れるか、止まらない程度に進めたい


Models/Extractors

Tombfix において 各サービスを集約してる Models / Extractors について。

例えば Tumblr dsbd の UI が変わった時、ほんの少しの変化、div の className が変わっただけでも動かなくなる可能性があり、 そのたびに Extractors.Tumblr を修正して push してリリースしなければ対応できない現状。
ひどい例とすると icon, favicon の URL が変わっただけでも対応が必要で (ICON がボタンになったりしてるから)、 少なくとも応急パッチみたいので対応してきた。
この問題は以前から指摘されてる




Models と Extractors を外に出す。 syoichi さんも言っていたように、「日頃そのサービスを使ってる人が対応」できると理想的。 それには pull request して本体のリリースを待つより、簡単なステップで対応できるような何かが必要と思う。

Models と Extractors を本体のリリースに依存しないよう別の場所に置く

GitHub とかに公開されたパッチ (script) の URL は、そう変化するものじゃないとして、
  • インストールした script の URL を保持しておく
  • 起動時、script をローカルから読み込む前に、保持している URL を調べ、script が更新されてないか確認する
  • 更新されてたら script をアップデート。その際、必要に応じて確認ダイアログを表示
これによりアップデートが自動化され、利用者はサービスの UI の変化や、「また動かなくなった!」など気にしないで使用できる。 (動かなくなった時の対応が速ければ…)。
若干思っていることは、1つのレポジトリに集約しようとすると、Tombfix 自体の拡張性を縛ってしまっているんじゃないかなって。
「野良パッチ」と呼ばれるくらい、Tombloo/Tombfix のパッチはいろんなところにあって、 今更パッチのフォーマットを決めようとか、@updateURL どうのこうのとか、めんどくさいし、ルールが増えるほど書き手も減ると思う。 (自分で 一括インストールパッチ とかでフォーマット決めておいてアレだけど…)。
パッチフォルダ内の script は現状、ソート順に読み込まれる。 これを、例えばフォルダ内に .json とかで、サービス名と models / extractors の URL が書いてあるファイルがある場合、 読み込み順序や自動更新など扱いやすくなるかもしれない。
直列になってるパッチの読み込みを、Tree な構造にするとか、 script は addBefore/addAround により、CSS のようにカスケード状に扱えるようになっていて、まだ具体的にこうすればいいかもって浮かばないけど、いい方法はありそう。

とか書いてたら、Taberareloo Canary !? と、驚きと感謝を込めて

おしまい

なんだかこれといった内容のない記事になってしまいました…。
明日は poochin さんです。よろしくお願いします!

2013年12月14日

YYYY-MM-DD形式の日付フォーマット

JavaScript で日付扱う時、何かとめんどくさいのでコピペできるようなの書いてみた


2013年11月24日

SoundCloudのプレーヤーをアドオンバーから操作できるFirefoxアドオン作りました

追記: このアドオンは現在公開していません

SoundCloud でいろんなトラック聴きまわってると、いつの間にかタブが増えて、今どのタブで再生してるんだ?ってことがよくあったので作りました。

機能概要

  • プレーヤー: インストールするとアドンバーにプレーヤーボタン一式が追加される
  • 再生ボタン: SoundCloud が開かれてるとそのタブで再生/一時停止
  • タイトル: 再生中のタイトルが表示される。押すとタブがアクティブになりトラックページに行く
  • ボリューム: ミュートとボリューム調節
  • イモムシみたいなボタン: 雲っぽくしようとしたらできなかった。SoundCloud タブがアクチブになる
  • アクティブな SoundCloud タブを優先します

アクティブな SoundCloud タブのプレーヤー部分をアドオンバーにもってきた感じです

Install



先日書いた SoundCloudでQueueを使えるようにするUserScript だけでは物足りずアドオンにしてしまいました。 まだ公開されてるのは 0.0.2 で、いま 0.0.3 あたりを作っています。 バグフィックスというより1個のObjectに関数全部突っ込んでるだけの構成になってしまってるのが悲しいので直し中です。 ミュートしても一瞬音が出てしまう不具合は 0.0.3 (現レポジトリ) で直ってます。
何かあれば下のレポジトリの Issues などからお願いします

レポジトリ


2013年11月11日

よくある Back to Top ボタンの作成例


ブログとかでページ右下あたりによくある 「Back to Top」 ボタンを作ってみました。

DEMO


ページを開いてスクロールすると、右下に「Back to Top」ボタンが現れます。 ボタンをクリックしてページ上部までスクロールします。 今回は jQuery を使ってみたら、ごくありがちなボタンになってしまいました。 もしかしたら多少クリックしずらいかもしれません。

サンプルコード

※以下はDEMOを簡略化するため多少コード変更しています

HTML:
<div class="back-to-top">Back to Top</div>
JavaScript:
$(function() {
  var backToTop = $('.back-to-top');

  // クリックでページ上部までスクロール
  backToTop.hide().on('click', function() {
    $('html,body').animate({
      scrollTop: 0
    }, 800);
    return false;
  });

  // スクロール位置がページ上部じゃなかったら表示
  $(window).on('scroll', function() {
    if ($(this).scrollTop() < 10) {
      backToTop.hide();
    } else {
      backToTop.show();
    }
  });

});
CSS:
.back-to-top {
  display: none;
  position: fixed;
  bottom: 10px;
  right: 10px;
  width: 100px;
  height: 40px;
  background: rgba(255, 64, 64, 0.7);
  color: rgba(255, 255, 255, 0.9);
  line-height: 3;
  text-align: center;
  font-size: 13px;
  font-weight: bold;
  font-family: verdana, sans-serif;
  cursor: pointer;
  z-index: 9999;
}

.back-to-top:hover {
  background: rgba(255, 16, 16, 0.8);
  color: rgba(255, 255, 64, 0.8);
}
お好みで変更してください。

まとめ

真面目なページに突然こんなんがあったら嬉しくなりそう

/show/ じゃないほうの jsFiddle:

Contact


2013年10月31日

JavaScriptでsleep

Firefox のスクラッチパッド (chrome有効) で実行してみた
Components.utils.import('resource://gre/modules/ctypes.jsm');

var lib = ctypes.open('kernel32.dll');
var sleep = lib.declare('Sleep', ctypes.winapi_abi, ctypes.void_t, ctypes.uint32_t);

var a = Date.now();
sleep(5000);
var b = Date.now();

lib.close();

[a, b];

/*
a = 1383184940621
b = 1383184945621
*/
ぴったり 5秒でした

これは Windows 環境でできることだけど、Linux だったら
Components.utils.import('resource://gre/modules/ctypes.jsm');

var libc = ctypes.open('libc.so.6');
var sleep = libc.declare('sleep', ctypes.default_abi, ctypes.int, ctypes.int);

sleep(1000);
// ...
こんな感じで同じことできそう (確認してないから違うかも)

2013年10月30日

SoundCloudでQueueを使えるようにするUserScript書いたらコード解析が楽しかった


SoundCloud でいろんな人のトラックを聴いて回ってると、いい曲が見つかって、「次この曲を再生予約したいなあ」などと思うことがよくありますので user.js 書きました

機能概要

  • Queue追加ボタン: 再生ボタンの隣に (+) みたいなボタンがつく
  • Queue Playlist: ページ右上に [Queue] というボタンがついて、押すとキューのプレイリストが表示される
  • 並び替え: キューのプレイリストのトラック名をドラッグドロップで並び替え可
  • リストから削除: キューのプレイリストのトラック名をリストの外にドロップすると削除される
  • localStorage に保存してるのでページ移動しても保持できる

Install



スクリプトを書く前に、キュー機能はないのかなと調べてたら公式ブログがそのうちつけるみたいなこと言ってて、実装例は発見できず、あれこれ検索してるうちに SoundCloud のソースコードを見てて、
minify された JavaScript なのですが、通常 minify では Object のプロパティ/メソッド名まで1文字とかにしないので、時間はかかったけど解析っぽいことはできました
関数名がわかりやすい。いちいち調べなくても連想できるくらい的確な単語で、追ってるうちに Backbone.js + jQuery + Underscore.js + RequireJS ぽいことがわかってきた
例えばページ遷移は
// soundcloud.com/stream に移動
require('config').get('router').navigate('/stream', true);
で移動できる。ヘッダーの黒いバーはそのままで content だけ書き換わる
Backbone.js なんて触ったことないし、AMD も頻繁に使うわけではないので慣れてない。ソースを見てるうちに魅力的だと思った
非同期処理はほとんどが jQuery.Deferred で行われている。カンマ演算子だらけなのは minify の影響なのかな。
sound model は、polling-model, audible などの複数から継承されている、playlist model は Backbone の Collection から継承されている。 playlist に複数の sound が入り、基本的に複数の soundsCollection なるものを持っていて、トラックの再生が続く感じ。
Sound オブジェクトは、とりあえず以下のようにして生成できる
var sound = new require('models/sound')({
  id: 115078163,
  resource_id: null
});
resource_id は、オブジェクトを定義するごとにインクリメントされる counter のような値っぽいですが、その値までわからなかったので null にしたら一応作れた
id は track id のことで、トラックの URL から track id に変換する必要があります。 最初は sdk を使って id を取得してたのですが、今は API を使っています
http://api.soundcloud.com/resolve.json?url=http://soundcloud.com/matas/hobnotropic&client_id=YOUR_CLIENT_ID
ここで使う client_id は、
var client_id = require('config').get('client_id');
こんな感じで取得できます
トラックの再生や停止などの制御は sound model から play() するのか、audioManager というのを使うのか、それとも play-manager か探ってましたが 今は play-manager を使っています。どれがベストなのかわかりませんが、
require('lib/play-manager').toggleCurrent({
  userInitiated: true
});
これでヘッダーの再生ボタンを押したのと同じ効果が得られます
任意のトラックを再生するには、
var trackId = 115078163;
var ms = require('models/sound');
var pm = require('lib/play-manager');
var sound;

// 初回はこの方法じゃないとダメっぽい
sound = ms.instances.get(ms.hashFn({
  id: trackId,
  resource_type: 'sound'
}));
if (!sound) {
  sound = new ms({
    id: trackId,
    resource_id: null
  });
}
// まだ何も再生してない場合
if (pm.sourceCursor === -1) {
  pm.setInitialSource(sound);
}
pm.playSource(sound);
適切なやり方なのか不明ですが、とりあえずこれで再生できると思います。
トラックの再生は、たぶん本来 playlist に入れて soundsCollection とかで扱うっぽいですが、解析力不足です。
あと、難関だったのが、曲の再生が終わったことを取得する方法です。 最初は <title> の文字列を見て判断してたのですが、なんだか悲しくなったのでやめました。 SoundCloud は、'finish', 'audio:flash_block', 'change:source', 'change:currentSound' などいろいろな Events が定義されていて、 on('finish', ...) とかでいけそうな気がしたんですが、あれこれやって動かなくて play-manager で定義されている 'change:currentSound' を使うことにしました。 でもこれ、本来の次の曲が一瞬 かかってしまうことがあって、現状の問題点となっています。
var pm = require('lib/play-manager');
pm.on('change:currentSound', function(sounds) {
  var prevSound = sounds.prev;
  var currentSound = sounds.current;
  // ...
});
こんな感じにすると、曲が変わった時に拾ってくれます。ただ、自分で再生ボタン押して違う曲にしたときも反応するので、今回の user.js ではめんどくさい感じになってしまいました。
なんだかんだで SoundCloud のソースコードは読み応えがあって、コード解析を通して Backbone.js の勉強になった気もして minify コードを無理に読むのもいいかも
そんなこんなの事情もあって、まだ動作が不安定かもしれないです。 SoundCloud が今の UI のうちは user.js のメンテ続けたいところ。

Contact


2013年9月25日

jQuery.DeferredをMochiKit.Async.Deferredな感じに操作できるプラグイン作った

日頃、もちもちな Deferred に慣れてるせいか、jQuery.Deferred があまりに使い難いので作ってみました
JSDeferred でもよかったのですが、エラー処理が楽なので伝統を継承する感じで。
dojo や Closure Library などの Deferred は MochiKit.Async.Deferred からの派生なので、汎用性はあると思います。

機能概要

  • jQuery.Deferred 本体に機能を追加してるだけなので、本来の機能 .done() とかはそのまま使えて他のプラグインも普通に動く(と思う)
  • minify版 5KB とけっこう軽量
  • jQueryオブジェクトに async という関数オブジェクトが追加される
  • Deferred は、今までどおり var d = $.Deferred(); で生成
  • たぶん古い環境でも動く

Download

使い方

スクリプトを読み込みます
<script src="/path/to/jquery.async.js"></script>
or
<script src="/path/to/jquery.async.min.js"></script>

基本の Deferred チェイン:

var d = $.Deferred();
d.addCallback(function() {
    return 1;
}).addCallback(function(res) {
    console.log(res); // 1
});
d.callback();

succeed() を使った例:

$.async.succeed(1).addCallback(function(res) {
    return res + 1;
}).addCallback(function(res) {
    console.log(res); // 2
});

値の受け渡しと、エラー処理:

$.async(function() {
    return 1;
}).addCallback(function(res) {
    console.log(res); // 1
    throw new Error('error');
}).addCallback(function(res) {
    console.log('このメッセージは表示されない');
    return 'noop';
}).addErrback(function(err) {
    console.log(err); // error
    return 'hello';
}).addBoth(function(res) {
    console.log(res); // hello
});
$.async() は、引数の関数を非同期で実行して新しい Deferred チェインを作ります。JSDeferred でいう next() みたいな感じ。

基本的な使い方は GitHub に書いてありますので参考ください

addCallback() とか addErrback() などの 他の機能については MochiKit.Async と同じなのでリファレンスを参考ください

バグとか問題等ありましたら issues または、Twitter @polygon_planet までご報告ください

レポジトリ


2013年9月20日

TumblrでPhotosetをローカル保存する時、全画像を取得するTombfixパッチ

Firefox アドオン Tombfix (Tombloo fork) のパッチです。

機能概要

Tumblr dashboard から Photoset をリブログする時に、ローカルに保存も設定してた場合
Photoset の全画像をローカル保存するパッチです
(Photoset 1枚めのみ画像取得は本体にコミットされています)

TumblrでHighRes画像を取得するTombfixパッチ と組み合わせるといいかもしれないです

パッチ (Download or Update):

インストール/アップデート:

上のパッチリンクを
右クリック→「Tombfix」→「Tombfixパッチのインストール」でインストール/アップデート

レポジトリ


※レポジトリ名とか tombloo のままになってますが、各パッチは tombfix で動作確認済みです

デスクトップに保存するmodelを追加するTombfixパッチ

Firefox アドオン Tombfix (Tombloo fork) のパッチです。

機能概要

ローカルのデスクトップに保存する model を追加します

てっとり早くデスクトップに保存したい時は tombfix 設定で Desktop を ON にして、どんどん share すると楽です

パッチ (Download or Update):

インストール/アップデート:

上のパッチリンクを
右クリック→「Tombfix」→「Tombfixパッチのインストール」でインストール/アップデート

レポジトリ


※レポジトリ名とか tombloo のままになってますが、各パッチは tombfix で動作確認済みです

TumblrでHighRes画像を取得するTombfixパッチ

Firefox アドオン Tombfix (Tombloo fork) のパッチです。

機能概要

Tumblr で画像をリブログする時に、High-Res 画像を取得します
High-Res がなければ通常の画像を取得します
主にローカル保存時に使われます

(この機能はオプション的な立ち位置なので本体に pull req しないでパッチにしてあります)

パッチ (Download or Update):

インストール/アップデート:

上のパッチリンクを
右クリック→「Tombfix」→「Tombfixパッチのインストール」でインストール/アップデート

レポジトリ



2013年9月16日

iPhone,iPodでalertが2回表示されちゃう

iPhone、iPod touch とかで JavaScript の alert() が2回表示されちゃって
どうも非同期になってないようなので、以下のようにしたら直りました
window.alert = function() {
  var alert_ = window.alert;

  return function(msg) {
    setTimeout(function() {
      alert_(msg);
    }, 0);
  };
}();