Fork me on GitHub

2011/05/02


このエントリーをはてなブックマークに追加
普段からvimでtwitterしてますが、どっちかっていうとtwitter見たりボヤいたりするのにもコンソールのままでいたい派です。
ページャとか、自分で拾うのでコンソールにまま出力されてくれればいい...そんなtwitterクライアントが欲しかったので自分で書いた。
mattn/twty - GitHub

twty: command-line twitter client

https://github.com/mattn/twty
Go言語で書いたのは一度ビルドすればexeだけでランタイムが要らないから。あとGoだと簡単に書けるのよね。僕は。
ただ残念ながらどうしてもWindowsのコマンドプロンプトをサポートから外す訳にいかなかったのでGoのiconv拡張だけはライブラリとして入れる必要があります。
なお、この場合出来上がったexeとiconv.dllだけあれば動作します。USBかなんかで持ち歩けばどこでもtwitter出来ます。

実行すると初回は自動でブラウザが起動して、PINコードの入力を求められるので、ブラウザに出てる番号をコピペして下さい。使い方は
# twty -h
で分かる内容のままですが、だいたいこんな動きになります。
# twty -h
Usage of twty:
  -f ID: specify favorite ID
  -i ID: specify in-reply ID, if not specify text, it will be RT.
  -l USER/LIST: show list's timeline (ex: mattn_jp/subtech)
  -u USER: show user's timeline
  -r: show replies
  -v: detail display

# twty
mattn_jp: あーあー
☃☃☃☃☃☃☃☃☃: いーいー
...

こんな感じ

# twty -r
☃☃☃☃☃☃☃☃: @mattn_jp こちらこそです。ドキュメント直したり、ソースに手を入れて pull したりという程の腕も頭もないです。この程度のお手伝いでしたら、喜んでヤリマッセw
☃☃☃☃☃☃☃☃: ハァハァwww RT @mattn_jp: うし。コマンドラインで使えるtwitter clientでけた。goで書いたからランタイム要らない。USBに入れとけばいつでもどこでも... もちろんwin32も対応。
...

こんな感じ

# twty -u mattn_jp
mattn_jp: うし。コマンドラインで使えるtwitter clientでけた。goで書いたからランタイム要らない。URLに入れとけばいつでもどこでも... もちろんwin32も対応。
mattn_jp: URLってなんだ。USBねw
...

こんな感じ

# twty -l mattn_jp/subtech
☃☃☃☃☃☃☃☃☃: java-ja からきました、っていえば下品なこともいい放題ですし。
☃☃☃☃☃: 懇談会どこ? #perlcasual
...

こんな感じ

# twty -v
☃☃☃☃☃: ☃☃☃☃☃☃☃
  懇談会どこ? #perlcasual
  XXXXXXXXXXXXXXXXX
  Thu Apr 28 10:11:08 +0000 2011

☃☃☃☃☃☃☃☃☃: ☃☃ ☃☃
  朝までのんでればいいんじゃ RT @☃☃☃☃☃: >< RT @☃☃☃☃☃☃☃☃: 俺明日早いんすよねー RT @☃☃☃☃☃: @☃☃☃☃☃☃☃☃ 見せ決まったらおしえて///  #perlcasual
  YYYYYYYYYYYYYYYYY
  Thu Apr 28 10:14:34 +0000 2011
設定ファイルはWindows XPデフォルトだと
%USERPROFILE¥Application Data¥&twty¥settings.json
に格納されます。
Posted at 04:07 in ソフトウェア::lang::go
Tagged as: go, twitter
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2011/03/03


このエントリーをはてなブックマークに追加
こんばんわ。node.jsやってないとjavascriptのもぐりだと言われている昨今、いかがお過ごしでしょうか。
個人的には node.js は2回くらい飽きてしまって、3周目くらいです。

よく考えたらこのブログでは一度も node.js に触れてなかったなーと思ったのと、最近触ってないから忘れてそうだな...という思いからエントリにしてみました。
node.js が面白いと言われている理由の一つに非同期処理があります。そして非同期を面白くする題材として websocket があります。今日はその websocket を使って、サンプルを作ります。
物としては、twitter の filter stream から、instagr.ampicplz.com の画像URLを収集し、それをクライアントにブロードキャストします。クライアントはそれを受けて Growl 風にポップアップ表示するという物です。
まずサイトに仕上げるには、静的ファイルサーバが必要になります。
どのサイトでも紹介してそうですが、適当ながら動くレベルの物を作ります。
var sys = require('sys'),
    fs = require('fs'),
    url = require('url'),
    http = require('http');

