sinatraつかって跳.jpとp.tlをbit.lyとj.mpに化けさせる

昨日、bit.lyのAPIのふりをする短縮URLを作ってふと気付いたのだが、

あまりメジャーでないためクライアントが対応していない短縮URLもこの方法で使いやすくなるのではないかと考えた。

ここでターゲットにしたのは、跳.jp と p.tl だ。


跳.jpは日本語ドメインを使った短縮URLで非常に短くなるのが特徴。

http://丁嗟.跳.jp のようになる。確かに短い

p.tlはPixivが提供している短縮URLサービスで、主にPixivの投稿画像についてTwitterにPostするときに使う。

けどそれ以外に使っていけない決まりはないし、ちゃんとAPIも提供されている。

http://p.tl/3WuT のようになる。利用者が少ないからか短い。なんか昨日作ったサービスが負けた気になる。

ただ、どちらも比較的新しいサービスなのでなかなか対応していない。

というわけでbit.lyのAPIのふりをするサーバーをlocalhostに作ろうというわけだ。

bit.lyとj.mpは同じAPIを使っているので、使い分けもできるかもしれない。

Rubyのsinatraを使って作ってみた。


まずgemでいろいろ入れた。

gem install sinatra
gem install json
gem install rubygems-update
update_rubygems
gem install activesupport

そして、sinatraでサーバーを作った。その前にIISを切っておく必要があったが。

# -*- coding: utf-8 -*-
require 'rubygems'
require 'sinatra/base'
require 'json'
require 'net/http'
require 'active_support/all'
Net::HTTP.version_1_2
class MyApp < Sinatra::Base
  @@jmp=:tobi; @@bitly=:tobi; #:tobiまたは:ptl
  @@ptlkey='*******';
  def tobi(url)
    Net::HTTP.start('xn--vt3a.jp',80) {|http|
      response = http.get("/api?url=#{url}")
      response.body.force_encoding('utf-8')
    }
  end
  def ptl(url)
    Net::HTTP.start('p.tl',80) {|http|
      response = http.get("/api/api_simple.php?key=#{@@ptlkey}&url=#{url}")
      res=JSON::parse(response.body)
      res['status']=='ok' ? res['short_url'] : nil
    }
  end
  def shorten(params)
    shorten=request.host=='api.j.mp'?self.__send__(@@jmp,params[:longUrl]):self.__send__(@@bitly,params[:longUrl])
    p shorten
    if shorten
      if params[:version]=='2.0.1'
        res={'errorCode'=>0,'errorMessage'=>'','result'=>{
             params[:longUrl]=>{'userHash'=>0,'hash'=>0,'shortUrl'=>shorten,'shortCNAMEUrl'=>shorten,
                                'shortKeywordUrl'=>''}},'statusCode'=>'OK'};
      else
        res={'status_code'=>200,'status_txt'=>'OK','data'=>{'hash'=>0,'global_hash'=>0,'url'=>shorten,
                                                          'long_url'=>params[:longUrl],'new_hash'=>0}};
      end
      if params[:format]=='xml'
        if params[:login]=='twit'  #twitはなぜかShift_JISで解釈する
          res.to_xml(:root=>'response',:skip_types=>true,:dasherize=>false,:skip_instruct=>true).
            gsub(/&#(?:(\d*?)|(?:[xX]([0-9a-fA-F]{4})));/){[$1.nil? ? $2.to_i(16) : $1.to_i].pack('U')}.
            encode('shift_jis')
        else
          res.to_xml(:root=>'response',:skip_types=>true,:dasherize=>false).
            gsub(/&#(?:(\d*?)|(?:[xX]([0-9a-fA-F]{4})));/){[$1.nil? ? $2.to_i(16) : $1.to_i].pack('U')}
        end
      else
        res.to_json.gsub(/\\u(\h{4})/){[$1.to_i(16)].pack('U')}.gsub(/\\\//,'/').gsub('":','": ')
      end
    end
  end
  get  '/shorten' do
    shorten(params);
  end
  post '/shorten' do
    shorten(params);
  end
  get  '/v3/shorten' do
    shorten(params);
  end
  post '/v3/shorten' do
    shorten(params);
  end
end
MyApp.run! :host => 'localhost', :port => 80

これでOKというのは不思議な感じだが、それがsinatraだ。

あとhostsにbit.lyとj.mpをlocalhostにしておくよう書いておかないと実験できんな。

127.0.0.1 api.bit.ly api.j.mp

下にget ‘/shorten’ など並んでいるが、これ1つ1つがアクセスを受けるブロックだ。

このブロックで受けてとやかく処理してレスポンスを返す。それだけで作れる。

ruby sinatobi.rb などすれば待ち受け状態になる。そしてCtrl+Cで止まる。

短縮の処理だが、api.j.mpにアクセスされた場合と api.bit.ly にアクセスされた場合で跳.jpとp.tlを使い分けられるようにした。

@@jmpと@@bitlyにそれぞれ跳.jpを使いたければ:tobi、p.tlを使いたい時は:ptlを入れておけばいい。

p.tlを使う時はあらかじめAPIキーを取得して@ptlkeyに代入しておく。(cf. p.tl Web API )

実際の短縮の処理はtobiメソッドかptlメソッドでやってる。どっちもHTTPでアクセスしているだけ。

p.tlの場合はJSONのパースを行っている。跳.jpの場合、マルチバイト文字列の問題があるのでUTF-8だとする処理を行っている。


あとはこの結果をbit.lyのAPIの出力にすればいい。JSONはHash#to_jsonがjsonに入ってたから問題なかったが、

to_xmlがなかなか見あたらなかった。Rails関係のactive_supportにあるのを使えば良かったようだ。

エスケープされた文字列を元に戻すとか処理をすればとりあえずOK。

Tweenではちょっと調整すればうまくいったのだが、Twitでは跳.jpがなぜか文字化けする。

XMLのエンコードは正しそうだけど、どうもTwitはそんなのおかまいなしでShift_JISとして解釈しているようだ。

まぁbit.lyはマルチバイト文字使わんけどさ。というわけでTwitの場合はbit.lyのIDにtwitと入れて使ってください。


本当はこれをサービス化したかったのだが、MongrelというHTTPサーバの実行環境が入らないので断念した。

標準ではWEBrickってのを使ってんだけど、Windowsではデーモン化できんらしい。UNIX系OSでは、

$ ruby sinatobi.rb -e production -d

とすればいいらしいんですけどね。どうもうまくいかんのでこれは後回し。

プロンプトが邪魔だけどスタートアップにでも登録すればいいんじゃないですかね。