nano jpmobile

rails で携帯アプリと言ったら定番のjpmobile の超軽量コピー。

できること

  • キャリア判定
  • 端末情報取得

ソース

# porting from http://github.com/darashi/jpmobile/tree/0.0.3

class Mobile
  def self.carriers
    self.constants - %w[Base]
  end
  
  def self.create(env)
    return nil unless env
    
    self.carriers.each do |carrier|
      klass = self.const_get(carrier)
      return klass.new(env) if klass::USER_AGENT_REGEXP =~ env['HTTP_USER_AGENT']
    end
    
    nil
  end
  
  class Base
    USER_AGENT_REGEXP = nil
    
    def initialize(env)
      @env = env
    end
    
    def device_name; end
    
    def ident(options = {})
      ident_subscriber(options) || ident_device      
    end
    
    def ident_subscriber(options = {}); end
    
    def ident_device; end
    
    private
    
    def user_agent
      @env['HTTP_USER_AGENT']
    end
  end
  
  class Docomo < Base
    USER_AGENT_REGEXP = /^DoCoMo/
    
    def device_name
      'imode'
    end
    
    def ident_subscriber(options = {})
      @env['HTTP_X_DCMGUID'] || (options[:use_icc] && icc)
    end
    
    def ident_device
      case user_agent
      # mova
      when /ser([0-9a-zA-Z]{11})$/
        $1
      # FOMA
      when /ser([0-9a-zA-Z]{15});/
        $1
      else
        nil
      end
    end
    
    private
    
    def icc
      user_agent =~ /icc([0-9a-zA-Z]{20})\)/
      $1
    end
  end
  
  class Au < Base
    USER_AGENT_REGEXP = /^(KDDI|UP.Browser)/

    def device_name
      'ezweb'
    end
    
    def ident_subscriber(options = {})
      @env['HTTP_X_UP_SUBNO']
    end  
  end
  
  class Softbank < Base
    USER_AGENT_REGEXP = /^(SoftBank|Vodafone|J-PHONE)/
    
    def device_name
      'yahoo'
    end
    
    def ident_subscriber(options = {})
      @env['HTTP_X_JPHONE_UID']
    end
    
    def ident_device
      user_agent =~ /SN(.+?) /
      $1
    end
  end
end

# XXX: Mobile.constatns doesn't return each carrier classes before loading them.
class Mobile::Base
  Mobile.carriers.each do |carrier|
    define_method("#{carrier.downcase}?") do
      self.instance_of?(Mobile.const_get(carrier))
    end
  end
end

使い方

require 'mobile'