// static file server
var extmap = {'.htm''text/html''.css''text/css'};
var server = http.createServer(function(req, res){
  var uri = url.parse(req.url).pathname;  
  if (uri.match('/$')) uri = '/index.html'
  try {
    var filename = __dirname + '/static' + uri;
    var contenttype = extmap[filename.substr(filename.lastIndexOf("."), 4)] || 'application/octet-stream';
    var rs = fs.createReadStream(filename);
    res.writeHead(200, {'Content-Type': contenttype});
    sys.pump(rs, res);
  } catch(e) {
    console.log(e);
    res.sendHeader(404, {"Content-Type""text/plain"});  
    res.write("Not Found\n");  
    res.close();  
  }
});
server.listen(8080);
これを server.js というファイル名で保存して
# node server.js
と実行すると、static フォルダ配下のファイルが http://localhost:8080/ でサーブされる仕組みです。
次に、websocket を扱える様にします。既存のサーバ上に websocket を乗せるには以下の様にサーバを指定すればokです。(おそらくこれは sugyan さんへのヒントになるのかもしれませんが)
var ws = websocket.createServer({server: server});
クライアント側の実装だとこんな感じですね。
var connection = new WebSocket('ws://' + location.host);
connection.onopen = function(event) {
}
connection.onmessage = function(event) {
}
さて、下準備は出来たので twitter stream を扱いましょう。twitter stream API はまだ basic 認証で扱えます。http-basic-auth モジュールを使いましょう。なお、Ubuntu 向けに launchpad から提供されている nodejs には node-waf が入っていませんので、依存で入る base64 モジュールのビルドに失敗します。どうしても試したい人は nodejs をソースからビルドしましょう。
twitter filter stream を扱うコードは以下の様になります。
var basicauth = require('http-basic-auth');

var basicauthclient = basicauth.createClient(80'stream.twitter.com'falsefalse, account)
var req = basicauthclient.request('GET''/1/statuses/filter.json?track=picplz,instagr', {'host''stream.twitter.com'})
req.end();
req.on('response'function (res) {
  res.on('data'function(chunk) {
  })
})
account は username と password をキーに持つオブジェクトです。ご自分のアカウントを設定しましょう。
twitter stream を受信したら、tweet の entities から URL 一覧を取得し、instagr.am と picplz.com の画像URLを取得しましょう。picplz.com はURLの後ろに /thumb/400 を足すだけですが、instagr.am は API にアクセスしなければなりません。
tweet 受信時の処理は以下の様になりました。
req.on('response'function (res) {
  res.on('data'function(chunk) {
    try {
      var tweet = JSON.parse(chunk);
      // parse entities urls
      [].forEach.call(tweet.entities.urls, function(item) {
         // pick images of instagr.am or picplz.com
         if (item.url.match('^http://instagr.am/p/')) {
           var client = http.createClient(80'instagr.am');
           var req = client.request('GET''/api/v1/oembed/?format=json&maxheight=330&url=' + item.url, {'host''instagr.am'});
           req.on('response'function(res){
             res.on('data'function(chunk){
               var url = JSON.parse(chunk).url
               console.log(url)
               ws.broadcast(url)
             });
           });
           req.end();
         }
         if (item.url.match('^http://picplz.com/')) {
           var url = item.url + '/thumb/400'
           console.log(url)
           ws.broadcast(url)
         }
      });
    } catch(e) { console.log(e) }
  });
});
サーバ部は出来上がりですね。最後にクライアント部を作りましょう。
$(function() {
  var connection = new WebSocket('ws://' + location.host);
  connection.onopen = function(event) {
    $('#fotoflo').empty();
  }
  connection.onmessage = function(event) {
    var x = (Math.random() * ($(document).width() - 330)).toFixed();
    var y = (Math.random() * ($(document).height() - 330)).toFixed();
    var item = $('<div/>')
      .addClass('popup-image')
      .css({'left': x + 'px''top': y + 'px''position''absolute''display''none'})
      .appendTo('body')
    $('<img/>')
      .attr('src'event.data)
      .bind('load'function() {
        $(item).fadeIn(500).delay(10000).fadeOut(500function() {
          $(this).remove()
        })
      }).appendTo(item)
  }
})
こんな感じでしょうか。受信する度にランダムな位置へ画像をフェイドイン表示し、数秒経過したら消すというオーソドックスな物です。
以下、リポジトリの場所は示しますが、サーバの全体ソースを載せておきます。
var sys = require('sys'),
    fs = require('fs'),
    url = require('url'),
    http = require('http'),
    websocket = require('websocket-server'),
    basicauth = require('http-basic-auth');

