chatterbot 0.4.0 → 0.5.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/Gemfile +0 -1
- data/README.markdown +37 -12
- data/chatterbot.gemspec +1 -4
- data/examples/tweet_logger.rb +68 -0
- data/lib/chatterbot.rb +1 -0
- data/lib/chatterbot/bot.rb +1 -0
- data/lib/chatterbot/client.rb +1 -4
- data/lib/chatterbot/dsl.rb +26 -16
- data/lib/chatterbot/retweet.rb +17 -0
- data/lib/chatterbot/version.rb +1 -1
- data/spec/dsl_spec.rb +16 -7
- data/spec/retweet_spec.rb +27 -0
- metadata +17 -25
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -36,7 +36,7 @@ Write your bot
|
|
36
36
|
--------------
|
37
37
|
|
38
38
|
Chatterbot has a very simple DSL inspired by Sinatra and Twibot, an
|
39
|
-
earlier Twitter bot framework. Here's an example, based on
|
39
|
+
earlier Twitter bot framework. Here's an example, based on
|
40
40
|
[@dr_rumack](http://twitter.com/#!/Dr_Rumack), an actual bot running
|
41
41
|
on Twitter:
|
42
42
|
|
@@ -60,7 +60,7 @@ Authorization
|
|
60
60
|
If you only want to use Chatterbot to search for tweets, it will work
|
61
61
|
out of the box without any authorization. However, if you want to
|
62
62
|
reply to tweets, or check for replies to your bot, you will have to
|
63
|
-
jump through a few authorization hoops with Twitter.
|
63
|
+
jump through a few authorization hoops with Twitter.
|
64
64
|
|
65
65
|
Before you setup a bot for the first time, you will need to register an
|
66
66
|
application with Twitter. Twitter requires all API communication to be via an
|
@@ -78,11 +78,9 @@ the instructions if you want to do it yourself:
|
|
78
78
|
|
79
79
|
1. [Setup your own app](https://twitter.com/apps/new) on Twitter.
|
80
80
|
|
81
|
-
2.
|
81
|
+
2. Put in whatever name, description, and website you want.
|
82
82
|
|
83
|
-
3.
|
84
|
-
|
85
|
-
4. Take the consumer key/consumer secret values, and either run your bot, and enter them
|
83
|
+
3. Take the consumer key/consumer secret values, and either run your bot, and enter them
|
86
84
|
in when prompted, or store them in a config file for your bot. (See
|
87
85
|
below for details on this). It should look like this:
|
88
86
|
|
@@ -141,13 +139,40 @@ Run it via cron. Here's an example of running a bot every two minutes
|
|
141
139
|
|
142
140
|
Run it as a background process. Just put the guts of your bot in a loop like this:
|
143
141
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
142
|
+
```rb
|
143
|
+
loop do
|
144
|
+
search "twitter" do |tweet|
|
145
|
+
# here you could do something with a tweet
|
146
|
+
# if you want to retweet
|
147
|
+
retweet(tweet[:id])
|
148
|
+
end
|
149
|
+
|
150
|
+
replies do |tweet|
|
151
|
+
# do stuff
|
152
|
+
end
|
153
|
+
|
154
|
+
# explicitly update our config
|
155
|
+
update_config
|
156
|
+
|
157
|
+
sleep 60
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
**NOTE:** You need to call `update_config` to update the last tweet your script
|
162
|
+
has processed -- if you don't have this call, you will get duplicate
|
163
|
+
tweets.
|
164
|
+
|
165
|
+
Retweet
|
166
|
+
-------
|
167
|
+
|
168
|
+
Chatterbot can retweet the tweets found based upon the search:
|
169
|
+
|
170
|
+
```rb
|
171
|
+
search "xyzzy" do |tweet|
|
172
|
+
retweet(tweet[:id])
|
173
|
+
end
|
174
|
+
```
|
148
175
|
|
149
|
-
sleep 60
|
150
|
-
end
|
151
176
|
|
152
177
|
Database logging
|
153
178
|
----------------
|
data/chatterbot.gemspec
CHANGED
@@ -28,7 +28,6 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
29
29
|
s.add_development_dependency(%q<rspec>, [">= 0"])
|
30
30
|
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
31
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
32
31
|
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
33
32
|
s.add_development_dependency(%q<watchr>, [">= 0"])
|
34
33
|
else
|
@@ -36,7 +35,6 @@ Gem::Specification.new do |s|
|
|
36
35
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
37
36
|
s.add_dependency(%q<rspec>, [">= 0"])
|
38
37
|
s.add_dependency(%q<rdoc>, [">= 0"])
|
39
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
40
38
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
41
39
|
s.add_dependency(%q<watchr>, [">= 0"])
|
42
40
|
end
|
@@ -44,8 +42,7 @@ Gem::Specification.new do |s|
|
|
44
42
|
s.add_dependency(%q<twitter_oauth>, [">= 0"])
|
45
43
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
46
44
|
s.add_dependency(%q<rspec>, [">= 0"])
|
47
|
-
|
48
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
45
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
49
46
|
s.add_dependency(%q<rcov>, [">= 0"])
|
50
47
|
s.add_dependency(%q<watchr>, [">= 0"])
|
51
48
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chatterbot/dsl'
|
4
|
+
|
5
|
+
##
|
6
|
+
#
|
7
|
+
# Simple example of a bot that will log tweets to the
|
8
|
+
# database. Chatterbot will only log outgoing tweets by default, and
|
9
|
+
# coding incoming tweet processing seems problematic right now, but
|
10
|
+
# this is pretty straightforward
|
11
|
+
#
|
12
|
+
|
13
|
+
#
|
14
|
+
# Set some date defaults for Sequel
|
15
|
+
#
|
16
|
+
Sequel.datetime_class = DateTime
|
17
|
+
Sequel.default_timezone = :utc
|
18
|
+
|
19
|
+
#
|
20
|
+
# grab a copy of the db handle
|
21
|
+
#
|
22
|
+
db = bot.db
|
23
|
+
|
24
|
+
#
|
25
|
+
# create a table to hold search results
|
26
|
+
#
|
27
|
+
if ! db.tables.include?(:searches)
|
28
|
+
|
29
|
+
#
|
30
|
+
# if there's other data you want to track, you can add it here
|
31
|
+
#
|
32
|
+
db.create_table :searches do
|
33
|
+
primary_key :id, :type=>Bignum
|
34
|
+
|
35
|
+
String :text
|
36
|
+
String :from_user
|
37
|
+
String :from_user_id
|
38
|
+
|
39
|
+
String :to_user
|
40
|
+
String :to_user_id
|
41
|
+
|
42
|
+
String :in_reply_to_status_id
|
43
|
+
|
44
|
+
DateTime :created_at
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
cols = db[:searches].columns
|
49
|
+
|
50
|
+
#
|
51
|
+
# run a search
|
52
|
+
#
|
53
|
+
search("foo", :lang => "en") do |tweet|
|
54
|
+
|
55
|
+
#
|
56
|
+
# reject anything from the incoming tweet that doesn't have a
|
57
|
+
# matching column
|
58
|
+
#
|
59
|
+
data = tweet.delete_if { |k, v|
|
60
|
+
! cols.include?(k)
|
61
|
+
}
|
62
|
+
|
63
|
+
# update timestamp manually -- sequel isn't doing it right
|
64
|
+
data[:created_at] ||= Sequel.string_to_datetime(data[:created_at])
|
65
|
+
|
66
|
+
# store to the db!
|
67
|
+
db[:searches].insert(data)
|
68
|
+
end
|
data/lib/chatterbot.rb
CHANGED
data/lib/chatterbot/bot.rb
CHANGED
data/lib/chatterbot/client.rb
CHANGED
@@ -70,10 +70,7 @@ module Chatterbot
|
|
70
70
|
puts "Hey, looks like you need to get an API key from Twitter before you can get started."
|
71
71
|
puts "Please go to this URL: https://twitter.com/apps/new"
|
72
72
|
|
73
|
-
|
74
|
-
puts "Choose 'Read & Write' access."
|
75
|
-
|
76
|
-
print "\n\n\nPaste the 'Consumer Key' here: "
|
73
|
+
print "\n\nPaste the 'Consumer Key' here: "
|
77
74
|
STDOUT.flush
|
78
75
|
config[:consumer_key] = STDIN.readline.chomp
|
79
76
|
|
data/lib/chatterbot/dsl.rb
CHANGED
@@ -11,19 +11,19 @@ module Chatterbot
|
|
11
11
|
# otherwise create a bot and return that
|
12
12
|
def bot
|
13
13
|
return @bot unless @bot.nil?
|
14
|
-
|
14
|
+
|
15
15
|
#
|
16
16
|
# parse any command-line options and use them to initialize the bot
|
17
17
|
#
|
18
18
|
params = {}
|
19
|
-
|
19
|
+
|
20
20
|
opts = OptionParser.new
|
21
21
|
|
22
22
|
opts.banner = "Usage: #{File.basename($0)} [options]"
|
23
23
|
|
24
24
|
opts.separator ""
|
25
|
-
opts.separator "Specific options:"
|
26
|
-
|
25
|
+
opts.separator "Specific options:"
|
26
|
+
|
27
27
|
opts.on('-d', '--db [ARG]', "Specify a DB connection URI") { |d| ENV["chatterbot_db"] = d }
|
28
28
|
opts.on('-c', '--config [ARG]', "Specify a config file to use") { |c| ENV["chatterbot_config"] = c }
|
29
29
|
opts.on('-t', '--test', "Run the bot without actually sending any tweets") { params[:debug_mode] = true }
|
@@ -34,8 +34,8 @@ module Chatterbot
|
|
34
34
|
opts.on_tail("-h", "--help", "Show this message") do
|
35
35
|
puts opts
|
36
36
|
exit
|
37
|
-
end
|
38
|
-
|
37
|
+
end
|
38
|
+
|
39
39
|
opts.parse!(ARGV)
|
40
40
|
|
41
41
|
@bot = Chatterbot::Bot.new(params)
|
@@ -80,37 +80,37 @@ module Chatterbot
|
|
80
80
|
end
|
81
81
|
end.flatten
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
#
|
85
|
-
# specify a bot-specific blacklist of users. accepts an array, or a
|
85
|
+
# specify a bot-specific blacklist of users. accepts an array, or a
|
86
86
|
# comma-delimited string
|
87
87
|
def blacklist(*args)
|
88
88
|
list = flatten_list_of_strings(args)
|
89
|
-
|
89
|
+
|
90
90
|
if list.nil? || list.empty?
|
91
91
|
bot.blacklist = []
|
92
|
-
else
|
92
|
+
else
|
93
93
|
bot.blacklist += list
|
94
94
|
end
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
#
|
98
98
|
# specify list of strings we will check when deciding to respond to a tweet or not
|
99
99
|
def exclude(*args)
|
100
100
|
e = flatten_list_of_strings(args)
|
101
101
|
if e.nil? || e.empty?
|
102
102
|
bot.exclude = []
|
103
|
-
else
|
103
|
+
else
|
104
104
|
bot.exclude += e
|
105
105
|
end
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
#
|
109
109
|
# search twitter for the specified terms
|
110
110
|
def search(query, opts = {}, &block)
|
111
111
|
bot.search(query, opts, &block)
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
114
|
#
|
115
115
|
# handle replies to the bot
|
116
116
|
def replies(&block)
|
@@ -121,7 +121,13 @@ module Chatterbot
|
|
121
121
|
# send a tweet
|
122
122
|
def tweet(txt, params = {}, original = nil)
|
123
123
|
bot.tweet(txt, params, original)
|
124
|
-
end
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# retweet
|
128
|
+
def retweet(id)
|
129
|
+
bot.retweet(id)
|
130
|
+
end
|
125
131
|
|
126
132
|
#
|
127
133
|
# reply to a tweet
|
@@ -132,10 +138,14 @@ module Chatterbot
|
|
132
138
|
def since_id
|
133
139
|
bot.config[:since_id]
|
134
140
|
end
|
135
|
-
|
141
|
+
|
136
142
|
def update_config
|
137
143
|
bot.update_config
|
138
144
|
end
|
145
|
+
|
146
|
+
def db
|
147
|
+
bot.db
|
148
|
+
end
|
139
149
|
end
|
140
150
|
end
|
141
151
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Chatterbot
|
2
|
+
|
3
|
+
# routines for retweet
|
4
|
+
module Retweet
|
5
|
+
|
6
|
+
# simple wrapper for retweeting a message
|
7
|
+
def retweet(id)
|
8
|
+
return if require_login == false
|
9
|
+
|
10
|
+
if debug_mode?
|
11
|
+
debug "I'm in debug mode, otherwise I would retweet with tweet id: #{id}"
|
12
|
+
else
|
13
|
+
client.retweet id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/chatterbot/version.rb
CHANGED
data/spec/dsl_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe "Chatterbot::DSL" do
|
|
8
8
|
|
9
9
|
Chatterbot::DSL.stub!(:bot).and_return(@bot)
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
describe "blacklist" do
|
13
13
|
it "#blacklist passes along to bot object" do
|
14
14
|
@bot.should_receive(:blacklist=).with(["foo"])
|
@@ -39,12 +39,12 @@ describe "Chatterbot::DSL" do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it "#debug_mode with true is passed" do
|
42
|
-
@bot.should_receive("#{method.to_s}=").with(true)
|
42
|
+
@bot.should_receive("#{method.to_s}=").with(true)
|
43
43
|
send method, true
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
describe "exclude" do
|
49
49
|
it "#exclude passes along to bot object" do
|
50
50
|
@bot.should_receive(:exclude=).with(["foo"])
|
@@ -61,8 +61,8 @@ describe "Chatterbot::DSL" do
|
|
61
61
|
exclude "foo, bar"
|
62
62
|
end
|
63
63
|
end
|
64
|
-
|
65
|
-
|
64
|
+
|
65
|
+
|
66
66
|
it "#search passes along to bot object" do
|
67
67
|
@bot.should_receive(:search).with("foo", { })
|
68
68
|
search("foo")
|
@@ -72,7 +72,7 @@ describe "Chatterbot::DSL" do
|
|
72
72
|
@bot.should_receive(:search).with(["foo","bar"], { })
|
73
73
|
search(["foo","bar"])
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
it "#replies passes along to bot object" do
|
77
77
|
@bot.should_receive(:replies)
|
78
78
|
replies
|
@@ -86,7 +86,7 @@ describe "Chatterbot::DSL" do
|
|
86
86
|
it "#reply passes along to bot object" do
|
87
87
|
@bot.should_receive(:reply).with("hello sailor!", { :source => "source "})
|
88
88
|
reply "hello sailor!", { :source => "source "}
|
89
|
-
end
|
89
|
+
end
|
90
90
|
|
91
91
|
describe "update_config" do
|
92
92
|
it "should pass to bot object" do
|
@@ -102,5 +102,14 @@ describe "Chatterbot::DSL" do
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
describe "db" do
|
106
|
+
it "should pass to bot object" do
|
107
|
+
bot_db = mock(Object)
|
108
|
+
@bot.should_receive(:db).and_return(bot_db)
|
109
|
+
|
110
|
+
db.should eql(bot_db)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
105
114
|
end
|
106
115
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Chatterbot::Retweet" do
|
4
|
+
describe "#retweet" do
|
5
|
+
before(:each) do
|
6
|
+
@bot = test_bot
|
7
|
+
end
|
8
|
+
|
9
|
+
it "calls require_login when tweeting" do
|
10
|
+
@bot.should_receive(:require_login).and_return(false)
|
11
|
+
@bot.retweet "tweet test!"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "calls client.retweet with the right values" do
|
15
|
+
bot = test_bot
|
16
|
+
|
17
|
+
bot.should_receive(:require_login).and_return(true)
|
18
|
+
bot.stub!(:client).and_return(mock(TwitterOAuth::Client))
|
19
|
+
|
20
|
+
bot.stub!(:debug_mode?).and_return(false)
|
21
|
+
|
22
|
+
tweet_id = 12345
|
23
|
+
bot.client.should_receive(:retweet).with(tweet_id)
|
24
|
+
bot.retweet(tweet_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chatterbot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-12-
|
12
|
+
date: 2011-12-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: twitter_oauth
|
16
|
-
requirement: &
|
16
|
+
requirement: &17236180 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *17236180
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: shoulda
|
27
|
-
requirement: &
|
27
|
+
requirement: &17235680 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *17235680
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &17235200 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *17235200
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rdoc
|
49
|
-
requirement: &
|
49
|
+
requirement: &17234720 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,21 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
58
|
-
- !ruby/object:Gem::Dependency
|
59
|
-
name: bundler
|
60
|
-
requirement: &24048180 !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
|
-
requirements:
|
63
|
-
- - ~>
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
version: 1.0.0
|
66
|
-
type: :development
|
67
|
-
prerelease: false
|
68
|
-
version_requirements: *24048180
|
57
|
+
version_requirements: *17234720
|
69
58
|
- !ruby/object:Gem::Dependency
|
70
59
|
name: simplecov
|
71
|
-
requirement: &
|
60
|
+
requirement: &17234240 !ruby/object:Gem::Requirement
|
72
61
|
none: false
|
73
62
|
requirements:
|
74
63
|
- - ! '>='
|
@@ -76,10 +65,10 @@ dependencies:
|
|
76
65
|
version: '0'
|
77
66
|
type: :development
|
78
67
|
prerelease: false
|
79
|
-
version_requirements: *
|
68
|
+
version_requirements: *17234240
|
80
69
|
- !ruby/object:Gem::Dependency
|
81
70
|
name: watchr
|
82
|
-
requirement: &
|
71
|
+
requirement: &17259500 !ruby/object:Gem::Requirement
|
83
72
|
none: false
|
84
73
|
requirements:
|
85
74
|
- - ! '>='
|
@@ -87,7 +76,7 @@ dependencies:
|
|
87
76
|
version: '0'
|
88
77
|
type: :development
|
89
78
|
prerelease: false
|
90
|
-
version_requirements: *
|
79
|
+
version_requirements: *17259500
|
91
80
|
description: A framework for writing bots that run on Twitter. Comes with a simple
|
92
81
|
DSL for easy coding.
|
93
82
|
email: colin@muffinlabs.com
|
@@ -113,6 +102,7 @@ files:
|
|
113
102
|
- examples/dsl_test.rb
|
114
103
|
- examples/echoes_bot.rb
|
115
104
|
- examples/loop_bot.rb
|
105
|
+
- examples/tweet_logger.rb
|
116
106
|
- lib/chatterbot.rb
|
117
107
|
- lib/chatterbot/blacklist.rb
|
118
108
|
- lib/chatterbot/bot.rb
|
@@ -123,6 +113,7 @@ files:
|
|
123
113
|
- lib/chatterbot/helpers.rb
|
124
114
|
- lib/chatterbot/logging.rb
|
125
115
|
- lib/chatterbot/reply.rb
|
116
|
+
- lib/chatterbot/retweet.rb
|
126
117
|
- lib/chatterbot/search.rb
|
127
118
|
- lib/chatterbot/tweet.rb
|
128
119
|
- lib/chatterbot/version.rb
|
@@ -135,6 +126,7 @@ files:
|
|
135
126
|
- spec/helpers_spec.rb
|
136
127
|
- spec/logging_spec.rb
|
137
128
|
- spec/reply_spec.rb
|
129
|
+
- spec/retweet_spec.rb
|
138
130
|
- spec/search_spec.rb
|
139
131
|
- spec/spec_helper.rb
|
140
132
|
- spec/tweet_spec.rb
|