tumblr4r 0.7.0
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/ChangeLog +4 -0
- data/README +48 -0
- data/Rakefile +145 -0
- data/lib/tumblr4r.rb +510 -0
- data/test/test_helper.rb +3 -0
- data/test/tumblr4r_test.rb +379 -0
- metadata +89 -0
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= tumblr4r
|
2
|
+
|
3
|
+
* http://github.com/tmaeda/tumblr4r
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Tumblr API wrapper for Ruby.
|
8
|
+
|
9
|
+
== Synopsis
|
10
|
+
|
11
|
+
Finding by conditions.
|
12
|
+
require 'rubygems'
|
13
|
+
require 'tumblr4r'
|
14
|
+
|
15
|
+
site_a = Tumblr4r::Site.new("site_a.tumblr.com")
|
16
|
+
posts = site_a.find(:all)
|
17
|
+
quote_posts = site_a.find(:all, :type => "quote")
|
18
|
+
posts_offset_and_limit = site_a.find(:all, :offset => 50, :limit => 20)
|
19
|
+
quote_search = site_a.find(:all, :type => "quote", :search => "foo")
|
20
|
+
quote_tagged = site_a.find(:all, :type => "quote", :tagged => "bar")
|
21
|
+
|
22
|
+
Finding by id.
|
23
|
+
post = site_a.find(12345678)
|
24
|
+
|
25
|
+
Posting.
|
26
|
+
site_b = Tumblr4r::Site.new("site_b.tumblr.com", "foo@example.com", "password")
|
27
|
+
site_b.save(post[0])
|
28
|
+
|
29
|
+
Deleting.
|
30
|
+
site_b.delete(post[0].post_id)
|
31
|
+
|
32
|
+
== Installation
|
33
|
+
|
34
|
+
gem install tumblr4r
|
35
|
+
|
36
|
+
== Problems
|
37
|
+
|
38
|
+
* Can't get private posts yet.
|
39
|
+
* Can't upload audio and video data yet.
|
40
|
+
* Can't handle feeds yet.
|
41
|
+
|
42
|
+
|
43
|
+
== Copyright
|
44
|
+
|
45
|
+
Author:: Tomoki MAEDA <http://twitter.com/tmaeda>
|
46
|
+
Copyright:: Copyright (c) 2009 Tomoki MAEDA
|
47
|
+
License:: Ruby's license
|
48
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'rake/contrib/sshpublisher'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'lib/tumblr4r'
|
12
|
+
include FileUtils
|
13
|
+
|
14
|
+
NAME = "tumblr4r"
|
15
|
+
AUTHOR = "Tomoki MAEDA"
|
16
|
+
EMAIL = "tmaeda@ruby-sapporo.org"
|
17
|
+
DESCRIPTION = "Tumblr API Wrapper for Ruby"
|
18
|
+
RUBYFORGE_PROJECT = "tumblr4r"
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
BIN_FILES = %w( )
|
21
|
+
|
22
|
+
VERS = Tumblr4r::VERSION
|
23
|
+
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
25
|
+
RDOC_OPTS = [
|
26
|
+
'--title', "#{NAME} documentation",
|
27
|
+
"--charset", "utf-8",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README",
|
31
|
+
"--inline-source",
|
32
|
+
]
|
33
|
+
|
34
|
+
task :default => [:test]
|
35
|
+
task :package => [:clean]
|
36
|
+
|
37
|
+
Rake::TestTask.new("test") do |t|
|
38
|
+
t.libs << "test"
|
39
|
+
t.pattern = "test/**/*_test.rb"
|
40
|
+
t.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
spec = Gem::Specification.new do |s|
|
44
|
+
s.name = NAME
|
45
|
+
s.version = VERS
|
46
|
+
s.platform = Gem::Platform::RUBY
|
47
|
+
s.has_rdoc = true
|
48
|
+
s.extra_rdoc_files = ["README", "ChangeLog"]
|
49
|
+
s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
|
50
|
+
s.summary = DESCRIPTION
|
51
|
+
s.description = DESCRIPTION
|
52
|
+
s.author = AUTHOR
|
53
|
+
s.email = EMAIL
|
54
|
+
s.homepage = HOMEPATH
|
55
|
+
s.executables = BIN_FILES
|
56
|
+
s.rubyforge_project = RUBYFORGE_PROJECT
|
57
|
+
s.bindir = "bin"
|
58
|
+
s.require_path = "lib"
|
59
|
+
#s.autorequire = ""
|
60
|
+
s.test_files = Dir["test/*_test.rb"]
|
61
|
+
|
62
|
+
s.add_runtime_dependency('activesupport', '>=2.3.2')
|
63
|
+
s.add_development_dependency('pit', '>=0.0.6')
|
64
|
+
s.required_ruby_version = '>= 1.8.6'
|
65
|
+
|
66
|
+
s.files = %w(README ChangeLog Rakefile) +
|
67
|
+
Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
|
68
|
+
Dir.glob("ext/**/*.{h,c,rb}") +
|
69
|
+
Dir.glob("examples/**/*.rb") +
|
70
|
+
Dir.glob("tools/*.rb") +
|
71
|
+
Dir.glob("rails/*.rb")
|
72
|
+
|
73
|
+
s.extensions = FileList["ext/**/extconf.rb"].to_a
|
74
|
+
end
|
75
|
+
|
76
|
+
Rake::GemPackageTask.new(spec) do |p|
|
77
|
+
p.need_tar = true
|
78
|
+
p.gem_spec = spec
|
79
|
+
end
|
80
|
+
|
81
|
+
task :install do
|
82
|
+
name = "#{NAME}-#{VERS}.gem"
|
83
|
+
sh %{rake package}
|
84
|
+
sh %{sudo gem install pkg/#{name}}
|
85
|
+
end
|
86
|
+
|
87
|
+
task :uninstall => [:clean] do
|
88
|
+
sh %{sudo gem uninstall #{NAME}}
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
Rake::RDocTask.new do |rdoc|
|
93
|
+
rdoc.rdoc_dir = 'html'
|
94
|
+
rdoc.options += RDOC_OPTS
|
95
|
+
rdoc.template = "resh"
|
96
|
+
#rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
97
|
+
if ENV['DOC_FILES']
|
98
|
+
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
|
99
|
+
else
|
100
|
+
rdoc.rdoc_files.include('README', 'ChangeLog')
|
101
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
102
|
+
rdoc.rdoc_files.include('ext/**/*.c')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "Publish to RubyForge"
|
107
|
+
task :rubyforge => [:rdoc, :package] do
|
108
|
+
require 'rubyforge'
|
109
|
+
Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'tmaeda').upload
|
110
|
+
end
|
111
|
+
|
112
|
+
desc 'Package and upload the release to rubyforge.'
|
113
|
+
task :release => [:clean, :package] do |t|
|
114
|
+
v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
|
115
|
+
abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
|
116
|
+
pkg = "pkg/#{NAME}-#{VERS}"
|
117
|
+
|
118
|
+
require 'rubyforge'
|
119
|
+
rf = RubyForge.new.configure
|
120
|
+
puts "Logging in"
|
121
|
+
rf.login
|
122
|
+
|
123
|
+
c = rf.userconfig
|
124
|
+
# c["release_notes"] = description if description
|
125
|
+
# c["release_changes"] = changes if changes
|
126
|
+
c["preformatted"] = true
|
127
|
+
|
128
|
+
files = [
|
129
|
+
"#{pkg}.tgz",
|
130
|
+
"#{pkg}.gem"
|
131
|
+
].compact
|
132
|
+
|
133
|
+
puts "Releasing #{NAME} v. #{VERS}"
|
134
|
+
rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
|
135
|
+
end
|
136
|
+
|
137
|
+
desc 'Show information about the gem.'
|
138
|
+
task :debug_gem do
|
139
|
+
puts spec.to_ruby
|
140
|
+
end
|
141
|
+
|
142
|
+
desc 'Update gem spec'
|
143
|
+
task :gemspec do
|
144
|
+
open("#{NAME}.gemspec", 'w').write spec.to_ruby
|
145
|
+
end
|
data/lib/tumblr4r.rb
ADDED
@@ -0,0 +1,510 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'active_support'
|
5
|
+
require 'logger'
|
6
|
+
require 'cgi'
|
7
|
+
module Tumblr4r
|
8
|
+
VERSION = '0.7.0'
|
9
|
+
class TumblrError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
module POST_TYPE
|
13
|
+
REGULAR = "regular"
|
14
|
+
PHOTO = "photo"
|
15
|
+
QUOTE = "quote"
|
16
|
+
LINK = "link"
|
17
|
+
CHAT = "conversation"
|
18
|
+
AUDIO = "audio"
|
19
|
+
VIDEO = "video"
|
20
|
+
end
|
21
|
+
|
22
|
+
# ConnectionオブジェクトとParserオブジェクトを組み合わせて、
|
23
|
+
# TumblrAPIとRubyオブジェクトの相互変換を行う
|
24
|
+
# TODO: private な post だけを取得する API が無いのだなぁ
|
25
|
+
# * Webから更新したものがAPIで取得できるデータに反映されるには少しタイムラグがあるようだ
|
26
|
+
# * Webから更新しちゃうと、POST日時の秒が丸められてしまう
|
27
|
+
class Site
|
28
|
+
attr_accessor :hostname, :email, :password, :name, :timezone, :title, :cname,
|
29
|
+
:description, :feeds
|
30
|
+
attr_accessor :logger
|
31
|
+
# TODO: 変数名もうちょっと考える
|
32
|
+
API_READ_LIMIT = 50
|
33
|
+
@@default_log_level = Logger::INFO
|
34
|
+
cattr_accessor :default_log_level
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# TODO: unit test
|
38
|
+
def find(hostname, email=nil, password=nil, http=nil, &block)
|
39
|
+
site = self.new(hostname, email, password, http)
|
40
|
+
result = site.find(:all)
|
41
|
+
if block_given?
|
42
|
+
result.each do |post|
|
43
|
+
yield post
|
44
|
+
end
|
45
|
+
else
|
46
|
+
return result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(hostname, email=nil, password=nil, http = nil, logger = nil)
|
52
|
+
@hostname = hostname
|
53
|
+
@email = email
|
54
|
+
@password = password
|
55
|
+
@logger = logger || Logger.new(STDERR)
|
56
|
+
@logger.level = @@default_log_level
|
57
|
+
@conn = XMLConnection.new(http || @hostname, email, password, @logger)
|
58
|
+
@parser = XMLParser.new
|
59
|
+
self.site_info
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: ここの再帰取得ロジックはTumblrAPIとは独立してるので
|
63
|
+
# TumblrAPIとは独立した形に切り出したり、TumblrAPIとは切り離してテストを書きたいものだ
|
64
|
+
# @param [Symbol|Integer] id_or_type :all, id
|
65
|
+
# @return [Array<Post>|Post]
|
66
|
+
def find(id_or_type, options = { })
|
67
|
+
params = { }
|
68
|
+
return result if options[:offset] && options[:offset].to_i < 0
|
69
|
+
[:type, :filter, :tagged, :search].each do |option|
|
70
|
+
params[option] = options[option] if options[option]
|
71
|
+
end
|
72
|
+
|
73
|
+
if id_or_type == :all
|
74
|
+
result = []
|
75
|
+
# 取得開始位置の初期化
|
76
|
+
params[:start] = options[:offset] || 0
|
77
|
+
# goal の設定
|
78
|
+
total = self.count(options)
|
79
|
+
if options[:limit]
|
80
|
+
goal = [total - params[:start],
|
81
|
+
options[:limit] - params[:start]].min
|
82
|
+
else
|
83
|
+
goal = total - params[:start]
|
84
|
+
end
|
85
|
+
# 取得件数の初期化
|
86
|
+
if goal < 0
|
87
|
+
return result
|
88
|
+
elsif goal < API_READ_LIMIT
|
89
|
+
params[:num] = goal
|
90
|
+
else
|
91
|
+
params[:num] = API_READ_LIMIT # :num を指定しないとデフォルトでは20件しかとれない
|
92
|
+
end
|
93
|
+
|
94
|
+
loop do
|
95
|
+
xml = @conn.get(params)
|
96
|
+
posts, start, total = @parser.posts(xml)
|
97
|
+
@logger.info("size: #{posts.size}")
|
98
|
+
@logger.info("start: #{start}")
|
99
|
+
@logger.info("total: #{total}")
|
100
|
+
result += posts
|
101
|
+
if result.size >= goal || posts.size == 0
|
102
|
+
# Tumblr API の total で得られる値は全く信用ならない。
|
103
|
+
# 検索条件を考慮した件数を返してくれない。
|
104
|
+
# (つまり、goalは信用ならない)ので、posts.sizeも終了判定に利用する。
|
105
|
+
# TODO: もしくは:numの値を足し合わせていって、それとgoalを比較する?
|
106
|
+
break
|
107
|
+
end
|
108
|
+
# 取得開始位置の調整
|
109
|
+
params[:start] += params[:num]
|
110
|
+
# 取得件数の調整
|
111
|
+
if (goal - result.size) >= API_READ_LIMIT
|
112
|
+
params[:num] = API_READ_LIMIT
|
113
|
+
else
|
114
|
+
params[:num] = goal - result.size
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return result
|
118
|
+
elsif id_or_type.kind_of?(Integer)
|
119
|
+
xml = @conn.get({:id => id_or_type})
|
120
|
+
posts, start, total = @parser.posts(xml)
|
121
|
+
@logger.info("size: #{posts.size}")
|
122
|
+
@logger.info("start: #{start}")
|
123
|
+
@logger.info("total: #{total}")
|
124
|
+
return posts[0]
|
125
|
+
else
|
126
|
+
raise ArgumentError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def count(options = { })
|
131
|
+
params = { }
|
132
|
+
[:id, :type, :filter, :tagged, :search].each do |option|
|
133
|
+
params[option] = options[option] if options[option]
|
134
|
+
end
|
135
|
+
params[:num] = 1
|
136
|
+
params[:start] = 0
|
137
|
+
xml = @conn.get(params)
|
138
|
+
posts, start, total = @parser.posts(xml)
|
139
|
+
return total
|
140
|
+
end
|
141
|
+
|
142
|
+
def site_info
|
143
|
+
xml = @conn.get(:num => 1)
|
144
|
+
@parser.siteinfo(self, xml)
|
145
|
+
end
|
146
|
+
|
147
|
+
def save(post)
|
148
|
+
post_id = @conn.write(post.params)
|
149
|
+
new_post = self.find(post_id)
|
150
|
+
return new_post
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
# Postおよびその子クラスは原則として単なるData Transfer Objectとし、
|
156
|
+
# 何かのロジックをこの中に実装はしない。
|
157
|
+
class Post
|
158
|
+
attr_accessor :post_id, # Integer
|
159
|
+
:url, # String
|
160
|
+
:url_with_slug, # String
|
161
|
+
:type, # String
|
162
|
+
:date_gmt,
|
163
|
+
:date,
|
164
|
+
:unix_timestamp, # Integer
|
165
|
+
:format, # String("html"|"markdown")
|
166
|
+
:tags, # Array<String>
|
167
|
+
:bookmarklet, # true|false
|
168
|
+
:private, # Integer(0|1)
|
169
|
+
:generator # String
|
170
|
+
|
171
|
+
@@default_generator = nil
|
172
|
+
cattr_accessor :default_generator
|
173
|
+
|
174
|
+
def initialize
|
175
|
+
@generator = @@default_generator || "Tumblr4R"
|
176
|
+
@tags = []
|
177
|
+
end
|
178
|
+
|
179
|
+
def params
|
180
|
+
{"type" => @type,
|
181
|
+
"generator" => @generator,
|
182
|
+
"date" => @date,
|
183
|
+
"private" => @private,
|
184
|
+
"tags" => @tags.join(","),
|
185
|
+
"format" => @format
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Regular < Post
|
191
|
+
attr_accessor :regular_title, :regular_body
|
192
|
+
|
193
|
+
def params
|
194
|
+
super.merge!({"title" => @regular_title, "body" => @regular_body })
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# TODO: Feed の扱いをどうするか
|
199
|
+
class Feed < Post
|
200
|
+
attr_accessor :regular_body, :feed_item, :from_feed_id
|
201
|
+
# TODO: titleのあるfeed itemってあるのか?
|
202
|
+
end
|
203
|
+
|
204
|
+
class Photo < Post
|
205
|
+
attr_accessor :photo_caption, :photo_link_url, :photo_url
|
206
|
+
#TODO: photo_url の max-width って何?
|
207
|
+
attr_accessor :data
|
208
|
+
|
209
|
+
# TODO: data をどうやってPOSTするか考える
|
210
|
+
# 生のデータを持たせるんじゃなく、TumblrPostDataみたいな
|
211
|
+
# クラスにラップして、それを各POSTのivarに保持させる?
|
212
|
+
def params
|
213
|
+
super.merge!(
|
214
|
+
{"source" => @photo_url,
|
215
|
+
"caption" => @photo_caption,
|
216
|
+
"click-through-url" => @photo_link_url,
|
217
|
+
"data" => @data})
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class Quote < Post
|
222
|
+
attr_accessor :quote_text, :quote_source
|
223
|
+
|
224
|
+
def params
|
225
|
+
super.merge!(
|
226
|
+
{"quote" => @quote_text,
|
227
|
+
"source" => @quote_source})
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class Link < Post
|
232
|
+
attr_accessor :link_text, :link_url, :link_description
|
233
|
+
def params
|
234
|
+
super.merge!(
|
235
|
+
{"name" => @link_text,
|
236
|
+
"url" => @link_url,
|
237
|
+
"description" => @link_description})
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class Chat < Post
|
242
|
+
attr_accessor :conversation_title, :conversation_text
|
243
|
+
# <conversation><line name="..." label="...">text</line>のリスト</conversation>
|
244
|
+
|
245
|
+
def params
|
246
|
+
super.merge!(
|
247
|
+
{"title" => @conversation_title,
|
248
|
+
"conversation" => @conversation_text})
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Audio < Post
|
253
|
+
attr_accessor :audio_plays, :audio_caption, :audio_player
|
254
|
+
attr_accessor :data
|
255
|
+
|
256
|
+
def params
|
257
|
+
super.merge!(
|
258
|
+
{"data" => @data,
|
259
|
+
"caption" => @audio_caption})
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class Video < Post
|
264
|
+
attr_accessor :video_caption, :video_source, :video_player
|
265
|
+
attr_accessor :data, :title
|
266
|
+
# TODO: title は vimeo へのアップロードのときのみ有効らしい
|
267
|
+
# TODO: embed を使うか、アップロードしたdataを使うかってのは
|
268
|
+
# Tumblr側で勝手に判断されるのかなぁ?
|
269
|
+
def params
|
270
|
+
super.merge!(
|
271
|
+
{"embed" => @video_source,
|
272
|
+
"data" => @data,
|
273
|
+
"title" => @title,
|
274
|
+
"caption" => @video_caption})
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Tumblr XML API への薄いラッパー。
|
279
|
+
# Rubyオブジェクトからの変換やRubyオブジェクトへの変換などは
|
280
|
+
# Parserクラスで行う。Parserクラスへの依存関係は一切持たない。
|
281
|
+
class XMLConnection
|
282
|
+
attr_accessor :logger, :group, :authenticated
|
283
|
+
def initialize(http_or_hostname, email=nil, password=nil, logger = nil)
|
284
|
+
case http_or_hostname
|
285
|
+
when String
|
286
|
+
@conn = Net::HTTP.new(http_or_hostname)
|
287
|
+
when Net::HTTP
|
288
|
+
@conn = http_or_hostname
|
289
|
+
else
|
290
|
+
raise ArgumentError.new("http_or_hostname must be String or Net::HTTP")
|
291
|
+
end
|
292
|
+
@email= email
|
293
|
+
@password = password
|
294
|
+
if @email && @password
|
295
|
+
begin
|
296
|
+
@authenticated = authenticate
|
297
|
+
rescue TumblrError
|
298
|
+
@authenticated = false
|
299
|
+
end
|
300
|
+
end
|
301
|
+
@group = @conn.address
|
302
|
+
@logger = logger || Logger.new(STDERR)
|
303
|
+
end
|
304
|
+
|
305
|
+
# @param [Hash] options :id, :type, :filter, :tagged, :search, :start, :num
|
306
|
+
def get(options = { })
|
307
|
+
params = options.map{|k, v|
|
308
|
+
"#{k}=#{v}"
|
309
|
+
}.join("&")
|
310
|
+
req = "/api/read?#{params}"
|
311
|
+
logger.info(req)
|
312
|
+
res = @conn.get(req)
|
313
|
+
logger.debug(res.body)
|
314
|
+
case res
|
315
|
+
when Net::HTTPOK
|
316
|
+
return res.body
|
317
|
+
when Net::HTTPNotFound
|
318
|
+
raise TumblrError.new("no such site(#{@hostname})")
|
319
|
+
else
|
320
|
+
raise TumblrError.new("unexpected response #{res.inspect}")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# @return true if email and password are valid
|
325
|
+
# @raise TumblrError if email or password is invalid
|
326
|
+
def authenticate
|
327
|
+
response = nil
|
328
|
+
http = Net::HTTP.new("www.tumblr.com")
|
329
|
+
response = http.post('/api/authenticate',
|
330
|
+
"email=#{CGI.escape(@email)}&password=#{CGI.escape(@password)}")
|
331
|
+
|
332
|
+
case response
|
333
|
+
when Net::HTTPOK
|
334
|
+
return true
|
335
|
+
else
|
336
|
+
raise TumblrError.new(response.inspect + "\n" + response.body)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
# @return [Integer] post_id if success
|
341
|
+
# @raise [TumblrError] if fail
|
342
|
+
def write(options)
|
343
|
+
raise TumblrError.new("email or password is invalid") unless authenticated
|
344
|
+
|
345
|
+
response = nil
|
346
|
+
http = Net::HTTP.new("www.tumblr.com")
|
347
|
+
params = options.merge({"email" => @email, "password" => @password, "group" => @group})
|
348
|
+
query_string = params.delete_if{|k,v| v == nil }.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" unless v.nil?}.join("&")
|
349
|
+
logger.debug("#### query_string: #{query_string}")
|
350
|
+
response = http.post('/api/write', query_string)
|
351
|
+
case response
|
352
|
+
when Net::HTTPSuccess
|
353
|
+
return response.body.to_i
|
354
|
+
else
|
355
|
+
msg = response.inspect + "\n"
|
356
|
+
response.each{|k,v| msg += "#{k}: #{v}\n"}
|
357
|
+
msg += response.body
|
358
|
+
raise TumblrError.new(msg)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# @params [Integer] post_id
|
363
|
+
def delete(post_id)
|
364
|
+
raise TumblrError.new("email or password is invalid") unless authenticated
|
365
|
+
response = nil
|
366
|
+
http = Net::HTTP.new("www.tumblr.com")
|
367
|
+
params = {"post-id" => post_id, "email" => @email, "password" => @password, "group" => @group}
|
368
|
+
query_string = params.delete_if{|k,v| v == nil }.map{|k,v| "#{k}=#{CGI.escape(v.to_s)}" unless v.nil?}.join("&")
|
369
|
+
logger.debug("#### query_string: #{query_string}")
|
370
|
+
response = http.post('/api/delete', query_string)
|
371
|
+
case response
|
372
|
+
when Net::HTTPSuccess
|
373
|
+
logger.debug("#### response: #{response.code}: #{response.body}")
|
374
|
+
return true
|
375
|
+
else
|
376
|
+
msg = response.inspect + "\n"
|
377
|
+
response.each{|k,v| msg += "#{k}: #{v}\n"}
|
378
|
+
msg += response.body
|
379
|
+
raise TumblrError.new(msg)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Tumblr XML API
|
385
|
+
class XMLParser
|
386
|
+
# @param [Site] site xmlをパースした結果を埋める入れ物
|
387
|
+
# @param [String] xml TumblrAPIのレスポンスのXMLそのまま
|
388
|
+
def siteinfo(site, xml)
|
389
|
+
xml_doc = REXML::Document.new(xml)
|
390
|
+
tumblelog = REXML::XPath.first(xml_doc, "//tumblr/tumblelog")
|
391
|
+
site.name = tumblelog.attributes["name"]
|
392
|
+
site.timezone = tumblelog.attributes["timezone"]
|
393
|
+
site.title = tumblelog.attributes["title"]
|
394
|
+
site.cname = tumblelog.attributes["cname"]
|
395
|
+
site.description = tumblelog.text
|
396
|
+
# tumblelog.elements["/feeds"]}
|
397
|
+
# TODO: feeds は後回し
|
398
|
+
return site
|
399
|
+
end
|
400
|
+
|
401
|
+
# XMLをパースしてオブジェクトのArrayを作る
|
402
|
+
# @param [String] xml APIからのレスポンス全体
|
403
|
+
# @return [Array<Post>, Integer, Integer] 各種Postの子クラスのArray, start, total
|
404
|
+
def posts(xml)
|
405
|
+
rexml_doc = REXML::Document.new(xml)
|
406
|
+
rexml_posts = REXML::XPath.first(rexml_doc, "//tumblr/posts")
|
407
|
+
start = rexml_posts.attributes["start"]
|
408
|
+
total = rexml_posts.attributes["total"]
|
409
|
+
posts = []
|
410
|
+
rexml_posts.elements.each("//posts/post") do |rexml_post|
|
411
|
+
post_type = rexml_post.attributes["type"]
|
412
|
+
post = nil
|
413
|
+
case post_type
|
414
|
+
when POST_TYPE::REGULAR
|
415
|
+
post = self.regular(Regular.new, rexml_post)
|
416
|
+
when POST_TYPE::PHOTO
|
417
|
+
post = self.photo(Photo.new, rexml_post)
|
418
|
+
when POST_TYPE::QUOTE
|
419
|
+
post = self.quote(Quote.new, rexml_post)
|
420
|
+
when POST_TYPE::LINK
|
421
|
+
post = self.link(Link.new, rexml_post)
|
422
|
+
when POST_TYPE::CHAT
|
423
|
+
post = self.chat(Chat.new, rexml_post)
|
424
|
+
when POST_TYPE::AUDIO
|
425
|
+
post = self.audio(Audio.new, rexml_post)
|
426
|
+
when POST_TYPE::VIDEO
|
427
|
+
post = self.video(Video.new, rexml_post)
|
428
|
+
else
|
429
|
+
raise TumblrError.new("unknown post type #{post_type}")
|
430
|
+
end
|
431
|
+
posts << post
|
432
|
+
end
|
433
|
+
return posts, start.to_i, total.to_i
|
434
|
+
end
|
435
|
+
|
436
|
+
# TODO: この辺りの設計についてはもう少し考慮の余地がある?
|
437
|
+
# みんな同じような構造(まずはpost(post, rexml_post)呼んでその後独自処理)してるし、
|
438
|
+
# 引数にpostとrexml_postをもらってくるってのもなんかイケてない気がする。
|
439
|
+
def post(post, rexml_post)
|
440
|
+
post.post_id = rexml_post.attributes["id"].to_i
|
441
|
+
post.url = rexml_post.attributes["url"]
|
442
|
+
post.url_with_slug = rexml_post.attributes["url-with-slug"]
|
443
|
+
post.type = rexml_post.attributes["type"]
|
444
|
+
# TODO: time 関係の型をStringじゃなくTimeとかにする?
|
445
|
+
post.date_gmt = rexml_post.attributes["date-gmt"]
|
446
|
+
post.date = rexml_post.attributes["date"]
|
447
|
+
post.unix_timestamp = rexml_post.attributes["unix-timestamp"].to_i
|
448
|
+
post.format = rexml_post.attributes["format"]
|
449
|
+
post.tags = rexml_post.get_elements("tag").map(&:text)
|
450
|
+
post.bookmarklet = (rexml_post.attributes["bookmarklet"] == "true")
|
451
|
+
post
|
452
|
+
end
|
453
|
+
|
454
|
+
def regular(post, rexml_post)
|
455
|
+
post = self.post(post, rexml_post)
|
456
|
+
post.regular_title = rexml_post.elements["regular-title"].try(:text) || ""
|
457
|
+
post.regular_body = rexml_post.elements["regular-body"].try(:text) || ""
|
458
|
+
post
|
459
|
+
end
|
460
|
+
|
461
|
+
def photo(post, rexml_post)
|
462
|
+
post = self.post(post, rexml_post)
|
463
|
+
post.type
|
464
|
+
post.photo_caption = rexml_post.elements["photo-caption"].try(:text) || ""
|
465
|
+
post.photo_link_url = rexml_post.elements["photo-link-url"].try(:text) || ""
|
466
|
+
post.photo_url = rexml_post.elements["photo-url"].try(:text) || ""
|
467
|
+
post
|
468
|
+
end
|
469
|
+
|
470
|
+
def quote(post, rexml_post)
|
471
|
+
post = self.post(post, rexml_post)
|
472
|
+
post.quote_text = rexml_post.elements["quote-text"].try(:text) || ""
|
473
|
+
post.quote_source = rexml_post.elements["quote-source"].try(:text) || ""
|
474
|
+
post
|
475
|
+
end
|
476
|
+
|
477
|
+
def link(post, rexml_post)
|
478
|
+
post = self.post(post, rexml_post)
|
479
|
+
post.link_text = rexml_post.elements["link-text"].try(:text) || ""
|
480
|
+
post.link_url = rexml_post.elements["link-url"].try(:text) || ""
|
481
|
+
post.link_description = rexml_post.elements["link-description"].try(:text) || ""
|
482
|
+
post
|
483
|
+
end
|
484
|
+
|
485
|
+
def chat(post, rexml_post)
|
486
|
+
post = self.post(post, rexml_post)
|
487
|
+
post.conversation_title = rexml_post.elements["conversation-title"].try(:text) || ""
|
488
|
+
post.conversation_text = rexml_post.elements["conversation-text"].try(:text) || ""
|
489
|
+
post
|
490
|
+
end
|
491
|
+
|
492
|
+
def audio(post, rexml_post)
|
493
|
+
post = self.post(post, rexml_post)
|
494
|
+
post.audio_plays = (rexml_post.attributes["audio-plays"] == "1")
|
495
|
+
post.audio_caption = rexml_post.elements["audio-caption"].try(:text) || ""
|
496
|
+
post.audio_player = rexml_post.elements["audio-player"].try(:text) || ""
|
497
|
+
post
|
498
|
+
end
|
499
|
+
|
500
|
+
def video(post, rexml_post)
|
501
|
+
post = self.post(post, rexml_post)
|
502
|
+
post.video_caption = rexml_post.elements["video-caption"].try(:text) || ""
|
503
|
+
post.video_source = rexml_post.elements["video-source"].try(:text) || ""
|
504
|
+
post.video_player = rexml_post.elements["video-player"].try(:text) || ""
|
505
|
+
post
|
506
|
+
end
|
507
|
+
|
508
|
+
end
|
509
|
+
|
510
|
+
end # module
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require "test/unit"
|
3
|
+
require 'pit'
|
4
|
+
class Tumblr4rTest < Test::Unit::TestCase
|
5
|
+
include Tumblr4r
|
6
|
+
READ_TEST_HOST = "tumblr4rtest.tumblr.com"
|
7
|
+
WRITE_TEST_HOST = "tumblr4rwritetest.tumblr.com"
|
8
|
+
TOTAL_COUNT = 114
|
9
|
+
QUOTE_COUNT = 102
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@site = Site.new(READ_TEST_HOST)
|
13
|
+
writetest_conf = Pit.get("tumblr4rwritetest",
|
14
|
+
:require => {"email" => "required email",
|
15
|
+
"password" => "required password"})
|
16
|
+
@write_site = Site.new(WRITE_TEST_HOST,
|
17
|
+
writetest_conf["email"],
|
18
|
+
writetest_conf["password"])
|
19
|
+
@large_site = Site.new("tumblr4rfindtest.tumblr.com")
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def test_initialize
|
27
|
+
assert_equal READ_TEST_HOST, @site.hostname
|
28
|
+
assert_nil @site.email
|
29
|
+
assert_nil @site.password
|
30
|
+
|
31
|
+
assert_equal "tumblr4rtest", @site.name
|
32
|
+
assert_equal "Asia/Tokyo", @site.timezone
|
33
|
+
assert_equal "tumblr4rテスト", @site.title
|
34
|
+
assert_nil @site.cname
|
35
|
+
assert_equal "tumblr4rのテスト用サイトです。\r\ntumblrサイコー", @site.description
|
36
|
+
# TODO: feeds は後回し
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_count
|
40
|
+
total = @large_site.count
|
41
|
+
assert_equal TOTAL_COUNT, total
|
42
|
+
|
43
|
+
total = @large_site.count(:type => "quote")
|
44
|
+
assert_equal QUOTE_COUNT, total
|
45
|
+
|
46
|
+
total = @large_site.count(:filter => "text")
|
47
|
+
assert_equal TOTAL_COUNT, total
|
48
|
+
|
49
|
+
total = @large_site.count(:tagged => "test")
|
50
|
+
assert_equal TOTAL_COUNT, total
|
51
|
+
|
52
|
+
total = @large_site.count(:search => "test")
|
53
|
+
assert_equal TOTAL_COUNT, total
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_find
|
57
|
+
posts = @site.find(:all)
|
58
|
+
assert_equal 8, posts.size
|
59
|
+
assert_equal Video, posts[0].class
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_find_all
|
63
|
+
posts = @large_site.find(:all)
|
64
|
+
assert_equal TOTAL_COUNT, posts.size
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_find_all_quote
|
68
|
+
posts = @large_site.find(:all, :type => "quote")
|
69
|
+
assert_equal QUOTE_COUNT, posts.size
|
70
|
+
end
|
71
|
+
|
72
|
+
# 実際に存在する件数より少なく指定した場合
|
73
|
+
def test_find_all_with_num
|
74
|
+
posts = @large_site.find(:all, :limit => 74)
|
75
|
+
assert_equal 74, posts.size
|
76
|
+
end
|
77
|
+
|
78
|
+
# 実際に存在する件数より多く指定した場合
|
79
|
+
def test_find_all_with_over_num
|
80
|
+
posts = @large_site.find(:all, :limit => 765)
|
81
|
+
assert_equal TOTAL_COUNT, posts.size
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_find_all_with_offset
|
85
|
+
posts = @large_site.find(:all, :offset => 12)
|
86
|
+
assert_equal TOTAL_COUNT-12, posts.size
|
87
|
+
end
|
88
|
+
|
89
|
+
# 実際に存在するよりも多いoffsetを指定した場合
|
90
|
+
def test_find_all_over_offset
|
91
|
+
posts = @large_site.find(:all, :type => "quote", :offset => TOTAL_COUNT + 1)
|
92
|
+
assert_equal 0, posts.size
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_find_with_type_regular
|
96
|
+
posts = @site.find(:all, :type => "regular")
|
97
|
+
assert_equal 2, posts.size
|
98
|
+
|
99
|
+
assert_equal Regular, posts[0].class
|
100
|
+
assert_equal 123459291, posts[0].post_id
|
101
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123459291", posts[0].url
|
102
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123459291/regular-test", posts[0].url_with_slug
|
103
|
+
assert_equal "regular", posts[0].type
|
104
|
+
assert_equal "2009-06-14 16:30:44 GMT", posts[0].date_gmt
|
105
|
+
assert_equal "Mon, 15 Jun 2009 01:30:44", posts[0].date
|
106
|
+
assert_equal 1244997044, posts[0].unix_timestamp
|
107
|
+
assert_equal "html", posts[0].format
|
108
|
+
assert_equal ["test", "regular"], posts[0].tags
|
109
|
+
assert_equal false, posts[0].bookmarklet
|
110
|
+
|
111
|
+
assert_equal "Text Postのテストです", posts[0].regular_title
|
112
|
+
assert_equal <<EOF.chomp, posts[0].regular_body
|
113
|
+
<p>テキストです。</p>
|
114
|
+
<p><b>ボールドです。</b></p>
|
115
|
+
<p><i>イタリックです。</i></p>
|
116
|
+
<p><strike>取り消し線です。</strike></p>
|
117
|
+
<ul>
|
118
|
+
<li>unordered 1</li>
|
119
|
+
<li>unordered 2</li>
|
120
|
+
</ul>
|
121
|
+
<ol>
|
122
|
+
<li>ordered 1</li>
|
123
|
+
<li>ordered 2</li>
|
124
|
+
</ol>
|
125
|
+
<p>ここからインデント</p>
|
126
|
+
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">インデント開始<br/>ああああ<br/>インデント終了</blockquote>
|
127
|
+
<p>ここまでインデント</p>
|
128
|
+
EOF
|
129
|
+
|
130
|
+
assert_equal Regular, posts[1].class
|
131
|
+
assert_equal 122871637, posts[1].post_id
|
132
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/122871637", posts[1].url
|
133
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/122871637/tumblr4r", posts[1].url_with_slug
|
134
|
+
assert_equal "regular", posts[1].type
|
135
|
+
assert_equal "2009-06-13 12:46:23 GMT", posts[1].date_gmt
|
136
|
+
assert_equal "Sat, 13 Jun 2009 21:46:23", posts[1].date
|
137
|
+
assert_equal 1244897183, posts[1].unix_timestamp
|
138
|
+
assert_equal "html", posts[1].format
|
139
|
+
assert_equal [], posts[1].tags
|
140
|
+
assert_equal false, posts[1].bookmarklet
|
141
|
+
|
142
|
+
assert_equal "", posts[1].regular_title
|
143
|
+
assert_equal "Tumblr4rのテストです", posts[1].regular_body
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_find_with_type_photo
|
147
|
+
posts = @site.find(:all, :type => "photo")
|
148
|
+
assert_equal 1, posts.size
|
149
|
+
assert_equal Photo, posts[0].class
|
150
|
+
assert_equal 123461063, posts[0].post_id
|
151
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123461063", posts[0].url
|
152
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123461063/photo", posts[0].url_with_slug
|
153
|
+
assert_equal "photo", posts[0].type
|
154
|
+
assert_equal "2009-06-14 16:34:50 GMT", posts[0].date_gmt
|
155
|
+
assert_equal "Mon, 15 Jun 2009 01:34:50", posts[0].date
|
156
|
+
assert_equal 1244997290, posts[0].unix_timestamp
|
157
|
+
assert_equal "html", posts[0].format
|
158
|
+
assert_equal ["test", "photo"], posts[0].tags
|
159
|
+
assert_equal false, posts[0].bookmarklet
|
160
|
+
|
161
|
+
assert_equal "<p>Photoのテストです。</p>\n\n<p>ギコです。</p>", posts[0].photo_caption
|
162
|
+
assert_equal "http://www.google.co.jp/", posts[0].photo_link_url
|
163
|
+
assert_equal "http://5.media.tumblr.com/GyEYZujUYopiula4XKmXhCgmo1_250.jpg", posts[0].photo_url
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_find_with_type_quote
|
167
|
+
posts = @site.find(:all, :type => "quote")
|
168
|
+
assert_equal 1, posts.size
|
169
|
+
assert_equal Quote, posts[0].class
|
170
|
+
assert_equal 123470309, posts[0].post_id
|
171
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123470309", posts[0].url
|
172
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123470309/wikipedia-tumblr", posts[0].url_with_slug
|
173
|
+
assert_equal "quote", posts[0].type
|
174
|
+
assert_equal "2009-06-14 16:58:00 GMT", posts[0].date_gmt
|
175
|
+
assert_equal "Mon, 15 Jun 2009 01:58:00", posts[0].date
|
176
|
+
assert_equal 1244998680, posts[0].unix_timestamp
|
177
|
+
assert_equal "html", posts[0].format
|
178
|
+
assert_equal ["quote", "test"], posts[0].tags
|
179
|
+
assert_equal true, posts[0].bookmarklet
|
180
|
+
|
181
|
+
assert_equal <<EOF.chomp, posts[0].quote_text
|
182
|
+
Tumblelog/Tumblr(タンブルログ/タンブラー)は、メディアミックスウェブログサービス。米国 Davidville.incにより2007年3月1日にサービスが開始された。
|
183
|
+
EOF
|
184
|
+
assert_equal "<a href=\"http://ja.wikipedia.org/wiki/Tumblr\">Tumblelog - Wikipedia</a>", posts[0].quote_source
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_find_with_type_link
|
188
|
+
posts = @site.find(:all, :type => "link")
|
189
|
+
assert_equal 1, posts.size
|
190
|
+
assert_equal Link, posts[0].class
|
191
|
+
assert_equal 123470990, posts[0].post_id
|
192
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123470990", posts[0].url
|
193
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123470990/tumblr-link", posts[0].url_with_slug
|
194
|
+
assert_equal "link", posts[0].type
|
195
|
+
assert_equal "2009-06-14 17:00:00 GMT", posts[0].date_gmt
|
196
|
+
assert_equal "Mon, 15 Jun 2009 02:00:00", posts[0].date
|
197
|
+
assert_equal 1244998800, posts[0].unix_timestamp
|
198
|
+
assert_equal "html", posts[0].format
|
199
|
+
assert_equal ["test", "link"], posts[0].tags
|
200
|
+
assert_equal false, posts[0].bookmarklet
|
201
|
+
|
202
|
+
assert_equal "たんぶらー", posts[0].link_text
|
203
|
+
assert_equal "http://www.tumblr.com/", posts[0].link_url
|
204
|
+
assert_equal "ですくりぷしょん", posts[0].link_description
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_find_with_type_conversation
|
208
|
+
posts = @site.find(:all, :type => "conversation")
|
209
|
+
assert_equal 1, posts.size
|
210
|
+
assert_equal Chat, posts[0].class
|
211
|
+
assert_equal 123471808, posts[0].post_id
|
212
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123471808", posts[0].url
|
213
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123471808/pointcard-chat", posts[0].url_with_slug
|
214
|
+
assert_equal "conversation", posts[0].type
|
215
|
+
assert_equal "2009-06-14 17:01:54 GMT", posts[0].date_gmt
|
216
|
+
assert_equal "Mon, 15 Jun 2009 02:01:54", posts[0].date
|
217
|
+
assert_equal 1244998914, posts[0].unix_timestamp
|
218
|
+
assert_equal "html", posts[0].format
|
219
|
+
assert_equal ["test", "chat"], posts[0].tags
|
220
|
+
assert_equal false, posts[0].bookmarklet
|
221
|
+
|
222
|
+
assert_equal "なにそれこわい", posts[0].conversation_title
|
223
|
+
assert_equal <<EOF.chomp, posts[0].conversation_text
|
224
|
+
店員: 当店のポイントカードはお餅でしょうか\r
|
225
|
+
ぼく: えっ\r
|
226
|
+
店員: 当店のポイントカードはお餅ですか \r
|
227
|
+
ぼく: いえしりません\r
|
228
|
+
店員: えっ\r
|
229
|
+
ぼく: えっ\r
|
230
|
+
EOF
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_find_with_type_audio
|
234
|
+
posts = @site.find(:all, :type => "audio")
|
235
|
+
assert_equal 1, posts.size
|
236
|
+
assert_equal Audio, posts[0].class
|
237
|
+
assert_equal 131705561, posts[0].post_id
|
238
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/131705561", posts[0].url
|
239
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/131705561/tumblr4r-miku", posts[0].url_with_slug
|
240
|
+
assert_equal "audio", posts[0].type
|
241
|
+
assert_equal "2009-06-28 13:58:13 GMT", posts[0].date_gmt
|
242
|
+
assert_equal "Sun, 28 Jun 2009 22:58:13", posts[0].date
|
243
|
+
assert_equal 1246197493, posts[0].unix_timestamp
|
244
|
+
assert_equal "html", posts[0].format
|
245
|
+
assert_equal [], posts[0].tags
|
246
|
+
assert_equal false, posts[0].bookmarklet
|
247
|
+
|
248
|
+
assert_equal true, posts[0].audio_plays
|
249
|
+
assert_equal "tumblr4r miku", posts[0].audio_caption
|
250
|
+
assert_equal "<embed type=\"application/x-shockwave-flash\" src=\"http://tumblr4rtest.tumblr.com/swf/audio_player.swf?audio_file=http://www.tumblr.com/audio_file/131705561/GyEYZujUYp9df3nv1WMefTH8&color=FFFFFF\" height=\"27\" width=\"207\" quality=\"best\"></embed>", posts[0].audio_player
|
251
|
+
end
|
252
|
+
|
253
|
+
def test_find_with_type_video
|
254
|
+
posts = @site.find(:all, :type => "video")
|
255
|
+
assert_equal 1, posts.size
|
256
|
+
assert_equal Video, posts[0].class
|
257
|
+
assert_equal 131714219, posts[0].post_id
|
258
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/131714219", posts[0].url
|
259
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/131714219/matrix-sappoloaded", posts[0].url_with_slug
|
260
|
+
assert_equal "video", posts[0].type
|
261
|
+
assert_equal "2009-06-28 14:22:56 GMT", posts[0].date_gmt
|
262
|
+
assert_equal "Sun, 28 Jun 2009 23:22:56", posts[0].date
|
263
|
+
assert_equal 1246198976, posts[0].unix_timestamp
|
264
|
+
assert_equal "html", posts[0].format
|
265
|
+
assert_equal [], posts[0].tags
|
266
|
+
assert_equal false, posts[0].bookmarklet
|
267
|
+
|
268
|
+
assert_equal "matrix sappoloaded", posts[0].video_caption
|
269
|
+
assert_equal "http://www.youtube.com/watch?v=FavWH5RhYpw", posts[0].video_source
|
270
|
+
assert_equal "<object width=\"400\" height=\"336\"><param name=\"movie\" value=\"http://www.youtube.com/v/FavWH5RhYpw&rel=0&egm=0&showinfo=0&fs=1\"></param><param name=\"wmode\" value=\"transparent\"></param><param name=\"allowFullScreen\" value=\"true\"></param><embed src=\"http://www.youtube.com/v/FavWH5RhYpw&rel=0&egm=0&showinfo=0&fs=1\" type=\"application/x-shockwave-flash\" width=\"400\" height=\"336\" allowFullScreen=\"true\" wmode=\"transparent\"></embed></object>", posts[0].video_player
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_find_with_tagged
|
274
|
+
posts = @site.find(:all, :tagged => "test")
|
275
|
+
assert_equal 5, posts.size
|
276
|
+
end
|
277
|
+
|
278
|
+
def test_find_with_search
|
279
|
+
posts = @site.find(:all, :search => "matrix")
|
280
|
+
assert_equal 1, posts.size
|
281
|
+
end
|
282
|
+
|
283
|
+
def test_generator_default
|
284
|
+
post = Post.new
|
285
|
+
assert_equal "Tumblr4R", post.generator
|
286
|
+
|
287
|
+
Post.default_generator = "foo"
|
288
|
+
post2 = Post.new
|
289
|
+
assert_equal "foo", post2.generator
|
290
|
+
assert_equal "Tumblr4R", post.generator
|
291
|
+
|
292
|
+
|
293
|
+
Post.default_generator = nil
|
294
|
+
post3 = Post.new
|
295
|
+
assert_equal "Tumblr4R", post3.generator
|
296
|
+
end
|
297
|
+
|
298
|
+
def test_find_by_id
|
299
|
+
post = @site.find(123459291)
|
300
|
+
|
301
|
+
assert_equal Regular, post.class
|
302
|
+
assert_equal 123459291, post.post_id
|
303
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123459291", post.url
|
304
|
+
assert_equal "http://tumblr4rtest.tumblr.com/post/123459291/regular-test", post.url_with_slug
|
305
|
+
assert_equal "regular", post.type
|
306
|
+
assert_equal "2009-06-14 16:30:44 GMT", post.date_gmt
|
307
|
+
assert_equal "Mon, 15 Jun 2009 01:30:44", post.date
|
308
|
+
assert_equal 1244997044, post.unix_timestamp
|
309
|
+
assert_equal "html", post.format
|
310
|
+
assert_equal ["test", "regular"], post.tags
|
311
|
+
assert_equal false, post.bookmarklet
|
312
|
+
|
313
|
+
assert_equal "Text Postのテストです", post.regular_title
|
314
|
+
assert_equal <<EOF.chomp, post.regular_body
|
315
|
+
<p>テキストです。</p>
|
316
|
+
<p><b>ボールドです。</b></p>
|
317
|
+
<p><i>イタリックです。</i></p>
|
318
|
+
<p><strike>取り消し線です。</strike></p>
|
319
|
+
<ul>
|
320
|
+
<li>unordered 1</li>
|
321
|
+
<li>unordered 2</li>
|
322
|
+
</ul>
|
323
|
+
<ol>
|
324
|
+
<li>ordered 1</li>
|
325
|
+
<li>ordered 2</li>
|
326
|
+
</ol>
|
327
|
+
<p>ここからインデント</p>
|
328
|
+
<blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;">インデント開始<br/>ああああ<br/>インデント終了</blockquote>
|
329
|
+
<p>ここまでインデント</p>
|
330
|
+
EOF
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_quote_create
|
334
|
+
quotes = @site.find(:all, :type => Tumblr4r::POST_TYPE::QUOTE)
|
335
|
+
post = @write_site.save(quotes[0])
|
336
|
+
assert_not_equal quotes[0].post_id, post.post_id
|
337
|
+
assert_equal "http://#{WRITE_TEST_HOST}/post/#{post.post_id}", post.url
|
338
|
+
assert_equal "http://#{WRITE_TEST_HOST}/post/#{post.post_id}/tumblelog-tumblr", post.url_with_slug
|
339
|
+
assert_equal Tumblr4r::POST_TYPE::QUOTE, post.type
|
340
|
+
assert_equal quotes[0].date_gmt, post.date_gmt
|
341
|
+
assert_equal quotes[0].date, post.date
|
342
|
+
# TODO
|
343
|
+
# assert_equal 1245045480, post.unix_timestamp
|
344
|
+
assert_equal quotes[0].format, post.format
|
345
|
+
assert_equal quotes[0].tags, post.tags
|
346
|
+
assert_equal false, post.bookmarklet
|
347
|
+
|
348
|
+
assert_equal quotes[0].quote_text, post.quote_text
|
349
|
+
assert_equal quotes[0].quote_source, post.quote_source
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_regular_create
|
353
|
+
regulars = @site.find(:all, :type => Tumblr4r::POST_TYPE::REGULAR)
|
354
|
+
post = @write_site.save(regulars[0])
|
355
|
+
end
|
356
|
+
|
357
|
+
def test_link_create
|
358
|
+
links = @site.find(:all, :type => Tumblr4r::POST_TYPE::LINK)
|
359
|
+
post = @write_site.save(links[0])
|
360
|
+
end
|
361
|
+
|
362
|
+
def test_photo_create
|
363
|
+
photos = @site.find(:all, :type => Tumblr4r::POST_TYPE::PHOTO)
|
364
|
+
post = @write_site.save(photos[0])
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
# def test_audio_create
|
369
|
+
# audios = @site.find(:all, :type => Tumblr4r::POST_TYPE::AUDIO)
|
370
|
+
# post = @write_site.save(audios[0])
|
371
|
+
# end
|
372
|
+
|
373
|
+
def test_video_create
|
374
|
+
videos = @site.find(:all, :type => Tumblr4r::POST_TYPE::VIDEO)
|
375
|
+
post = @write_site.save(videos[0])
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tumblr4r
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomoki MAEDA
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-09 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.3.2
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: pit
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.6
|
34
|
+
version:
|
35
|
+
description: Tumblr API Wrapper for Ruby
|
36
|
+
email: tmaeda@ruby-sapporo.org
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
- ChangeLog
|
44
|
+
files:
|
45
|
+
- README
|
46
|
+
- ChangeLog
|
47
|
+
- Rakefile
|
48
|
+
- test/test_helper.rb
|
49
|
+
- test/tumblr4r_test.rb
|
50
|
+
- lib/tumblr4r.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://tumblr4r.rubyforge.org
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --title
|
56
|
+
- tumblr4r documentation
|
57
|
+
- --charset
|
58
|
+
- utf-8
|
59
|
+
- --opname
|
60
|
+
- index.html
|
61
|
+
- --line-numbers
|
62
|
+
- --main
|
63
|
+
- README
|
64
|
+
- --inline-source
|
65
|
+
- --exclude
|
66
|
+
- ^(examples|extras)/
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.8.6
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: tumblr4r
|
84
|
+
rubygems_version: 1.3.1
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: Tumblr API Wrapper for Ruby
|
88
|
+
test_files:
|
89
|
+
- test/tumblr4r_test.rb
|