// load twitter account
var account = JSON.parse(fs.readFileSync(__dirname + '/config.json''utf8'))

// static file server
var extmap = {'.htm''text/html''.css''text/css'};
var server = http.createServer(function(req, res){
  var uri = url.parse(req.url).pathname;  
  if (uri.match('/$')) uri = '/index.html'
  try {
    var filename = __dirname + '/static' + uri;
    var contenttype = extmap[filename.substr(filename.lastIndexOf("."), 4)] || 'application/octet-stream';
    var rs = fs.createReadStream(filename);
    res.writeHead(200, {'Content-Type': contenttype});
    sys.pump(rs, res);
  } catch(e) {
    console.log(e);
    res.sendHeader(404, {"Content-Type""text/plain"});  
    res.write("Not Found\n");  
    res.close();  
  }
});
server.listen(80);

// listen websocket server on the server
var ws = websocket.createServer({server: server});

// twitter filter stream
var basicauthclient = basicauth.createClient(80'stream.twitter.com'falsefalse, account)
var req = basicauthclient.request('GET''/1/statuses/filter.json?track=picplz,instagr', {'host''stream.twitter.com'})
req.end();
req.on('response'function (res) {
  res.on('data'function(chunk) {
    try {
      var tweet = JSON.parse(chunk);
      // parse entities urls
      [].forEach.call(tweet.entities.urls, function(item) {
         // pick images of instagr.am or picplz.com
         if (item.url.match('^http://instagr.am/p/')) {
           var client = http.createClient(80'instagr.am');
           var req = client.request('GET''/api/v1/oembed/?format=json&maxheight=330&url=' + item.url, {'host''instagr.am'});
           req.on('response'function(res){
             res.on('data'function(chunk){
               var url = JSON.parse(chunk).url
               console.log(url)
               ws.broadcast(url)
             });
           });
           req.end();
         }
         if (item.url.match('^http://picplz.com/')) {
           var url = item.url + '/thumb/400'
           console.log(url)
           ws.broadcast(url)
         }
      });
    } catch(e) { console.log(e) }
  });
});
簡単ですね!
imagestream
server.js と同じフォルダに config.json というファイルがあるので、そのファイル内を twitter アカウントで修正して頂ければ動きます。
mattn/node-image-stream - GitHub

display images from twitter stream

https://github.com/mattn/node-image-stream
みなさんもぜひ面白い物作ってみて下さい。
Posted at 04:20 in ソフトウェア::nodejs
Tagged as: instagram, javascript, nodejs, picplz, twitter
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2010/11/10


このエントリーをはてなブックマークに追加
favstarを見にいくのにいちいちブラウザあげるのが面倒くさいので、なんとかならないかなと思ってた。
普段TwitVimを使ってるんだけど、同じようにvimから見れないかなーと思って勢いで書いた。
function! s:ShowFavStar(...)
  let user = a:0 > 0 ? a:1 : exists('g:favstar_user') ? g:favstar_user : ''
  if len(user) == 0
    echohl WarningMsg
    echo "Usage:"
    echo "  :FavStar [user]"
    echo "  you can set g:favstar_user to specify default user"
    echohl None
    return
  endif
  let yql = "select * from html where url = 'http://favstar.fm/users/".user."/recent' and xpath = '//div[@class=\"tweetContainer\"]'"
  let res = http#get("http://query.yahooapis.com/v1/public/yql", {'q': yql})
  let dom = xml#parse(res.content)
  for item in dom.childNode('results').childNodes('div')
    let tweet = item.find('div', {"class": "theTweet"})
    let text = substitute(tweet.childNode('p').value(), "\n", " ", "g")
    let text = substitute(text, "^ *", "", "")
    echohl Function
    echo text
    echohl None
    let actions = item.findAll('div', {"class": "avatarList"})
    for action in actions
      let line = ''
      if action.attr['id'] =~ "^faved_by_"
        let line .= "FAV:"
      elseif action.attr['id'] =~ "^rt_by_"
        let line .= "RT:"
      endif
      for a in action.findAll('img')
        let line .= " " . a.attr['alt']
      endfor
      echohl Statement
      echo line
      echohl None
    endfor
    echo "\n"
  endfor
endfunction

command! -nargs=? FavStar call <SID>ShowFavStar(<f-args>)
ソースはたったこれだけ。いちおうリポジトリのリンク
mattn's favstar-vim at master - GitHub

vimscript for favstar

