booru 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 +4 -0
- data/LICENSE +22 -0
- data/README.md +47 -0
- data/Rakefile +33 -0
- data/bin/booru.rb +36 -0
- data/booru.gemspec +23 -0
- data/fixtures/konachan/post_index.json +1 -0
- data/fixtures/yandere/post_index.json +1 -0
- data/lib/booru.rb +44 -0
- data/lib/booru/api.rb +27 -0
- data/lib/booru/api/artist.rb +74 -0
- data/lib/booru/api/comment.rb +48 -0
- data/lib/booru/api/favorite.rb +21 -0
- data/lib/booru/api/forum.rb +23 -0
- data/lib/booru/api/note.rb +82 -0
- data/lib/booru/api/pool.rb +101 -0
- data/lib/booru/api/post.rb +153 -0
- data/lib/booru/api/tag.rb +57 -0
- data/lib/booru/api/user.rb +21 -0
- data/lib/booru/api/wiki.rb +136 -0
- data/lib/booru/client.rb +33 -0
- data/lib/booru/client/behoimi.rb +29 -0
- data/lib/booru/client/danbooru.rb +25 -0
- data/lib/booru/client/konachan.rb +25 -0
- data/lib/booru/client/yandere.rb +25 -0
- data/lib/booru/downloader.rb +95 -0
- data/lib/booru/error.rb +32 -0
- data/lib/booru/request.rb +55 -0
- data/lib/booru/utils.rb +80 -0
- data/lib/booru/version.rb +4 -0
- metadata +140 -0
data/lib/booru/client.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Client
|
4
|
+
include Error
|
5
|
+
include Request
|
6
|
+
|
7
|
+
include API
|
8
|
+
include API::Artist
|
9
|
+
include API::Comment
|
10
|
+
include API::Favorite
|
11
|
+
include API::Note
|
12
|
+
include API::Pool
|
13
|
+
include API::Post
|
14
|
+
include API::Tag
|
15
|
+
include API::User
|
16
|
+
include API::Wiki
|
17
|
+
|
18
|
+
def initialize(user=nil, pass=nil, format='json')
|
19
|
+
@user = user
|
20
|
+
@pass = pass ? hash_password(pass) : nil
|
21
|
+
@format = format.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash_password(pass)
|
25
|
+
Digest::SHA1.hexdigest(pass)
|
26
|
+
end
|
27
|
+
|
28
|
+
def url_file_segment(url, md5)
|
29
|
+
CGI.unescape(url)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Behoimi < Client
|
4
|
+
attr_accessor :host, :referer
|
5
|
+
|
6
|
+
def initialize(user = nil, pass = nil)
|
7
|
+
@host = "http://behoimi.org"
|
8
|
+
@referer = "http://behoimi.org/post/show"
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_password(pass)
|
13
|
+
salt = 'meganekko-heaven'
|
14
|
+
salted_pass = "#{salt}--#{pass}--"
|
15
|
+
Digest::SHA1.hexdigest(salted_pass)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def url_file_segment(url, md5)
|
20
|
+
strings = ["http://behoimi.org/"]
|
21
|
+
strings.each do |str|
|
22
|
+
url = url.gsub(str, "")
|
23
|
+
end
|
24
|
+
url = url.gsub(/data\/.{2}\/.{2}\//,"")
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Danbooru < Client
|
4
|
+
attr_accessor :host, :referer
|
5
|
+
|
6
|
+
def initialize(user = nil, pass = nil)
|
7
|
+
@host = "http://danbooru.donmai.us"
|
8
|
+
@referer = "http://danbooru.donmai.us"
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_password(pass)
|
13
|
+
salt = 'choujin-steiner'
|
14
|
+
salted_pass = "#{salt}--#{pass}--"
|
15
|
+
Digest::SHA1.hexdigest(salted_pass)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def url_file_segment(url, md5)
|
20
|
+
url = url.gsub(/http:\/\/.+\/.+\//, "")
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Konachan < Client
|
4
|
+
attr_accessor :host, :referer
|
5
|
+
|
6
|
+
def initialize(user = nil, pass = nil)
|
7
|
+
@host = "http://konachan.com"
|
8
|
+
@referer = "http://konachan.com"
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_password(pass)
|
13
|
+
salt = 'So-I-Heard-You-Like-Mupkids-?'
|
14
|
+
salted_pass = "#{salt}--#{pass}--"
|
15
|
+
Digest::SHA1.hexdigest(salted_pass)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def url_file_segment(url, md5)
|
20
|
+
url = url.gsub(/http:\/\/.+\/image\/#{md5}\//, "")
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Yandere < Client
|
4
|
+
attr_accessor :host, :referer
|
5
|
+
|
6
|
+
def initialize(user = nil, pass = nil)
|
7
|
+
@host = "https://yande.re"
|
8
|
+
@referer = "https://yande.re"
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_password(pass)
|
13
|
+
salt = 'choujin-steiner'
|
14
|
+
salted_pass = "#{salt}--#{pass}--"
|
15
|
+
Digest::SHA1.hexdigest(salted_pass)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def url_file_segment(url, md5)
|
20
|
+
url = url.gsub(/https:\/\/.+\/image\/#{md5}\//, "")
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
class Downloader
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
board = options[:board].downcase.to_sym
|
8
|
+
user = options[:user]
|
9
|
+
pass = options[:pass]
|
10
|
+
limit = options[:max]
|
11
|
+
@tags = sanitize(options[:tags])
|
12
|
+
@pool = sanitize(options[:pool])
|
13
|
+
@store = options[:output]
|
14
|
+
@user_agent = 'Mozilla/5.0'
|
15
|
+
|
16
|
+
@client = case board
|
17
|
+
when :konachan
|
18
|
+
Konachan.new(user,pass)
|
19
|
+
when :behoimi
|
20
|
+
Behoimi.new(user, pass)
|
21
|
+
when :yandere
|
22
|
+
Yandere.new(user, pass)
|
23
|
+
when :danbooru
|
24
|
+
Danbooru.new(user, pass)
|
25
|
+
else
|
26
|
+
puts "Image board unsupported. Exiting."
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
@curr = 1
|
31
|
+
@page = 1
|
32
|
+
fetch_posts(@page)
|
33
|
+
|
34
|
+
# TODO Figure out a way to get total count
|
35
|
+
if limit
|
36
|
+
@total = limit
|
37
|
+
else
|
38
|
+
@total = 100
|
39
|
+
end
|
40
|
+
#@pages = @total.to_i/100 + 1
|
41
|
+
FileUtils.mkdir_p File.expand_path(@store)
|
42
|
+
end
|
43
|
+
|
44
|
+
def download
|
45
|
+
while @posts.length > 0 do
|
46
|
+
download_posts
|
47
|
+
paginate
|
48
|
+
end
|
49
|
+
puts "No more results for #{@tags}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def paginate
|
55
|
+
@page += 1
|
56
|
+
puts "Fetching page #{@page} of #{@pages}"
|
57
|
+
fetch_posts(@page)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an array of posts
|
61
|
+
def fetch_posts(page)
|
62
|
+
params = {:page => page, :tags => @tags}
|
63
|
+
@posts = @client.list_posts(params)
|
64
|
+
# TODO Implement download by pool
|
65
|
+
#@posts = @client.list_pool_posts(params)
|
66
|
+
end
|
67
|
+
|
68
|
+
def download_posts
|
69
|
+
@posts.each do |post|
|
70
|
+
file_url = post['file_url']
|
71
|
+
md5 = post['md5']
|
72
|
+
tags = post['tags']
|
73
|
+
|
74
|
+
filename = @client.url_file_segment(file_url, md5)
|
75
|
+
filename = reduce_filename(filename, 100)
|
76
|
+
filename = clean_filename(filename)
|
77
|
+
filename = File.join(@store, filename)
|
78
|
+
|
79
|
+
if File.exist?(filename) && md5 == get_md5_hash(filename)
|
80
|
+
puts "(#{@curr}/#{@total}): File already exists, skipping."
|
81
|
+
else
|
82
|
+
puts "(#{@curr}/#{@total}): Saving..."
|
83
|
+
transfer(file_url, filename, @user_agent, @client.referer)
|
84
|
+
puts "Saved."
|
85
|
+
end
|
86
|
+
|
87
|
+
@curr += 1
|
88
|
+
if @curr > @total
|
89
|
+
puts "Maximum downloads reached."
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/booru/error.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
module Error
|
4
|
+
# Custom error class for rescuing all Moebooru errors
|
5
|
+
class BooruError < StandardError; end
|
6
|
+
|
7
|
+
# Raised when Booru returns the HTTP status code 420
|
8
|
+
# Reason: Record could not be saved
|
9
|
+
class InvalidRecordError < BooruError; end
|
10
|
+
# Raised when Booru returns the HTTP status code 421
|
11
|
+
# Reason: User is throttled, try again later
|
12
|
+
class UserThrottled < BooruError; end
|
13
|
+
# Raised when Booru returns the HTTP status code 422
|
14
|
+
# Reason: The resource is locked and cannot be modified
|
15
|
+
class LockedError < BooruError; end
|
16
|
+
# Raised when Booru returns the HTTP status code 423
|
17
|
+
# Reason: Resource already exists
|
18
|
+
class AlreadyExistsError < BooruError; end
|
19
|
+
# Raised when Booru returns the HTTP status code 424
|
20
|
+
# Reason: The given parameters were invalid
|
21
|
+
class InvalidParametersError < BooruError; end
|
22
|
+
|
23
|
+
# Raised when Booru returns the HTTP status code 403
|
24
|
+
class ForbiddenError < StandardError; end
|
25
|
+
# Raised when Booru returns the HTTP status code 404
|
26
|
+
class NotFoundError < StandardError; end
|
27
|
+
# Raised when Booru returns the HTTP status code 500
|
28
|
+
class InternalServerError < StandardError; end
|
29
|
+
# Raised when Booru returns the HTTP status code 503
|
30
|
+
class UnavailableError < StandardError; end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
module Request
|
4
|
+
|
5
|
+
HEADERS = {
|
6
|
+
'User-Agent' => 'Mozilla/5.0',
|
7
|
+
}
|
8
|
+
|
9
|
+
def get(path, options={})
|
10
|
+
request(:get, path, HEADERS.merge(options))
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(path, options = {})
|
14
|
+
request(:post, path, HEADERS.merge(options))
|
15
|
+
end
|
16
|
+
|
17
|
+
# Perform an HTTP request
|
18
|
+
def request(method, path, options)
|
19
|
+
case method
|
20
|
+
when :get
|
21
|
+
response = RestClient.get("#{@host}#{path}", options)
|
22
|
+
when :post
|
23
|
+
response = RestClient.post("#{@host}#{path}", options)
|
24
|
+
end
|
25
|
+
raise_errors(response)
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def raise_errors(response)
|
32
|
+
case response.code.to_i
|
33
|
+
when 403
|
34
|
+
raise Booru::Error::AccessDeniedError
|
35
|
+
when 404
|
36
|
+
raise Booru::Error::NotFoundError
|
37
|
+
when 420
|
38
|
+
raise Booru::Error::InvalidRecordError
|
39
|
+
when 421
|
40
|
+
raise Booru::Error::UserThrottledError
|
41
|
+
when 422
|
42
|
+
raise Booru::Error::ResourceLockedError
|
43
|
+
when 423
|
44
|
+
raise Booru::Error::ResourceAlreadyExistsError
|
45
|
+
when 424
|
46
|
+
raise Booru::Error::InvalidParametersError
|
47
|
+
when 500
|
48
|
+
raise Booru::Error::InternalServerError
|
49
|
+
when 503
|
50
|
+
raise Booru::Error::UnavailableError
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/booru/utils.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Booru
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
def clean_filename(filename)
|
6
|
+
filename.gsub(' ', '_').gsub(/[^a-zA-Z0-9_.]+/, '-')
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO Rather than just reduce filename size by removing
|
10
|
+
# characters, try to not to clip tags in the filename
|
11
|
+
def reduce_filename(filename, max = 255)
|
12
|
+
if filename.length > max
|
13
|
+
suffix = File.extname(filename)
|
14
|
+
basename = filename.chomp(suffix)
|
15
|
+
|
16
|
+
size = max.to_i - suffix.length
|
17
|
+
shortname = basename[0..size]
|
18
|
+
|
19
|
+
[shortname, suffix].join('')
|
20
|
+
else
|
21
|
+
filename
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def sanitize(values)
|
26
|
+
values = values.gsub(" ", "+") if values.is_a?(String)
|
27
|
+
if values.is_a?(Array)
|
28
|
+
values.each { |value| value.gsub(' ', '_') }
|
29
|
+
values = values.join("+")
|
30
|
+
end
|
31
|
+
values
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_md5_hash(file)
|
35
|
+
string = File.read(file)
|
36
|
+
hash = Digest::MD5.hexdigest(string)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Cross-platform 'which'
|
40
|
+
# http://stackoverflow.com/a/5471032
|
41
|
+
def which(cmd)
|
42
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
43
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
44
|
+
exts.each { |ext|
|
45
|
+
exe = "#{path}/#{cmd}#{ext}"
|
46
|
+
return exe if File.executable? exe
|
47
|
+
}
|
48
|
+
end
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# File download wrapper
|
53
|
+
def transfer(url, output, user_agent, referer)
|
54
|
+
if which('wget')
|
55
|
+
wget(url, output, user_agent, referer)
|
56
|
+
elsif which('curl')
|
57
|
+
curl(url, output, user_agent, referer)
|
58
|
+
else
|
59
|
+
write(url, output, user_agent, referer)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def wget(url, output, user_agent, referer)
|
64
|
+
command = "wget -nv #{url} -O #{output} "\
|
65
|
+
"--user-agent=#{user_agent} --referer=#{referer}"
|
66
|
+
system(command)
|
67
|
+
end
|
68
|
+
|
69
|
+
def curl(url, output, user_agent, referer)
|
70
|
+
command = "curl -A #{user_agent} -e #{referer} --progress-bar "\
|
71
|
+
"-o #{output} #{url}"
|
72
|
+
system(command)
|
73
|
+
end
|
74
|
+
|
75
|
+
def write(url, output, user_agent, referer)
|
76
|
+
data = open(url, "User-Agent" => user_agent, "Referer" => referer).read
|
77
|
+
File.open(output, "wb") { |file| file.write(data) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: booru
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Palma
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: trollop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: nori
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rest-client
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A Moebooru API client and image downloader for Danbooru style image boards.
|
79
|
+
email: requiem.der.seele@gmail.com
|
80
|
+
executables:
|
81
|
+
- booru.rb
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- Gemfile
|
87
|
+
- LICENSE
|
88
|
+
- README.md
|
89
|
+
- Rakefile
|
90
|
+
- bin/booru.rb
|
91
|
+
- booru.gemspec
|
92
|
+
- fixtures/konachan/post_index.json
|
93
|
+
- fixtures/yandere/post_index.json
|
94
|
+
- lib/booru.rb
|
95
|
+
- lib/booru/api.rb
|
96
|
+
- lib/booru/api/artist.rb
|
97
|
+
- lib/booru/api/comment.rb
|
98
|
+
- lib/booru/api/favorite.rb
|
99
|
+
- lib/booru/api/forum.rb
|
100
|
+
- lib/booru/api/note.rb
|
101
|
+
- lib/booru/api/pool.rb
|
102
|
+
- lib/booru/api/post.rb
|
103
|
+
- lib/booru/api/tag.rb
|
104
|
+
- lib/booru/api/user.rb
|
105
|
+
- lib/booru/api/wiki.rb
|
106
|
+
- lib/booru/client.rb
|
107
|
+
- lib/booru/client/behoimi.rb
|
108
|
+
- lib/booru/client/danbooru.rb
|
109
|
+
- lib/booru/client/konachan.rb
|
110
|
+
- lib/booru/client/yandere.rb
|
111
|
+
- lib/booru/downloader.rb
|
112
|
+
- lib/booru/error.rb
|
113
|
+
- lib/booru/request.rb
|
114
|
+
- lib/booru/utils.rb
|
115
|
+
- lib/booru/version.rb
|
116
|
+
homepage: https://github.com/mistofvongola/booru
|
117
|
+
licenses: []
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- - lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 1.8.23
|
137
|
+
signing_key:
|
138
|
+
specification_version: 3
|
139
|
+
summary: A Ruby client for the Moebooru API.
|
140
|
+
test_files: []
|