class MobileTest < Test::Unit::TestCase
  def setup
    # mocks of request headers
    @mova     = { 'HTTP_USER_AGENT' => 'DoCoMo/1.0/X503i/c10/serabcdef12345' }
    @foma     = { 'HTTP_USER_AGENT' => 'DoCoMo/2.0 SO901i(c100;TB;W22H12;ser0a02t138yw2q901;icc5r12ugi08uahf693dd43)' }
    @ez       = { 'HTTP_USER_AGENT' => 'KDDI-CA32 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0' }
    @tuka     = { 'HTTP_USER_AGENT' => 'UP.Browser/3.04-SN12 UP.Link/3.4.4' }
    @yahoo    = { 'HTTP_USER_AGENT' => 'SoftBank/1.0/910T/TJ001/SN0000000000007sb Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1' }
    @vodafone = { 'HTTP_USER_AGENT' => 'Vodafone/1.0/V903SH/SHJ001/SN0000000000007vd Browser/UP.Browser/7.0.2.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 Ext-J-Profile/JSCL-1.2.2 Ext-V-Profile/VSCL-2.0.0' }
    @jphone   = { 'HTTP_USER_AGENT' => 'J-PHONE/4.3/V603SH/SN000000007jp SH/0007aa Profile/MIDP-1.0 Configuration/CLDC-1.0 Ext-Profile/JSCL-1.3.2' }
  end
  
  def test_create
    # DoCoMo
    assert_instance_of Mobile::Docomo, create(@mova)
    assert_instance_of Mobile::Docomo, create(@foma)
    
    # Au
    assert_instance_of Mobile::Au, create(@ez)
    assert_instance_of Mobile::Au, create(@tuka)
    
    # SoftBank
    assert_instance_of Mobile::Softbank, create(@yahoo)
    assert_instance_of Mobile::Softbank, create(@vodafone)
    assert_instance_of Mobile::Softbank, create(@jphone)
    
    # Unknown
    assert_nil create(nil)
    assert_nil create('Mozilla/3.0(WILLCOM;KYOCERA/WX310K/2;1.2.2.16.000000/0.1/C100) Opera 7.0')
    assert_nil create('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;  Embedded Web Browser from: http://bsalsa.com/; .NET CLR 2.0.50727)')
    assert_nil create('Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)')
  end
  
  def test_determination
    d = create(@foma)
    assert d.docomo?
    assert !d.au?
    assert !d.softbank?
    
    e = create(@ez)
    assert !e.docomo?
    assert e.au?
    assert !e.softbank?
    
    y = create(@yahoo)
    assert !y.docomo?
    assert !y.au?
    assert y.softbank?
  end
  
  def test_device_name
    assert_equal 'imode', create(@foma).device_name
    assert_equal 'ezweb', create(@ez).device_name
    assert_equal 'yahoo', create(@yahoo).device_name
  end
  
  def test_ident_imode
    # without ident
    m = create('HTTP_USER_AGENT' => 'DoCoMo/1.0/X503i/c10/erab2345')

    assert_nil m.ident
    assert_nil m.ident_subscriber
    assert_nil m.ident_device

    # mova
    m = create(@mova)
  
    assert_equal 'abcdef12345', m.ident(:use_icc => true)    
    assert_equal 'abcdef12345', m.ident
    assert_nil m.ident_subscriber(:use_icc => true)
    assert_nil m.ident_subscriber    
    assert_equal 'abcdef12345', m.ident_device
    
    # foma
    m = create(@foma)

    assert_equal '5r12ugi08uahf693dd43', m.ident(:use_icc => true)
    assert_equal '0a02t138yw2q901', m.ident
    assert_equal '5r12ugi08uahf693dd43', m.ident_subscriber(:use_icc => true)
    assert_nil m.ident_subscriber
    assert_equal '0a02t138yw2q901', m.ident_device    

    # with imode id
    m = create(@foma.merge('HTTP_X_DCMGUID' => '1q2w3e4'))
    
    assert_equal '1q2w3e4', m.ident(:use_icc => true)
    assert_equal '1q2w3e4', m.ident
    assert_equal '1q2w3e4', m.ident_subscriber(:use_icc => true)
    assert_equal '1q2w3e4', m.ident_subscriber
    assert_equal '0a02t138yw2q901', m.ident_device
  end
  
  def test_ident_ezweb
    # without ident
    m = create(@ez)

    assert_nil m.ident
    assert_nil m.ident_subscriber
    assert_nil m.ident_device
    
    # with ident
    m = create(@ez.merge('HTTP_X_UP_SUBNO' => '05031234567890_6h.ezweb.ne.jp'))

    assert_equal '05031234567890_6h.ezweb.ne.jp', m.ident
    assert_equal '05031234567890_6h.ezweb.ne.jp', m.ident_subscriber
    assert_nil m.ident_device
  end
  
  def test_ident_softbank
    # without ident
    m = create('HTTP_USER_AGENT' => 'Vodafone/1.0/V903T/TJ001 Browser/VF-Browser/1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 Ext-J-Profile/JSCL-1.2.2 Ext-V-Profile/VSCL-2.0.0')

    assert_nil m.ident
    assert_nil m.ident_subscriber
    assert_nil m.ident_device    
    
    # yahoo
    m = create(@yahoo)
    
    assert_equal '0000000000007sb', m.ident
    assert_nil m.ident_subscriber
    assert_equal '0000000000007sb', m.ident_device

    # vodafone
    m = create(@vodafone)
    
    assert_equal '0000000000007vd', m.ident
    assert_nil m.ident_subscriber
    assert_equal '0000000000007vd', m.ident_device
    
    # jphone
    m = create(@jphone)
    
    assert_equal '000000007jp', m.ident
    assert_nil m.ident_subscriber
    assert_equal '000000007jp', m.ident_device
    
    # with jphone uid
    m = create(@yahoo.merge('HTTP_X_JPHONE_UID' => 'a1aaaaaaaaaaaaa9'))
    
    assert_equal 'a1aaaaaaaaaaaaa9', m.ident
    assert_equal 'a1aaaaaaaaaaaaa9', m.ident_subscriber
    assert_equal '0000000000007sb',  m.ident_device    
  end
  
  private
  
  def create(user_agent)
    Mobile.create(user_agent)
  end
end

メモ

  • jpmobile はシンプルに実装されていて、ruby(rails) のコードリーディングにとても良い
  • Object.const_get と内部クラス
module Hoge
  class Fuga
  end
end

Object.const_get('Fuga')
NameError: uninitialized constant Fuga
        from (irb):5:in `const_get'
        from (irb):5
        from :0

Hoge.const_get('Fuga')
Hoge::Fuga