Home

sukechan.net

はてブ Web Hook を使ってブックマーク時に Twitter に投稿する

はてなブックマークから Web Hook 機能というのがリリースされました。
これははてブにブックマークしたタイミングで、設定した URL に HTTP POST を投げてくれます。

というわけで、これを使ってはてブにブックマークしたら Twitter にその内容を投稿する PHP スクリプトを書いてみました。

<?php
require_once 'HTTP/Client.php';
header('Content-type: text/plain; charset=utf-8');

// はてなブックマーク Web Hook Key
define('HB_WEBHOOK_KEY', 'your_key');

// bit.ly アカウント
define('BITLY_USERNAME', 'your_username');
define('BITLY_API_KEY', 'your_api_key');

// Twitter アカウント
define('TW_USERNAME', 'your_username');
define('TW_PASSWORD', 'your_password');

// 正しいパラメータが設定されている場合
if(urldecode($_POST['key']) == HB_WEBHOOK_KEY) {
  if(isset($_POST['title'], $_POST['url'], $_POST['status'], $_POST['comment'])) {

    // ブックマークを追加した場合のみ
    if($_POST['status'] == 'add') {

      // パラメータをデコードする
      $title = mb_convert_encoding(urldecode($_POST['title']), 'UTF-8', 'auto');
      $url = urldecode($_POST['url']);
      $comment = preg_replace('/\[.+\]/', '', mb_convert_encoding(urldecode($_POST['comment']), 'UTF-8', 'auto'));

      // URL を bit.ly で短縮する
      $client =& new HTTP_Client();
      $http_status = $client->get('http://api.bit.ly/shorten?version=2.0.1&login='.BITLY_USERNAME.'&apiKey='.BITLY_API_KEY.'&longUrl='.$url);
      if($http_status == 200) {
        $response = $client->currentResponse();
        $result = mb_convert_encoding($response['body'], 'UTF-8', 'auto');
        $json = json_decode($result, true);
        if($json['statusCode'] == 'OK') {
          $url = $json['results'][$url]['shortUrl'];
        }
      }
      $status = "[B!] $title $url $comment";

      // Twitter に投稿する
      $client =& new HTTP_Client(null, array('Authorization' => 'Basic '.base64_encode(TW_USERNAME.':'.TW_PASSWORD)));
      $http_status = $client->post('http://twitter.com/statuses/update.xml', array('status' => $status));
      if($http_status == 200) {
        print('success.');
      } else {
        print('error. status='.$http_status);
      }
    }
  } else {
    header('HTTP', true, 400);
  }
} else {
  header('HTTP', true, 401);
}
?>

改変などはご自由にどうぞ。
せっかくなので Twitter の OAuth に対応させてみようかと思いましたが、途中でめんどくさくなりましたw

(追記@20090607)
140 文字以内に収まるように、bit.ly で短縮 URL にしてから投稿するようにしました。

Coda + Subversion でソース管理

先日、iMac 24 インチを購入しました。
これをきっかけとして、制作環境の見直しをして、Subversion でソースコードを管理することにしました。
エディタは愛用している Coda です。Coda はバージョン 1.5 から Subversion との連携機能がついているのでこれでソース管理していきます。
以下、導入時のメモです。

環境

iMac (Early 2009) / Mac OS X 10.5.6
今回は、リポジトリを ~/repos、作業コピーを ~/work に置きます。
1 つのリポジトリ内に複数のプロジェクトを入れていく予定です。

インストール

Leopard には標準で Subversion がインストールされています。
ターミナルから「which svn」と実行し、/usr/bin/svn と返ってくれば大丈夫です。
バージョンは「svn –version」と実行すれば出てきます。自分の環境は 1.4.4 でした。

リポジトリの作成

Coda 自体にはリポジトリの作成機能は無いようなので、ターミナルまたは Coda のターミナルモードでリポジトリを作成します。

cd
mkdir repos
svnadmin create repos

リポジトリの設定

~/repos/conf フォルダ内の 3 ファイルを編集します。
まず、svnserve.conf。下記のコメントを外します。

anon-access = read
auth-access = write
password-db = passwd
authz-db = authz

次に authz。自分を読み書き可能、他を読み込み専用にします。以下を追加。

[/]
yusuke = rw
* = r

最後に passwd。アカウント名 = パスワードの形式で設定します。

yusuke = p@ssw0rd

Web 共有

他の端末からもリポジトリにアクセスできるようにするため、Web 共有機能をオンにします。
システム環境設定 → 共有 → Web 共有をオン。

svnserve の自動起動

毎回デーモンを起動するのは面倒なので、Mac にログオン時に自動的に起動するようにします。
設定方法は下記を参考にさせていただきました。

OSX 10.5でsvnserveを自動的に動かす方法 - 速報ダム日和

インポート

既存のソースをリポジトリにインポートします。
まず、デスクトップに「tmpdir」というフォルダを作成し、その中にレイアウトに沿ってソースコードを格納してそのフォルダ内をインポートします。

