pixiv 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.
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +37 -0
- data/Rakefile +1 -0
- data/lib/pixiv.rb +17 -0
- data/lib/pixiv/bookmark_list.rb +60 -0
- data/lib/pixiv/client.rb +157 -0
- data/lib/pixiv/error.rb +6 -0
- data/lib/pixiv/illust.rb +57 -0
- data/lib/pixiv/member.rb +22 -0
- data/lib/pixiv/page.rb +91 -0
- data/lib/pixiv/page_collection.rb +81 -0
- data/lib/pixiv/version.rb +3 -0
- data/pixiv.gemspec +21 -0
- metadata +77 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tomoki Aonuma
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Pixiv gem
|
2
|
+
|
3
|
+
A client library for [pixiv](http://www.pixiv.net/)
|
4
|
+
|
5
|
+
## Important Note
|
6
|
+
|
7
|
+
The pixiv Guidelines [[en][Guidelines.en], [ja][Guidelines.ja]] prohibit to
|
8
|
+
crawl the pixiv service. Do not abuse this library or you may be banned!
|
9
|
+
|
10
|
+
[Guidelines.en]: http://www.pixiv.net/guideline.php?lang=en
|
11
|
+
[Guidelines.ja]: http://www.pixiv.net/guideline.php?lang=ja
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'pixiv', github: 'uasi/pixiv'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
See [a sample script](https://gist.github.com/4362297)
|
26
|
+
|
27
|
+
## Documentation
|
28
|
+
|
29
|
+
[Documentation for uasi/pixiv on rubydoc.info](http://rubydoc.info/github/uasi/pixiv/master/frames)
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
37
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/pixiv.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'mechanize'
|
2
|
+
require 'pixiv/error'
|
3
|
+
require 'pixiv/client'
|
4
|
+
require 'pixiv/page'
|
5
|
+
require 'pixiv/illust'
|
6
|
+
require 'pixiv/member'
|
7
|
+
require 'pixiv/page_collection'
|
8
|
+
require 'pixiv/bookmark_list'
|
9
|
+
|
10
|
+
module Pixiv
|
11
|
+
ROOT_URL = 'http://www.pixiv.net'
|
12
|
+
|
13
|
+
# Delegates to {Pixiv::Client#initialize}
|
14
|
+
def self.new(*args, &block)
|
15
|
+
Pixiv::Client.new(*args, &block)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Pixiv
|
2
|
+
class BookmarkList < Page
|
3
|
+
include PageCollection
|
4
|
+
|
5
|
+
# Returns the URL for given +member_id+ and +page_num+
|
6
|
+
# @param [Integer] member_id
|
7
|
+
# @param [Integer] page_num
|
8
|
+
# @return [String]
|
9
|
+
def self.url(member_id, page_num = 1)
|
10
|
+
"#{ROOT_URL}/bookmark.php?id=#{member_id}&rest=show&p=#{page_num}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Integer]
|
14
|
+
lazy_attr_reader(:page_num) { at!('li.pages-current').inner_text.to_i }
|
15
|
+
# @return [Boolean]
|
16
|
+
lazy_attr_reader(:last?) { at!('li.pages-current').next_element.inner_text.to_i == 0 }
|
17
|
+
# @return [Integer]
|
18
|
+
lazy_attr_reader(:member_id) { doc.body.match(/pixiv\.context\.userId = '(\d+)'/).to_a[1].to_i }
|
19
|
+
# @return [Array<Integer>]
|
20
|
+
lazy_attr_reader(:illust_ids) { search!('li[id^="li_"] a[href^="member_illust.php?mode=medium"]').map {|n| n.attr('href').match(/illust_id=(\d+)$/).to_a[1].to_i } }
|
21
|
+
# @return [Array<Hash{Symbol=>Object}>]
|
22
|
+
lazy_attr_reader(:illust_hashes) {
|
23
|
+
search!('li[id^="li_"]').map {|node| illust_hash_from_bookmark_item(node) }.compact
|
24
|
+
}
|
25
|
+
|
26
|
+
alias page_hashes illust_hashes
|
27
|
+
|
28
|
+
# @return [String]
|
29
|
+
def url; self.class.url(member_id, page_num) end
|
30
|
+
# @return [Boolean]
|
31
|
+
def first?; page_num == 1 end
|
32
|
+
# @return [String]
|
33
|
+
def next_url; last? ? nil : self.class.url(member_id, page_num + 1) end
|
34
|
+
# @return [String]
|
35
|
+
def prev_url; first? ? nil : self.class.url(member_id, page_num - 1) end
|
36
|
+
# @return [Class<Pixiv::Page>]
|
37
|
+
def page_class; Illust end
|
38
|
+
# @return [Array<String>]
|
39
|
+
def page_urls; illust_ids.map {|illust_id| Illust.url(illust_id) } end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# @param [Nokogiri::HTML::Node] node
|
44
|
+
# @return [Hash{Symbol=>Object}] illust_hash
|
45
|
+
def illust_hash_from_bookmark_item(node)
|
46
|
+
return nil if node.at('img[src*="limit_unknown_s.png"]')
|
47
|
+
member_node = node.at('a[href^="member_illust.php?id="]')
|
48
|
+
illust_node = node.at('a')
|
49
|
+
illust_id = illust_node['href'].match(/illust_id=(\d+)/).to_a[1].to_i
|
50
|
+
{
|
51
|
+
url: Illust.url(illust_id),
|
52
|
+
illust_id: illust_id,
|
53
|
+
title: illust_node.inner_text,
|
54
|
+
member_id: member_node['href'].match(/\?id=(\d+)/).to_a[1].to_i,
|
55
|
+
member_name: member_node.inner_text,
|
56
|
+
small_image_url: illust_node.at('img').attr('src'),
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/pixiv/client.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
module Pixiv
|
2
|
+
class Client
|
3
|
+
# A new agent
|
4
|
+
# @return [Mechanize::HTTP::Agent]
|
5
|
+
def self.new_agent
|
6
|
+
agent = Mechanize.new
|
7
|
+
agent.pluggable_parser['image/gif'] = Mechanize::Download
|
8
|
+
agent.pluggable_parser['image/jpeg'] = Mechanize::Download
|
9
|
+
agent.pluggable_parser['image/png'] = Mechanize::Download
|
10
|
+
agent
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Mechanize::HTTP::Agent]
|
14
|
+
attr_reader :agent
|
15
|
+
# @return [Integer]
|
16
|
+
attr_reader :member_id
|
17
|
+
|
18
|
+
# A new instance of Client, logged in with the given credentials
|
19
|
+
# @overload initialize(pixiv_id, password)
|
20
|
+
# @param [String] pixiv_id
|
21
|
+
# @param [String] password
|
22
|
+
# @yield [agent] (optional) gives a chance to customize the +agent+ before logging in
|
23
|
+
# @overload initialize(agent)
|
24
|
+
# @param [Mechanize::HTTP::Agent] agent
|
25
|
+
# @return [Pixiv::Client]
|
26
|
+
def initialize(*args)
|
27
|
+
if args.size < 2
|
28
|
+
@agent = args.first || self.class.new_agent
|
29
|
+
yield @agent if block_given?
|
30
|
+
ensure_logged_in
|
31
|
+
else
|
32
|
+
pixiv_id, password = *args
|
33
|
+
@agent = self.class.new_agent
|
34
|
+
yield @agent if block_given?
|
35
|
+
login(pixiv_id, password)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Log in to Pixiv
|
40
|
+
# @param [String] pixiv_id
|
41
|
+
# @param [String] password
|
42
|
+
def login(pixiv_id, password)
|
43
|
+
form = agent.get("#{ROOT_URL}/index.php").forms_with(:class => 'login-form').first
|
44
|
+
raise Error::LoginFailed, 'login form is not available' unless form
|
45
|
+
form.pixiv_id = pixiv_id
|
46
|
+
form.pass = password
|
47
|
+
doc = agent.submit(form)
|
48
|
+
raise Error::LoginFailed unless doc.body =~ /logout/
|
49
|
+
@member_id = member_id_from_mypage(doc)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Integer] illust_id
|
53
|
+
# @return [Pixiv::Illust]
|
54
|
+
def illust(illust_id)
|
55
|
+
attrs = {illust_id: illust_id}
|
56
|
+
Illust.lazy_new(attrs) { agent.get(Illust.url(illust_id)) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [Integer] member_id
|
60
|
+
# @return [Pixiv::Member]
|
61
|
+
def member(member_id = member_id)
|
62
|
+
attrs = {member_id: member_id}
|
63
|
+
Member.lazy_new(attrs) { agent.get(Member.url(member_id)) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param [Pixiv::Member, Integer] member_or_member_id
|
67
|
+
# @param [Integer] page_num
|
68
|
+
def bookmark_list(member_or_member_id = member_id, page_num = 1)
|
69
|
+
x = member_or_member_id
|
70
|
+
member_id = x.is_a?(Member) ? x.member_id : x
|
71
|
+
attrs = {member_id: member_id, page_num: page_num}
|
72
|
+
BookmarkList.lazy_new(attrs) { agent.get(BookmarkList.url(member_id, page_num)) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param [Pixiv::Member, Integer] member_or_member_id
|
76
|
+
# @param [Integer] page_num
|
77
|
+
def bookmarks(member_or_member_id = member_id, page_num = 1)
|
78
|
+
list = bookmark_list(member_or_member_id, page_num)
|
79
|
+
PageCollection::Enumerator.new(self, list)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Downloads the image to +io_or_filename+
|
83
|
+
# @param [Pixiv::Illust] illust
|
84
|
+
# @param [#write, String, Array<String, Symbol, #call>] io_or_filename io or filename or pattern (see {#filename_from_pattern})
|
85
|
+
# @param [Symbol] size image size (+:small+, +:medium+, or +:original+)
|
86
|
+
def download_illust(illust, io_or_filename, size = :original)
|
87
|
+
size = {:s => :small, :m => :medium, :o => :original}[size] || size
|
88
|
+
url = illust.__send__("#{size}_image_url")
|
89
|
+
referer = case size
|
90
|
+
when :small then nil
|
91
|
+
when :medium then illust.url
|
92
|
+
when :original then illust.original_image_referer
|
93
|
+
else raise ArgumentError, "unknown size `#{size}`"
|
94
|
+
end
|
95
|
+
save_to = io_or_filename
|
96
|
+
if save_to.is_a?(Array)
|
97
|
+
save_to = filename_from_pattern(save_to, illust, url)
|
98
|
+
end
|
99
|
+
FileUtils.mkdir_p(File.dirname(save_to)) unless save_to.respond_to?(:write)
|
100
|
+
@agent.download(url, save_to, [], referer)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Downloads the images to +pattern+
|
104
|
+
# @param [Pixiv::Illust] illust the manga to download
|
105
|
+
# @param [Array<String, Symbol, #call>] pattern pattern (see {#filename_from_pattern})
|
106
|
+
# @note +illust#manga?+ must be +true+
|
107
|
+
def download_manga(illust, pattern)
|
108
|
+
illust.original_image_urls.each do |url|
|
109
|
+
filename = filename_from_pattern(pattern, illust, url)
|
110
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
111
|
+
@agent.download(url, filename, [], illust.original_image_referer)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
def ensure_logged_in
|
118
|
+
doc = agent.get("#{ROOT_URL}/mypage.php")
|
119
|
+
raise Error::LoginFailed unless doc.body =~ /logout/
|
120
|
+
@member_id = member_id_from_mypage(doc)
|
121
|
+
end
|
122
|
+
|
123
|
+
def member_id_from_mypage(doc)
|
124
|
+
doc.at('.profile_area a').attr('href').match(/(\d+)$/).to_a[1].to_i
|
125
|
+
end
|
126
|
+
|
127
|
+
# Generate filename from +pattern+ in context of +illust+ and +url+
|
128
|
+
#
|
129
|
+
# @param [Array<String, Symbol, #call>] pattern
|
130
|
+
# @param [Pixiv::Illust] illust
|
131
|
+
# @param [String] url
|
132
|
+
# @return [String] filename
|
133
|
+
#
|
134
|
+
# The +pattern+ is an array of string, symbol, or object that responds to +#call+.
|
135
|
+
# Each component of the +pattern+ is replaced by the following rules and then
|
136
|
+
# the +pattern+ is concatenated as the returning +filename+.
|
137
|
+
#
|
138
|
+
# * +:image_name+ in the +pattern+ is replaced with the base name of the +url+
|
139
|
+
# * Any other symbol is replaced with the value of +illust.__send__(the_symbol)+
|
140
|
+
# * +#call+-able object is replaced with the value of +the_object.call(illust)+
|
141
|
+
# * String is left as-is
|
142
|
+
def filename_from_pattern(pattern, illust, url)
|
143
|
+
pattern.map {|i|
|
144
|
+
if i == :image_name
|
145
|
+
name = File.basename(url)
|
146
|
+
if name =~ /\.(\w+)\?\d+$/
|
147
|
+
name += '.' + $1
|
148
|
+
end
|
149
|
+
name
|
150
|
+
elsif i.is_a?(Symbol) then illust.__send__(i)
|
151
|
+
elsif i.respond_to?(:call) then i.call(illust)
|
152
|
+
else i
|
153
|
+
end
|
154
|
+
}.join('')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/lib/pixiv/error.rb
ADDED
data/lib/pixiv/illust.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Pixiv
|
2
|
+
class Illust < Page
|
3
|
+
# Returns the URL for given +illust_id+
|
4
|
+
# @param [Integer] illust_id
|
5
|
+
# @return [String]
|
6
|
+
def self.url(illust_id)
|
7
|
+
"#{ROOT_URL}/member_illust.php?mode=medium&illust_id=#{illust_id}"
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
lazy_attr_reader(:small_image_url) { at!('meta[property="og:image"]').attr('content') }
|
12
|
+
# @return [String]
|
13
|
+
lazy_attr_reader(:medium_image_url) { image_url_components.join('_m') }
|
14
|
+
# @return [String]
|
15
|
+
lazy_attr_reader(:original_image_url) { illust? && image_url_components.join('') }
|
16
|
+
# @return [Array<String>]
|
17
|
+
lazy_attr_reader(:original_image_urls) {
|
18
|
+
illust? ? [original_image_url]
|
19
|
+
: (0...num_pages).map {|n| image_url_components.join("_p#{n}") }
|
20
|
+
}
|
21
|
+
# @return [String]
|
22
|
+
lazy_attr_reader(:original_image_referer) { ROOT_URL + '/' + at!('//div[@class="works_display"]/a').attr('href') }
|
23
|
+
# @return [Integer]
|
24
|
+
lazy_attr_reader(:illust_id) { at!('#rpc_i_id').attr('title').to_i }
|
25
|
+
# @return [Integer]
|
26
|
+
lazy_attr_reader(:member_id) { at!('#rpc_u_id').attr('title').to_i }
|
27
|
+
# @return [String]
|
28
|
+
lazy_attr_reader(:member_name) { raise NotImplementError.new('XXX') }
|
29
|
+
# @return [String]
|
30
|
+
lazy_attr_reader(:title) { raise NotImplementError.new('XXX') }
|
31
|
+
# @return [Integer]
|
32
|
+
lazy_attr_reader(:num_pages) {
|
33
|
+
n = at!('//ul[@class="meta"]/li[2]').inner_text.match(/(\d+)P$/).to_a[1]
|
34
|
+
n && n.to_i
|
35
|
+
}
|
36
|
+
|
37
|
+
alias id illust_id
|
38
|
+
alias original_image_referrer original_image_referer # referrer vs. referer
|
39
|
+
|
40
|
+
# @return [String]
|
41
|
+
def url; self.class.url(illust_id) end
|
42
|
+
# @return [Boolean]
|
43
|
+
def illust?; !manga? end
|
44
|
+
# @return [Boolean]
|
45
|
+
def manga?; !!num_pages end
|
46
|
+
# @return [String]
|
47
|
+
def medium_image_url; image_url_components.join('_m') end
|
48
|
+
# @return [String]
|
49
|
+
def original_image_url; image_url_components.join('') end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def image_url_components
|
54
|
+
@image_url_components ||= small_image_url.match(%r{^(.+)_s(\.\w+(?:\?\d+)?)$}).to_a[1, 3]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/pixiv/member.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Pixiv
|
2
|
+
class Member < Page
|
3
|
+
# Returns the URL for given +member_id+
|
4
|
+
# @param [Integer] member_id
|
5
|
+
# @return [String]
|
6
|
+
def self.url(member_id)
|
7
|
+
"#{ROOT_URL}/member.php?id=#{member_id}"
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
lazy_attr_reader(:name) { at!('.profile_area h2').inner_text }
|
12
|
+
# @return [Integer]
|
13
|
+
lazy_attr_reader(:member_id) { at!('input[name="user_id"]').attr('value').to_i }
|
14
|
+
# return [Integer]
|
15
|
+
lazy_attr_reader(:pixiv_id) { at!('.profile_area img').attr('src').match(%r{/profile/([a-z_-]+)/}).to_a[1] }
|
16
|
+
|
17
|
+
alias id member_id
|
18
|
+
|
19
|
+
# @return [String]
|
20
|
+
def url; self.class.url(member_id) end
|
21
|
+
end
|
22
|
+
end
|
data/lib/pixiv/page.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
module Pixiv
|
2
|
+
class Page
|
3
|
+
# A new Page
|
4
|
+
# @param [Hash{Symbol=>Object}] attrs
|
5
|
+
# @yieldreturn [Nokogiri::HTML::Document]
|
6
|
+
def self.lazy_new(attrs = {}, &doc_creator)
|
7
|
+
self.new(doc_creator, attrs)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @overload initialize(doc, attrs = {})
|
11
|
+
# @param [Nokogiri::HTTP::Document] doc
|
12
|
+
# @param [Hash{Symbol=>Object}] attrs
|
13
|
+
# @overload initialize(doc_creator, attrs = {})
|
14
|
+
# @param [#call] doc_creator
|
15
|
+
# @param [Hash{Symbol=>Object}] attrs
|
16
|
+
def initialize(doc_or_doc_creator, attrs = {})
|
17
|
+
x = doc_or_doc_creator
|
18
|
+
if x.respond_to?(:call)
|
19
|
+
@doc_creator = x
|
20
|
+
else
|
21
|
+
@doc = x
|
22
|
+
end
|
23
|
+
set_attrs!(attrs)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Nokogiri::HTTP::Document]
|
27
|
+
def doc
|
28
|
+
@doc ||= begin
|
29
|
+
doc = @doc_creator.call
|
30
|
+
@doc_creator = nil
|
31
|
+
doc
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Fetch +#doc+ and lazy attrs
|
36
|
+
# @return [self]
|
37
|
+
def force
|
38
|
+
doc
|
39
|
+
(@@lazy_attr_readers || []).each do |reader|
|
40
|
+
__send__(reader) if respond_to?(reader)
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Defines lazy attribute reader
|
48
|
+
# @!macro attach lazy_attr_reader
|
49
|
+
# @!attribute [r] $1
|
50
|
+
# Lazily returns $1
|
51
|
+
def self.lazy_attr_reader(name, &reader)
|
52
|
+
ivar = :"@#{name.to_s.sub(/\?$/, '_q_')}"
|
53
|
+
(@@lazy_attr_readers ||= []) << ivar
|
54
|
+
define_method(name) do
|
55
|
+
if instance_variable_defined?(ivar)
|
56
|
+
instance_variable_get(ivar)
|
57
|
+
else
|
58
|
+
instance_variable_set(ivar, instance_eval(&reader))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set attribute values
|
64
|
+
#
|
65
|
+
# @param [Hash{Symbol=>Object}] attrs
|
66
|
+
#
|
67
|
+
# If +#set_attr!+ sets a value to an attribute,
|
68
|
+
# the lazy attr of the same name gets to return that value
|
69
|
+
# so its block will never called.
|
70
|
+
def set_attrs!(attrs)
|
71
|
+
attrs.each do |name, value|
|
72
|
+
ivar = :"@#{name.to_s.sub(/\?$/, '_q_')}"
|
73
|
+
instance_variable_set(ivar, value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# +node.at(path)+ or raise error
|
78
|
+
# @param [String] path XPath or CSS path
|
79
|
+
# @return [Nokogiri::HTML::Node]
|
80
|
+
def at!(path, node = doc)
|
81
|
+
node.at(path) || Error::NodeNotFound.new("node for `#{path}` not found").raise
|
82
|
+
end
|
83
|
+
|
84
|
+
# +node.search(path) or raise error
|
85
|
+
# @param [String] path XPath or CSS path
|
86
|
+
# @return [Array<Nokogiri::HTML::Node>]
|
87
|
+
def search!(path, node = doc)
|
88
|
+
node.search(path) || Error::NodeNotFound.new("node for `#{path}` not found").raise
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Pixiv
|
2
|
+
module PageCollection
|
3
|
+
def first?
|
4
|
+
raise NotImplementError
|
5
|
+
end
|
6
|
+
|
7
|
+
def last?
|
8
|
+
raise NotImplementError
|
9
|
+
end
|
10
|
+
|
11
|
+
def next_url
|
12
|
+
raise NotImplementError
|
13
|
+
end
|
14
|
+
|
15
|
+
def prev_url
|
16
|
+
raise NotImplementError
|
17
|
+
end
|
18
|
+
|
19
|
+
def page_class
|
20
|
+
raise NotImplementError
|
21
|
+
end
|
22
|
+
|
23
|
+
def page_urls
|
24
|
+
raise NotImplementError
|
25
|
+
end
|
26
|
+
|
27
|
+
def page_hash
|
28
|
+
page_urls.map {|url| {url: url} }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PageCollection::Enumerator
|
33
|
+
include Enumerable
|
34
|
+
|
35
|
+
def initialize(client, collection)
|
36
|
+
@client = client
|
37
|
+
@collection = collection
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_page
|
41
|
+
each_collection do |collection|
|
42
|
+
pages_from_collection(collection).each do |page|
|
43
|
+
yield page
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
alias each each_page
|
49
|
+
|
50
|
+
def each_slice(n = nil)
|
51
|
+
if n
|
52
|
+
super
|
53
|
+
else
|
54
|
+
if block_given?
|
55
|
+
each_collection do |collection|
|
56
|
+
yield pages_from_collection(collection)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
::Enumerator.new {|y| each_slice {|slice| y << slice } }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def pages_from_collection(collection)
|
67
|
+
collection.page_hashes.map {|attrs|
|
68
|
+
url = attrs.delete(:url)
|
69
|
+
collection.page_class.lazy_new(attrs) { @client.agent.get(url) }
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_collection(collection = @collection)
|
74
|
+
loop do
|
75
|
+
yield collection
|
76
|
+
break unless collection.next_url
|
77
|
+
collection = collection.class.new(@client.agent.get(collection.next_url))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/pixiv.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pixiv/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pixiv"
|
8
|
+
gem.version = Pixiv::VERSION
|
9
|
+
gem.authors = ["Tomoki Aonuma"]
|
10
|
+
gem.email = ["uasi@uasi.jp"]
|
11
|
+
gem.description = %q{A client library for Pixiv}
|
12
|
+
gem.summary = %q{A client library for Pixiv}
|
13
|
+
gem.homepage = "https://github.com/uasi/pixiv"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'mechanize', '~> 2.0'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pixiv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tomoki Aonuma
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mechanize
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
description: A client library for Pixiv
|
31
|
+
email:
|
32
|
+
- uasi@uasi.jp
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- lib/pixiv.rb
|
43
|
+
- lib/pixiv/bookmark_list.rb
|
44
|
+
- lib/pixiv/client.rb
|
45
|
+
- lib/pixiv/error.rb
|
46
|
+
- lib/pixiv/illust.rb
|
47
|
+
- lib/pixiv/member.rb
|
48
|
+
- lib/pixiv/page.rb
|
49
|
+
- lib/pixiv/page_collection.rb
|
50
|
+
- lib/pixiv/version.rb
|
51
|
+
- pixiv.gemspec
|
52
|
+
homepage: https://github.com/uasi/pixiv
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: A client library for Pixiv
|
76
|
+
test_files: []
|
77
|
+
has_rdoc:
|