miby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ # Mibyの基本ファイル
2
+ # 他のMibyライブラリファイルはこのファイルをrequireする必要があります。
3
+ $:.push File.dirname(__FILE__) + "/../lib"
4
+ require 'nkf'
5
+ require 'tempfile'
6
+ require 'config'
7
+
8
+
9
+ class String
10
+ def to_utf8
11
+ NKF.nkf("-Lu -d -w", self)
12
+ end
13
+ alias utf8 to_utf8
14
+
15
+ def to_euc
16
+ NKF.nkf("-e", self)
17
+ end
18
+ alias euc to_euc
19
+
20
+ # nkf_charset:: -e:EUC, -w:UTF-8, -s:SJIS(*INOF* mixiの文字コードはEUC)
21
+ def to_uri_encode(nkf_charset = "-e")
22
+ URI.encode(NKF.nkf(nkf_charset, self))
23
+ end
24
+ alias uri_encode to_uri_encode
25
+ end
26
+
27
+
28
+ module Miby
29
+ VERSION = "0.0.1"
30
+ CONFIG_FILE = File.dirname(__FILE__) + '/../etc/miby.conf'
31
+ USER_FILE = File.dirname(__FILE__) + '/../etc/user.yaml'
32
+ PAGESTRUCT_FILE = File.dirname(__FILE__) + '/../etc/pagestruct.yaml'
33
+
34
+ # Mibyの規定モジュール
35
+ module Base
36
+ # 1行入力して、入力された文字列を返します。
37
+ # term:: 入力行に表示する文字列
38
+ def input_line(term = "Input", io = $stdin)
39
+ print "#{term}: "; io.__send__(:gets).to_s.chomp
40
+ end
41
+
42
+ # 複数行入力して、入力された文字列を返します。
43
+ # io オブジェクトが設定されていなければ ENV['EDITOR'] を起動します。
44
+ def input_lines(io = nil)
45
+ if io.nil?
46
+ raise "No set IO and env['EDITOR']" if ENV['EDITOR'] == nil
47
+ temp_file = Tempfile.new("miby_base_get_lines")
48
+ system "#{ENV['EDITOR']} #{temp_file.path}"
49
+ io = temp_file
50
+ end
51
+ io.readlines.to_s
52
+ end
53
+
54
+
55
+ # *warning* This method is obsolete. Please using String#uri_encode
56
+ def encode(str)
57
+ URI.encode(NKF.nkf("-e", str.to_s))
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,28 @@
1
+
2
+ module Miby
3
+ # Miby::CONFIG_FILEで指定されあた設定ファイル(デフォルトは etc/miby.conf)を読み込み、設定情報をアクセサメソッドに登録します。
4
+ #
5
+ # == 使い方
6
+ # オブジェクトを生成したらアクセサメソッドで値を呼びだします。
7
+ # config = Miby::Config.new
8
+ # config.http_address #=> mixi.jp
9
+ #
10
+ # == miby.conf
11
+ # mibyの設定ファイルは @name のインスタンス変数で指定します。
12
+ # 指定できる変数は下のアクセサメソッドに登録されている必要があります。
13
+ # 設定できる項目の目的は Miby::CONFIG_FILE のファイルを参照してください(デフォルトは conf/miby.conf)。
14
+ # 例)
15
+ # @http_address = "mixi.jp" # mixiのベースURL(通常は mixi.jp)
16
+ # @http_port = "80" # HTTPサーバのポート番号(通常は80)
17
+ # @proxy_addr = "proxy" # プロキシのホスト名(nilなら無効)
18
+ # @proxy_port = "8080" # プロキシのポート番号(nilなら無効)
19
+ class Config
20
+ def initialize(path = CONFIG_FILE)
21
+ instance_eval File.read(path)
22
+ end
23
+ attr_reader :http_address, :http_port, :proxy_addr, :proxy_port
24
+ end
25
+
26
+ end
27
+
28
+
@@ -0,0 +1,34 @@
1
+ $:.push File.dirname(__FILE__) + "/../lib"
2
+ require 'base'
3
+ require 'net/http'
4
+ Net::HTTP.version_1_2
5
+
6
+ module Miby
7
+ # Mibyで利用するのhttpメソッドを提供します。
8
+ # 接続するホストのアドレスやプロキシの設定は Miby::Config で設定します。
9
+ #
10
+ # == 使い方
11
+ # 実行したい処理をブロックで渡します。ブロック内で利用できるメソッドは Net::HTTPと同じです。
12
+ # http_response = HTTP.new.connection {|http|
13
+ # http.get(path)
14
+ # ...
15
+ # }
16
+ class HTTP
17
+ def initialize(config = Config.new)
18
+ @http_address = config.http_address
19
+ @http_port = config.http_port
20
+ @proxy_addr = config.proxy_addr
21
+ @proxy_port = config.proxy_port
22
+ end
23
+ attr_reader :http_address, :http_port, :proxy_addr, :proxy_port
24
+
25
+ # ホスト @http_address に接続しブロックをを実行します。
26
+ # 成功した場合は Net::HTTPResponse のインスタンスを返します。
27
+ def connection(&block)
28
+ puts "connection : #@http_address, #@http_port, #@proxy_addr, #@proxy_port" if $VERBOSE
29
+ Net::HTTP.start(@http_address, @http_port, @proxy_addr, @proxy_port, &block)
30
+ end
31
+ end
32
+ end
33
+
34
+
@@ -0,0 +1,35 @@
1
+ # = miby : Mixi from Ruby
2
+ # Miby(ミビー) は Ruby から mixi を操作するためのライブラリです。
3
+ #
4
+ # == 使い方
5
+ # require 'miby/miby'
6
+ # miby = Miby::Miby.new
7
+ # page = miby.get_page("friend_diary")
8
+ # page.format_items.each do |item|
9
+ # puts "#{item["title"]} #{item["name"]}"
10
+ # end
11
+ #
12
+ $:.push File.dirname(__FILE__) + "/../lib"
13
+ require 'base'
14
+ require 'mixi'
15
+ module Miby
16
+ # Mibyの既定クラス。
17
+ class Miby
18
+ def initialize
19
+ @mixi = Mixi.new
20
+ end
21
+
22
+ # mixiのページを取得します。成功すると Miby::Page が返ります。
23
+ # name:: ページ名
24
+ # values:: URLパラメータの配列
25
+ def get_page(name, *values)
26
+ begin
27
+ @mixi.__send__(name, *values)
28
+ rescue
29
+ $stderr.puts "ページ #{name} 取得中にエラーが発生しました。"
30
+ raise
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,179 @@
1
+ $:.push File.dirname(__FILE__) + "/../lib"
2
+ require 'base'
3
+ require 'user'
4
+ require 'page'
5
+ require 'pagestruct'
6
+
7
+ module Miby
8
+ # mixiとのコネクションを行います。
9
+ # ユーザーの認証、ページデータの取得、日記・コメントの投稿ができます。
10
+ # ページの取得・投稿(get・post系)を行うメソッドの戻り値は Miby::Page のインスタンスです。
11
+ #
12
+ # == 使い方
13
+ # Mibyライブラリを読み込んで Miby::Mixiインスタンスを生成します。
14
+ # require 'miby'
15
+ # mixi = Miby::Mixi.new
16
+ # mixiに登録したメールアドレスとパスワードでログインします。
17
+ # 成功すればture、失敗した場合は例外が発生します。
18
+ # mixi.login("hoge@hogege.com", "hoga")
19
+ #
20
+ # マイミクの最新日記リストページを取得して、整形したデータの各アイテムの内容を表示するには以下のように実行します。
21
+ # ログインしていない場合は自動でログインします。
22
+ # pgae = mixi.friend_diary
23
+ # page.each do |item|
24
+ # puts %<#{item["title"]} #{item["name"]}>
25
+ # end
26
+ #
27
+ # == コマンド
28
+ # 取得したページの素の整形データを取得する。
29
+ # page.format_items
30
+ #
31
+ # 取得したページの整形データを配列に変換する。
32
+ # page.to_a
33
+ #
34
+ # 取得したページの Net::HTTPRequest#response を取得する。
35
+ # page.request
36
+ #
37
+ # == 利用できるページ
38
+ # 利用できるページには パブリックメソッドに定義されているものと、 Miby::PageStruct に定義されているものの2種類があります。
39
+ # パブリックメソッドのページは各メソッドの説明をご覧ください。
40
+ #
41
+ # Miby::PageStructは定義名で呼び出して利用することができます。
42
+ # # id:10 owner_id:1 の日記の本文を取得する
43
+ # mixi.view_diary_diary(10,1)
44
+ # 詳細は Miby::PageStruct を参照してください。
45
+ #
46
+ # == 引数
47
+ # 説明のない引数はmixiに渡すパラメータと同じです。
48
+ #
49
+ # email:: メールアドレス
50
+ # password:: ユーザーのパスワード
51
+ # id, diary_id:: 日記のID
52
+ # owner_id:: ユーザーのID(一部idを使うページがある)
53
+ # diary_title:: 日記タイトル
54
+ # diary_body:: 日記本文
55
+ # comment_body:: コメント本文
56
+ # submit:: hidden文字列(日記の投稿に必要)
57
+ #
58
+ # == 未実装の機能
59
+ # * メッセージ・動画・フォト・ミュージック・コミュニティー全般
60
+ # * 「前の日記へ」「次の日記へ」
61
+ class Mixi
62
+ include Base
63
+
64
+ # 未定義のメソッドはページ取得として実行する。
65
+ def method_missing(name, *values)
66
+ puts "try get page: #{name} (call from method missing)." if $VERBOSE
67
+ get_page(name, values)
68
+ end
69
+
70
+ def initialize
71
+ @user = User.new
72
+ @page_struct = PageStruct.new
73
+ @logined = false
74
+ end
75
+ attr_reader :user
76
+
77
+ #--
78
+ # get >>
79
+ #++
80
+ # 日記の表示
81
+ def view_diary(id, owner_id)
82
+ diary = get_page "view_diary_diary", id, owner_id
83
+ comment = get_page "view_diary_comment" ,id, owner_id
84
+ diary.to_a + comment.to_a
85
+ end
86
+
87
+ # マイミク日記リスト
88
+ # page_count: ページ番号
89
+ def friend_diary(page_count = nil)
90
+ unless page_count.nil? or page_count.empty?
91
+ params = get_next_page_param("new_friend_diary_next_page_param", page_count)
92
+ end
93
+ get_page("new_friend_diary", params)#.format_items
94
+ end
95
+
96
+ #--
97
+ # post >>
98
+ #++
99
+ # 自分の日記を追加します。
100
+ def add_diary(id, owner_id)
101
+ get_page("add_diary", id, owner_id, "main")
102
+ end
103
+
104
+ #--
105
+ # controll >>
106
+ #++
107
+ def logined?
108
+ @logined == true or get_home? ? true : false
109
+ end
110
+
111
+ # email と password を使ってmixiにログインします。
112
+ # 失敗した場合は例外が発生します。
113
+ def login(email = nil, password = nil)
114
+ puts "login ..." if $VERBOSE
115
+ email ||= input_line("email"); password ||= input_line("passowrd")
116
+ page = Page.new("login", { "email" => email, "password" => password, "next_url" => "home.pl"})
117
+
118
+ if page.to_s == "refresh"
119
+ @user.set(page.response.get_fields('Set-Cookie'))
120
+ @logined = true
121
+ return true
122
+ else
123
+ raise "Login refuse. email or password"
124
+ end
125
+ end
126
+ # << controll
127
+
128
+ private
129
+ #
130
+ def get_home?
131
+ return false unless @user.cookie?
132
+ page = Page.new("home_form", nil, @user.cookie)
133
+ page.to_s =~ /login.pl/ ? false : true
134
+ end
135
+
136
+ # name のMiby::Pageオブジェクトを取得する。
137
+ # name:: PageStructに定義された名前
138
+ # values:: URLに渡すパラメータ 配列かハッシュ。配列の場合は Miby::PageStructの定義順に配置する
139
+ def get_page(name, *values)
140
+ puts "get_page: #{name}" if $VERBOSE
141
+ login unless logined?
142
+ values.flatten!
143
+ list = values.first
144
+ list = create_param_value_list(@page_struct.find_by_name(name), values) unless list.kind_of? Hash
145
+ Page.new(name, list, @user.cookie)
146
+ end
147
+
148
+ # structのparamsとparams_optをvaluesからURLに渡すパラメータのリスト(Hash)を生成する。
149
+ # struct : PageStructオブジェクト
150
+ # values : 値の入った配列
151
+ def create_param_value_list(struct, values)
152
+ list = { }
153
+ keys = struct["params"].to_a + struct["params_opt"].to_a
154
+ keys.each_with_index do |key, i|
155
+ list.merge!({ key => values[i] })
156
+ end
157
+ list
158
+ end
159
+
160
+ # 次のページに移動するためのパラメータを取得する
161
+ def get_next_page_param(name, count)
162
+ params = { }
163
+ (count.to_i - 1).times do
164
+ items = get_page(name, params)
165
+ raise "データがありません" if items.empty?
166
+ # ページ毎の定義
167
+ case name
168
+ when "new_friend_diary_next_page_param"
169
+ items.each do |item|
170
+ params = item if item.has_key?("direction") and item["direction"] == "next"
171
+ end
172
+ else
173
+ params = items.first
174
+ end
175
+ end
176
+ params
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,183 @@
1
+ $:.push File.dirname(__FILE__) + "/../lib"
2
+ require 'hpricot'
3
+ require 'base'
4
+ require 'http'
5
+ require 'pagestruct'
6
+
7
+ module Miby
8
+ # ページオブジェクトを作成します。
9
+ # ページオブジェクトは1つURLで表現できるページを、ルール(ページ整形情報)に従って整形データを作成したものです。
10
+ #
11
+ # == 生成
12
+ # Miby::Pagestructで定義されている定義名(name)とURLパラメータを使って生成します。
13
+ # 定義されていないページを取得しようとすると例外が発生します。
14
+ #
15
+ # マイミクの最新日記リストのMiby::Pageを得るには以下のようにします。
16
+ # Miby::Page.new("friend_diary", nil, Miby::User.new.cookie)
17
+ # URLにパラメータを渡す場合は以下のようにします。
18
+ # Miby::Page.new("view_diary", {"id" => "10000", "owner_id" => "10000" }, Miby::User.new.cookie)
19
+ #
20
+ # == アクセス
21
+ # * 生成したPageオブジェクトの整形されたデータにはパブリックメソッド(to_s, to_a, each, empty?)を使ってアクセスできます。
22
+ # * 取得したページ情報に直接アクセスするには Page#response を使います(Net::HTTPResponseを返します)。
23
+ class Page
24
+ include Enumerable
25
+ include Base
26
+
27
+ # name:: Miby::PageStructに定義されている名前
28
+ # params:: URLに渡すパラメータのハッシュ。Miby::PageStructのparams配列で定義されているものを指定する。
29
+ # cookie:: headerに渡すcookieの値。Miby::User.cookie で取得できる。
30
+ def initialize(name, params = nil, cookie = nil)
31
+ params ||= { }
32
+ @name, @params, @cookie = name, params, cookie
33
+
34
+ @struct = PageStruct.find(name)
35
+ @response = get_http_response(name, params, cookie, @struct)
36
+ @format_items = response_to_format_items(@response, @struct)
37
+ @option_items = response_to_option_items(@response, @struct)
38
+ end
39
+ attr_reader :name, :params, :cookie
40
+ # Page::Structから得たページ整形情報。Miby::Pageはこの情報を元にHTMLファイルをアイテム毎にフォーマットします。
41
+ attr_reader :struct
42
+ # 取得したページの Net::HTTPResponse を返します。
43
+ attr_reader :response
44
+ # 整形されたデータ。通常は各アイテム情報を入れた配列になります。
45
+ # 例:friend_diary(マイミクの最新日記リスト)を取得した場合は [{"name" => "名前", "title" => "今日の日記", "body" => "本文"} ...] のようになります。
46
+ # 実際どのようなデータが代入されるかは Miby::PageStruct の variables配列 によります。
47
+ # Miby::Pageのパブリックメソッドはすべてこの変数に対して動作します。
48
+ attr_reader :format_items
49
+ # 整形されたオプションデータ。
50
+ attr_reader :option_items
51
+
52
+
53
+ def to_s
54
+ @format_items.to_s
55
+ end
56
+
57
+ def to_a
58
+ @format_items.to_a
59
+ end
60
+
61
+ def empty?
62
+ return true if @format_items.nil?
63
+ @format_items.size == 0 ? true : false
64
+ end
65
+
66
+ def first
67
+ @format_items.first
68
+ end
69
+
70
+ def each
71
+ @format_items.each { |item| yield item }
72
+ end
73
+
74
+ private
75
+ def get_http_response(name, params, cookie, struct)
76
+ path = File.join("/", struct["path"])
77
+ cookie = { 'Cookie' => cookie } unless cookie.nil?
78
+ uri_param = create_uri_param(struct, params)
79
+ puts "get_http_response: method:#{struct["method"]}, path: #{path}, cookie:#{cookie}" if $VERBOSE
80
+ p params if $VERBOSE
81
+
82
+ HTTP.new.connection do |http|
83
+ case struct["method"]
84
+ when 'post'
85
+ post_key = get_post_key(http.post(path, uri_param, cookie).body) # mixiの投稿は事前に post_id を取得して post_id を使って投稿する。
86
+ raise "not response found post_key" if post_key.nil?
87
+ uri_param.gsub!("&submit=main", "") # 日記投稿のsubmitを削除
88
+ http.post(path, uri_param + "&post_key=#{post_key.to_s.uri_encode}&submit=confirm", cookie) # 投稿にはpost_keyとsubmitが必要
89
+ else #get
90
+ http.get(path + uri_param, cookie)
91
+ end
92
+ end
93
+ end
94
+
95
+ # URIのパラメータ部分をstructとvaluesから生成します。
96
+ # struct:: ページ整形情報オブジェクト
97
+ # values:: パラメータとその値(ハッシュ)
98
+ def create_uri_param(struct, values = { })
99
+ param = ""
100
+ unless struct["params"].nil?
101
+ struct["params"].each do |key|
102
+ raise "No set require parameter : #{key}" if values[key].nil?
103
+ end
104
+ end
105
+ values.each do |key,val|
106
+ param << "&#{key.to_s.uri_encode}=#{val.to_s.uri_encode}"
107
+ end
108
+ struct["method"] == "post" ? param.sub(/^&/,"") : "?" + param
109
+ end
110
+
111
+ #--
112
+ # <<< parse
113
+ #++
114
+ # HTMLを整形し、配列を返します。
115
+ # response:: Net::HTTPResponse のインスタンス
116
+ def response_to_format_items(response, struct)
117
+ raise "response failed. path:#{struct["path"]}, code:#{response.code}" unless response.code == "200"
118
+ elements = format_from_cssselector(response.body, struct["css_selectors"])
119
+ matchs = format_from_regexp(elements, struct["regexp"])
120
+ items = set_variables(matchs, struct["variables"])
121
+ end
122
+ def response_to_option_items(response, struct)
123
+ return false unless response.code == "200"
124
+ elements = format_from_cssselector(response.body, struct["css_selectors_opt"])
125
+ matchs = format_from_regexp(elements, struct["regexp_opt"])
126
+ items = set_variables(matchs, struct["variables_opt"])
127
+ end
128
+
129
+ # selectors:: CSS selector の配列
130
+ def format_from_cssselector(html, selectors = nil)
131
+ return html.to_a if selectors.nil?
132
+ elements = Hpricot(html.to_s.utf8)
133
+ selectors.each { |sel| elements = elements.search(sel) }
134
+ elements
135
+ end
136
+
137
+ # regexp:: 正規表現
138
+ def format_from_regexp(str, regexp = nil)
139
+ return [] if regexp.nil?
140
+ str.to_s.utf8.gsub("\n", "").gsub("\t","").scan(Regexp.new(regexp))
141
+ end
142
+
143
+ #--
144
+ # 配列の配列を配列のハッシュにする
145
+ #++
146
+ def set_variables(matchs, variables)
147
+ return matchs if variables.nil?
148
+ matchs.map do |value|
149
+ data_tmp = Hash.new
150
+ variables.each_with_index do |variable, i|
151
+ value[i] = body2str(value[i]) if variable == "body"
152
+ value[i] = date2time(value[i]) if variable == "date"
153
+ data_tmp.merge!({ variable => value[i] })
154
+ end
155
+ data_tmp
156
+ end
157
+ end
158
+ #--
159
+ # parse >>>
160
+ #++
161
+
162
+ # post_key の値を取得します。
163
+ def get_post_key(str)
164
+ str.to_s.gsub("\n","").match(/.*"post_key".*?value="(.*?)"/).to_a.values_at(1)
165
+ end
166
+
167
+
168
+ # mixiの時刻文字列をTimeオブジェクトに変換します。
169
+ def date2time(str)
170
+ str.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\D*(\d{1,2}):(\d{1,2})/)
171
+ time = Time.mktime($1, $2, $3, $4, $5)
172
+ end
173
+
174
+ # HTMLの本文をプレーンテキストに変換します。
175
+ def body2str(body = "")
176
+ str = body.dup.gsub("\n", "")
177
+ str.gsub!("<br />", "\n")
178
+ str.gsub!(/<.*?>/, "") # 不要なタグは全削除
179
+ str
180
+ end
181
+ end
182
+ end
183
+