cd ~/Desktop/tmpdir
svn import -m “initial import” . file:///Users/yusuke/repos

最後に「Committed revision 1.」と表示されれば OK です。

Coda の設定

Coda のサイト画面から、「サイトを追加」または既存のサイトを編集します。
ローカルルートには、作業コピーを置く ~/work を指定、ソース管理の「ソースをチェックアウト」をクリックし、

リポジトリ URL:svn://localhost/
ユーザ名:yusuke
パスワード:p@ssw0rd

と入力し、「チェックアウト」をクリックします。そうすうとローカルルートに指定したフォルダが作成され、そこにソースがチェックアウトされます。
そうすると、Coda の「ソース管理」メニューからコミットや diff ができるようになります。

ちゃんとチェックアウトされたことを確認後、デスクトップの tmpdir フォルダは削除します。

参考リンク

連続するページの JSONP を取得して最後に Callback 関数を呼ぶライブラリ

拙作のツール、「Twitter Stream Viewer」や「Twitter Follow Manager(作成中)」で、Twitter API から複数ページを取得するロジックがあるんですが、そのロジックがスマートではないのでライブラリを作ってみました。
使ってみたら結構便利だったので公開してみます。

jsonPageLoader

使い方はこんな感じです。

var loader = new jsonPageLoader({
  url     : 'http://twitter.com/statuses/friends.json',
  start   : 1,
  end     : function(result) { return result.length > 0; },
  onStart : function(page) { alert(page + 'ページ目の取得開始'); },
  onEnd   : function(result, page) { alert(page + 'ページ目の取得完了'); },
  callback: function(results, start, end) { alert('全ページの取得完了'); }
});

オブジェクトを new してあげて、引数にハッシュ形式でオプションを渡します。
url にはベースとなる URL を指定します。
start には開始するページ番号を指定します。省略すれば 1 ページ目からになります。
end には終了するページ番号または関数を指定します。必須。
5 と指定すれば 5 ページ目まで取得して処理が終了します。
関数を指定した場合、各ページの取得完了時に結果を引数としてこの関数が呼び出されます。false が返されるまでページ番号を 1 ずつ増やしながら処理が続行されます。上の例の場合、取得結果が 0 件になったところで処理が終了します。
onStart, onEnd は各ページの処理開始・終了時に呼び出される関数を指定します。省略可。
callback は全ページの取得完了時に呼び出される関数を指定します。引数の results はページ毎の結果を格納した配列です。ページ番号と添字が一致しています。

ライブラリのソースは以下で。

Continue reading

jQuery.data() について

最近 jQuery のプラグインを作っていて、DOM 要素毎にデータを一時的に保持できる機能を持ったメソッド jQuery.data() がかなり便利であることに気がつきました。

例として検索フォームなどでよく使われている、フォームの入力フィールドにあらかじめ説明を入れておき、フォーカス時に消すプラグイン(にするほどのものじゃないけど)のソースを以下に載せます。

(function($) {
  $.fn.previewInput = function(previewValue) {
    if((typeof previewValue != 'string') || (previewValue.length == 0)) return;

    return this.each(function() {
      if(this.value == undefined) return;

      // 元の文字色を保存しておく
      $.data(this, 'previewInput', $(this).css('color'));

      // call, focus, blur 時に文字色を変更する
      $(this).attr('value', previewValue)
      .css('color', '#999999')
      .blur(function() {
        if($(this).val().length == 0) {
          $(this).val(previewValue).css('color', '#999999');
        }
      }).focus(function() {
        if($(this).val() == previewValue) {
          $(this).val('').css('color', $.data(this, 'previewInput'));
        }
      });
    });
  };
})(jQuery);

そこで、この data() メソッドがどういう仕組みでデータを保存しているのか疑問に思ったので、Firebug で調べてみました。

previewinput_1
previewinput_2

DOM の該当要素に、jQuery オブジェクトの一意の名前である「”jQuery” + timestamp 値」のプロパティを作成し、そこに ID(uuid) がセットされています。
その ID と jQuery 内の配列 cache の添え字が紐付いており、データを設定 / 取得することができるようになっていることが分かりました。

紹介したプラグインもそうですが、イベント処理で変更前の状態を保持しておく場所等で有効活用できそうですね。

参考リンク

短縮 URL を展開する Greasemonkey

きっかけ

Twitter など字数制限があるサイトではよく使われている URL の短縮サービス。
フィッシング詐欺に使われることもあるそうなので注意が必要です。
でもいちいちリンクを踏むたびにプレビューするのも煩わしいと思ったので、短縮 URL を展開表示するグリモンを書いてみました。

今回、Long URL Please というところで TinyURL 等の代表的なものだけでなく、55 種類のサービスに対応した API を公開していたので、それを使わせていただきました。

なお、このグリモンは Firefox には対応していません(理由は後述)。対応しました。上記リンクからプラグインを入れると幸せになれると思います。
Safari で動作確認済。Opera でも多分動きます。Opera でも動作確認しました。oAutoPagerize 対応。