http://github.com/mattn/favstar-vim
実行には webapi-vim が必要です。webapi-vim は更新が多いと思うので pathogen とかで使った方がよいです。
mattn's webapi-vim at master - GitHub

vim interface to Web API

http://github.com/mattn/webapi-vim
html(実際にはxml)をDOMでスクレイピングしてます。webapi-vimのxml.vimさまさまです。誰が書いたの?スゲーな。えっ俺ですか。俺スゲー?
で、YQLを使ってスクレイピングしているのはHTMLは正規化されていないのでYQLに正規化してもらっています。
実行は
:FavStar otsune
とするかvimrcに
let g:favstar_user = "otsune"
と書いておいて
:FavStar
とする等して下さい。動かなかったとしても大丈夫だ。問題ない。

実行するとこんな画面になります。
favstar-vim
ご活用下さい。もうvimscriptなんでもアリな気がしてきた。
Posted at 02:32 in ソフトウェア::vim
Tagged as: favstar, twitter, vim
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip

2010/10/10


このエントリーをはてなブックマークに追加
PerlのGrowlライブラリって色々ある訳ですが、誰かが書いたGrowlするアプリのコードを実行しようとした際にWindowsやLinuxで動かないと少し悲しくなります。
そこでGrowl::Anyってのがあればいいんじゃね?って事で書いてみました。
Mac::Growl、notify-send、Desktop::Notify、Net::GrowlClient、Growl::GNTPのどれかがインストールされていれば使えます。
mattn's p5-Growl-Any at master - GitHub

perl module that provide any growl application

http://github.com/mattn/p5-Growl-Any
今日はこれを使って、twitterのhome_timelineをGrowlするスクリプトを書いてみました。
#!perl

use strict;
use warnings;
use Config::Pit;
use Encode;
use Growl::Any;
use Net::Twitter::Lite;

my $config = pit_get("twitter-growler");
unless ( $config->{access_token_secret} ) {
    $config = pit_get(
        "twitter-growler",
        require => {
            consumer_key    => "YOUR_CONSUMER-KEY",
            consumer_secret => "YOUR-CONSUMER-SECRET",
        }
    );
    my $nt = Net::Twitter::Lite->new( %{$config} );
    $| = 1;
    print "Authorize this app at:\n ", $nt->get_authorization_url,
      "\nAnd enter the PIN: ";
    my $pin = <STDIN>;
    chomp $pin;
    my ( $access_token, $access_token_secret, $user_id, $screen_name ) =
      $nt->request_access_token( verifier => $pin );
    $config->{access_token}        = $access_token;
    $config->{access_token_secret} = $access_token_secret;
    pit_set( "twitter-growler", data => $config );
    exit;
}

my $nt = Net::Twitter::Lite->new( %{$config} );
$nt->access_token( $config->{access_token} );
$nt->access_token_secret( $config->{access_token_secret} );

my $growl = Growl::Any->new();
$growl->register( "Growl/Twitter", ["tweet"] );

my $last_id = undef;
while (1) {
    my @sl;
    if ($last_id) {
        @sl = $nt->friends_timeline( { count => 200, since_id => $last_id } );
    }
    else {
        @sl = $nt->friends_timeline( { count => 5 } );
    }
    for my $s ( @{ $sl[0] } ) {
        $last_id = $s->{id} unless $last_id;
        $growl->notify(
            "tweet",
            encode_utf8( $s->{user}{screen_name} ),
            encode_utf8( $s->{text} ),
            $s->{user}{profile_image_url},
        );
        sleep(4);
    }
}
上に書いたモジュールのどれかが入っていれば(notify-sendはコマンド)、MacでもLinuxでもWindowsでも動くはずです。

twitter-growler1
なお、実は現在Growl::Anyのパラメータをutf8フラグ付きにしようかどうか迷っているので、もしかするとその修正を入れた後にこのスクリプトを動かすと文字化けしてしまうかもしれませんので注意です。

今後ネットに転がっていたスクリプトを何も修正せずにGrowlする事が出来る様になれば、素敵な事だなと思います。

ちなみに上記のスクリプトですが、初回に実行するとconsumer_key/consumer_secretを聞かれます。ブラウザでPINを貰って入力するとConfig::Pitで設定を保存して終了するので、再度起動すると動き出します。
streamなAPIでもやってみたのですが、更新が多くGrowlの表示数が激しすぎたので一定間隔で取得する様にしてあります。お好みで修正してみて下さい。
Posted at 02:44 in ソフトウェア::lang::perl
Tagged as: growl, oauth, perl, twitter
Bookmarks: add to hatena add to hatena | add to delicious.com | add to livedoor.clip add to livedoor.clip