インストール

こちらからどうぞ。

Short URL Expander for Greasemonkey

ソース

// ==UserScript==
// @name           Short URL Expander
// @namespace      http://www.sukechan.net/
// @description    Expand shortened URLs.
// @include        *
// @version        1.1
// ==/UserScript==

(function() {
  var apiUrl = 'http://www.longurlplease.com/api/v1.1';
  var urls = new Array();
  var shortUrlsPattern = new RegExp("(^http(s?)://(adjix\.com|b23\.ru|bacn\.me|bit\.ly|bloat\.me|budurl\.com|cli\.gs|clipurl\.us|dwarfurl\.com|ff\.im|fff\.to|href\.in|idek\.net|is\.gd|korta\.nu|lin\.cr|ln\-s\.net|loopt\.us|merky\.de|moourl\.com|nanourl\.se|ow\.ly|peaurl\.com|ping\.fm|piurl\.com|pnt\.me|poprl\.com|reallytinyurl\.com|rubyurl\.com|short\.ie|short\.to|smallr\.com|sn\.vc|snipr\.com|snipurl\.com|snurl\.com|tiny\.cc|tinyurl\.com|tr\.im|tra\.kz|twurl\.cc|twurl\.nl|u\.mavrev\.com|ur1\.ca|url\.az|url\.ie|urlx\.ie|w34\.us|xrl\.us|yep\.it|zi\.ma|zurl\.ws)/[a-zA-Z0-9_-]*)|((^http(s?)://[a-zA-Z0-9_-]+\.notlong\.com)|(^http(s?)://[a-zA-Z0-9_-]+\.qlnk\.net)|(^http(s?)://chilp\.it/[?][a-zA-Z0-9_-]+))[/]?$");

  // JSONP Callback Function
  function createNamespace() {
    window.shortURLExpanderUserJs = {
      json_callback: function(r) {
        for(url in r) {
          for(var i = 0, l = this.items.length; i < l; i++) {
            var item = this.items[i];
            if(item.href == url) {
              item.href = r[url];
              if(r[url].length <= 40) {
                item.textContent = r[url];
              } else {
                item.textContent = r[url].substr(0, 30) + '...';
              }
            }
          }
        }
      },
      items: []
    };
  }

  var f = function() {
    var x = document.evaluate('//a', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for(var i = 0; i < x.snapshotLength; i++) {
      var url = x.snapshotItem(i).href;
      if(url.match(shortUrlsPattern)) {
        if((urls.length == 0) && (typeof GM_xmlhttpRequest == 'undefined')) {
          createNamespace();
        } else {
          var items = new Array();
        }
        urls.push(url);
        if(typeof GM_xmlhttpRequest == 'undefined') {
          shortURLExpanderUserJs.items.push(x.snapshotItem(i));
        } else {
          items.push(x.snapshotItem(i));
        }
      }
    }
    if(urls.length > 0) {
      var requestUrl = apiUrl + '?q=' + urls.join('&q=');
      if(typeof GM_xmlhttpRequest == 'undefined') {
        jsonp(requestUrl);
      } else {
        GM_xmlhttpRequest({
          method: 'GET',
          url: requestUrl,
          onload: function(x) {
            var r = eval('(' + x.responseText + ')');
            for(var url in r) {
              for(var i = 0, l = items.length; i < l; i++) {
                var item = items[i];
                if(item.href == url) {
                  item.href = r[url];
                  if(r[url].length <= 40) {
                    item.textContent = r[url];
                  } else {
                    item.textContent = r[url].substr(0, 30) + '...';
                  }
                }
              }
            }
          }
        });
      }
    }
  }
  function jsonp(url) {
    var s = document.createElement('script');
    s.src = url + '&callback=shortURLExpanderUserJs.json_callback';
    s.charset = 'UTF-8';
    document.body.appendChild(s);
  }

  f();
  addFilter(f);
  function addFilter(filter, i) {
    i = i || 4;
    if(window.AutoPagerize && window.AutoPagerize.addFilter) {
      window.AutoPagerize.addFilter(filter);
    } else if(i > 1) {
      setTimeout(arguments.callee, 1000, filter, i -1);
    }
  }
})();

Firefox に対応していない理由

このグリモンでは JSONP を使って API を実行しており、Callback 関数をグローバルに定義する必要がありました。
ただ、Firefox の Greasemonkey からグローバルの変数にアクセスする方法が無いらしいので、対応できませんでした><。
Firefox はプラグインがあるので、まあ要らないんですけどね。

(追記@20090315)
version 1.1 で Firefox / Greasemonkey に対応しました。
それと Firefox / Greasemonkey の場合、グローバル変数には unsafeWindow を使えばアクセスできます。

 Page 1 of 11  1  2  3  4  5 » ...  Last » 

Home

Search
Feeds
Meta
あわせて読みたい
あわせて読みたいブログパーツ
Others...
フィードメーター - sukechan.net
track feed

Return to page top