tweetomator 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/composers/composer.rb +18 -0
- data/lib/composers/markov_composer.rb +66 -0
- data/lib/composers/random_composer.rb +21 -10
- data/lib/config/config.rb +2 -1
- data/lib/finders/tweet_finder.rb +1 -14
- data/lib/io/text_reader.rb +15 -0
- data/lib/tweetomator/version.rb +1 -1
- data/lib/tweetomator.rb +8 -8
- data/test/lib/composers/markov_composer_test.rb +36 -0
- data/test/lib/composers/random_composer_test.rb +12 -6
- data/test/lib/finders/tweet_finder_test.rb +1 -6
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 499c4df1bff2a96595cffa6188a37a5d3aa05e87
|
4
|
+
data.tar.gz: 853355749050b089c4bd9a80d3ebb8b6aa923853
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a184e4fdbb68947988767bc8b2ef1a2e85260f3b624fc2e2b3fa4eb350f492a285fa24cfec06765b212e2578a22f187f69ced5cf4a51d01c51bb384ddc0864e3
|
7
|
+
data.tar.gz: f41e1638fac1a420ef0610aeab93583786f9e4ad36fef8087b44acec8e48252b2a26734f4d025c7a51387b734921268a3784520fce3d9b18467f02e8065cf4f6
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Composer
|
2
|
+
def initialize(config, twitter_client, tweet_finder, emoji_finder)
|
3
|
+
@blacklist = config.blacklist
|
4
|
+
@max_length = config.max_length
|
5
|
+
@stop_words = config.stop_words
|
6
|
+
@twitter_client = twitter_client
|
7
|
+
@tweet_finder = tweet_finder
|
8
|
+
@emoji_finder = emoji_finder
|
9
|
+
end
|
10
|
+
|
11
|
+
def compose_tweet
|
12
|
+
raise NoMethodError 'implement compose_tweet'
|
13
|
+
end
|
14
|
+
|
15
|
+
def compose_image_query(query)
|
16
|
+
raise NoMethodError 'implement compose_image_query(query)'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative '../tweet'
|
2
|
+
require_relative '../io/text_reader'
|
3
|
+
require_relative 'composer'
|
4
|
+
|
5
|
+
class MarkovComposer < Composer
|
6
|
+
def initialize(config, twitter_client, tweet_finder, emoji_finder)
|
7
|
+
super(config, twitter_client, tweet_finder, emoji_finder)
|
8
|
+
@chain = {}
|
9
|
+
reader = TextReader.new(config.input_text_file)
|
10
|
+
reader.process_input_text_file
|
11
|
+
@beginnings = reader.beginnings
|
12
|
+
@lines = reader.lines
|
13
|
+
build_chain(@lines.join.split)
|
14
|
+
end
|
15
|
+
|
16
|
+
def compose_tweet
|
17
|
+
text = ''
|
18
|
+
word = @beginnings.sample
|
19
|
+
until text.count('.') == 1 || text.length + word.length + 1 >= @max_length
|
20
|
+
text = "#{text}#{word} "
|
21
|
+
word = get(word)
|
22
|
+
end
|
23
|
+
text = clean_up(text)
|
24
|
+
hashtag = @lines.sample.split.reject { |w| w.strip.nil? || /[^A-Za-z]/.match(w.strip) != nil || @stop_words.include?(w.strip) }.sample
|
25
|
+
if hashtag != nil && (text.length + hashtag.length + 1) < @max_length && (1..4).to_a.sample == 4
|
26
|
+
return Tweet.new(clean_up(text), hashtag.downcase, @twitter_client)
|
27
|
+
end
|
28
|
+
return Tweet.new(clean_up(text), '', @twitter_client)
|
29
|
+
end
|
30
|
+
|
31
|
+
def compose_image_query(query)
|
32
|
+
"##{@chain.keys.reject { |k| @stop_words.include?(k) }.sample}"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def build_chain(words)
|
38
|
+
words.each_with_index do |word, i|
|
39
|
+
add(word, words[i + 1]) unless i >= words.size - 2
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add(word, next_word)
|
44
|
+
@chain[word] = Hash.new(0) if @chain[word] == nil
|
45
|
+
@chain[word][next_word] += 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(word)
|
49
|
+
return '' if @chain[word] == nil
|
50
|
+
sum = @chain[word].reduce(0) { |sum, kv| sum += kv[1] }
|
51
|
+
random = rand(sum) + 1
|
52
|
+
count = 0
|
53
|
+
@chain[word].find do |w, i|
|
54
|
+
count += i
|
55
|
+
count >= random
|
56
|
+
end.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def clean_up(text)
|
60
|
+
text = text.strip.capitalize
|
61
|
+
if text[text.length-1].match('[A-Za-z]') != nil && text.length < @max_length
|
62
|
+
text << '.'
|
63
|
+
end
|
64
|
+
text
|
65
|
+
end
|
66
|
+
end
|
@@ -1,19 +1,16 @@
|
|
1
|
-
require 'cgi'
|
2
1
|
require_relative '../tweet'
|
2
|
+
require_relative 'composer'
|
3
|
+
require 'cgi'
|
3
4
|
|
4
|
-
class RandomComposer
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
@emoji_finder = emoji_finder
|
8
|
-
@blacklist = blacklist
|
9
|
-
@twitter_client = twitter_client
|
10
|
-
@max_length = max_length
|
5
|
+
class RandomComposer < Composer
|
6
|
+
def initialize(config, twitter_client, tweet_finder, emoji_finder)
|
7
|
+
super(config, twitter_client, tweet_finder, emoji_finder)
|
11
8
|
end
|
12
9
|
|
13
|
-
def
|
10
|
+
def compose_tweet
|
14
11
|
tweets = @tweet_finder.find_tweets
|
15
12
|
tweets = tweets.map { |t| normalize(t) }
|
16
|
-
hashtag =
|
13
|
+
hashtag = find_random_word(tweets.sample)
|
17
14
|
emoji = @emoji_finder.find
|
18
15
|
text = tweets.join([', ', ' - ', '! '].sample)
|
19
16
|
text = filter(text)
|
@@ -23,8 +20,22 @@ class RandomComposer
|
|
23
20
|
Tweet.new(text.capitalize, hashtag, @twitter_client)
|
24
21
|
end
|
25
22
|
|
23
|
+
def compose_image_query(query)
|
24
|
+
find_random_word(query)
|
25
|
+
end
|
26
|
+
|
26
27
|
private
|
27
28
|
|
29
|
+
def find_random_word(query)
|
30
|
+
term = extract_one_word(query)
|
31
|
+
tweet = @tweet_finder.find_tweet(term)
|
32
|
+
extract_one_word(tweet)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_one_word(tweet)
|
36
|
+
tweet.split.reject { |w| /[^A-Za-z0-9]/.match(w.strip) != nil || @stop_words.include?(w.strip) }.sample
|
37
|
+
end
|
38
|
+
|
28
39
|
def normalize(tweet)
|
29
40
|
CGI.unescapeHTML(tweet.gsub(/(@([^\s])+|http([^\s])+|([^\s])+\/([^\s])+|)/, ''))
|
30
41
|
.gsub(/\s+/, ' ')
|
data/lib/config/config.rb
CHANGED
@@ -39,12 +39,13 @@ class TweetomatorConfig
|
|
39
39
|
|
40
40
|
attr_reader :twitter_config, :flickr_config, :topics, :blacklist, :stop_words, :delay_in_seconds
|
41
41
|
|
42
|
-
def initialize(twitter_config, flickr_config, topics, blacklist, stop_words, delay_in_seconds)
|
42
|
+
def initialize(twitter_config, flickr_config, topics, blacklist, stop_words, delay_in_seconds, composer_type)
|
43
43
|
@twitter_config = twitter_config
|
44
44
|
@flickr_config = flickr_config
|
45
45
|
@topics = topics
|
46
46
|
@blacklist = blacklist
|
47
47
|
@stop_words = stop_words
|
48
48
|
@delay_in_seconds = delay_in_seconds
|
49
|
+
@composer_type = composer_type
|
49
50
|
end
|
50
51
|
end
|
data/lib/finders/tweet_finder.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
class TweetFinder
|
2
|
-
def initialize(client, topics
|
2
|
+
def initialize(client, topics)
|
3
3
|
@client = client
|
4
4
|
@topics = topics
|
5
|
-
@stop_words = stop_words
|
6
5
|
end
|
7
6
|
|
8
7
|
def find_tweets
|
@@ -14,16 +13,4 @@ class TweetFinder
|
|
14
13
|
def find_tweet(query)
|
15
14
|
@client.search(query)
|
16
15
|
end
|
17
|
-
|
18
|
-
def find_random_word(query)
|
19
|
-
term = extract_one_word(query)
|
20
|
-
tweet = find_tweet(term)
|
21
|
-
extract_one_word(tweet)
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def extract_one_word(tweet)
|
27
|
-
tweet.split.reject { |w| /[^A-Za-z0-9]/.match(w.strip) != nil || @stop_words.include?(w.strip) }.sample
|
28
|
-
end
|
29
16
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TextReader
|
2
|
+
attr_accessor :beginnings, :lines
|
3
|
+
|
4
|
+
def initialize(input_text_file)
|
5
|
+
@input_text_file = input_text_file
|
6
|
+
@beginnings = []
|
7
|
+
@lines = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_input_text_file
|
11
|
+
@lines = File.readlines(@input_text_file)
|
12
|
+
@beginnings = @lines.collect { |s| s.split(' ').first }.reject { |w| w.nil? }
|
13
|
+
return self
|
14
|
+
end
|
15
|
+
end
|
data/lib/tweetomator/version.rb
CHANGED
data/lib/tweetomator.rb
CHANGED
@@ -35,20 +35,20 @@ class Tweetomator
|
|
35
35
|
def somebody_set_us_up_the_bot(config)
|
36
36
|
@delay = config.delay_in_seconds
|
37
37
|
@twitter_client = TwitterClient.new(config.twitter_config)
|
38
|
-
|
38
|
+
tweet_finder = TweetFinder.new(@twitter_client, config.topics)
|
39
39
|
flickr_client = FlickrClient.new(config.flickr_config)
|
40
40
|
@flickr_finder = FlickrFindr.new(flickr_client)
|
41
41
|
emoji_finder = EmojiFinder.new
|
42
|
-
@
|
42
|
+
@composer = config.composer_type.class.new(config, @twitter_client, tweet_finder, emoji_finder)
|
43
43
|
@downloader = Downloader.new
|
44
44
|
@scheduler = Rufus::Scheduler.new
|
45
45
|
end
|
46
46
|
|
47
47
|
def do_stuff
|
48
|
-
r = (1..
|
49
|
-
if r <
|
48
|
+
r = (1..10).to_a.sample
|
49
|
+
if r < 2
|
50
50
|
tweet_with_image!
|
51
|
-
elsif r <
|
51
|
+
elsif r < 9
|
52
52
|
tweet!
|
53
53
|
else
|
54
54
|
follow!
|
@@ -56,13 +56,13 @@ class Tweetomator
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def tweet!
|
59
|
-
tweet = @
|
59
|
+
tweet = @composer.compose_tweet
|
60
60
|
tweet.post!
|
61
61
|
end
|
62
62
|
|
63
63
|
def tweet_with_image!
|
64
|
-
tweet = @
|
65
|
-
flickr_query = @
|
64
|
+
tweet = @composer.compose_tweet
|
65
|
+
flickr_query = @composer.compose_image_query(tweet.hashtag)
|
66
66
|
image_uri = @flickr_finder.find(flickr_query)
|
67
67
|
image_local = @downloader.download(image_uri, 'image')
|
68
68
|
tweet.post!(image_local)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../../../lib/composers/markov_composer'
|
3
|
+
require_relative '../../../lib/tweetomator'
|
4
|
+
|
5
|
+
class TestMarkovComposer < Minitest::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
cool_story = "So there I am, in Sri Lanka, formerly Ceylon, at about 3 o'clock in the morning, \nlooking for one thousand brown M&Ms to fill a brandy glass, or Ozzy wouldn't go on stage that night. So, Jeff Beck pops his head 'round the door, \n and mentions there's a little sweets shop on the edge of town. So - we go. And - it's closed. So there's me, and Keith Moon, and David Crosby, breaking into that little sweets shop, eh. Well, instead of a guard dog, they've got this bloody great big Bengal tiger. I managed to take out the tiger with a can of mace, but the shopkeeper and his son... that's a different story altogether. I had to beat them to death with their own shoes. Nasty business, really, but sure enough I got the M&Ms, and Ozzy went on stage and did a great show."
|
8
|
+
riveting_tale = "And even in this cosmopolitan trend, he did that precede and open the way to other more advanced civilizations. \n About henceforth in Europe, no cease to be a citizen of his country, it is not, in any way, International? no one \n can shut, like the snail, by its shell: all need air, space wider for match the new needs, their aspirations."
|
9
|
+
@amazing_narrative = "#{cool_story} #{riveting_tale}"
|
10
|
+
mock_emoji_finder = Minitest::Mock.new
|
11
|
+
mock_emoji_finder.expect :find, ':trollface:'
|
12
|
+
mock_tweet_finder = Minitest::Mock.new
|
13
|
+
mock_tweet_client = Minitest::Mock.new
|
14
|
+
@max_length = 100
|
15
|
+
@blacklist = %w(sri lanka)
|
16
|
+
@stop_words = %w(Potato Salad)
|
17
|
+
mock_config = Minitest::Mock.new
|
18
|
+
mock_config.expect :max_length, @max_length
|
19
|
+
mock_config.expect :blacklist, @blacklist
|
20
|
+
mock_config.expect :stop_words, @stop_words
|
21
|
+
mock_config.expect :input_text_file, 'text.txt'
|
22
|
+
mock_text_reader = MiniTest::Mock.new
|
23
|
+
mock_text_reader.expect :process_input_text_file, mock_text_reader
|
24
|
+
mock_text_reader.expect :beginnings, @amazing_narrative.split
|
25
|
+
mock_text_reader.expect :lines, @amazing_narrative.split("\n")
|
26
|
+
TextReader.stub :new, mock_text_reader do
|
27
|
+
@markov_composer = MarkovComposer.new(mock_config, mock_tweet_client, mock_tweet_finder, mock_emoji_finder)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_that_tweet_is_short_enough
|
32
|
+
tweet = @markov_composer.compose_tweet
|
33
|
+
puts tweet.with_hashtag
|
34
|
+
assert tweet.with_hashtag.length <= @max_length, true
|
35
|
+
end
|
36
|
+
end
|
@@ -11,25 +11,31 @@ class TestRandomComposer < Minitest::Unit::TestCase
|
|
11
11
|
mock_emoji_finder.expect :find, ':trollface:'
|
12
12
|
mock_tweet_finder = Minitest::Mock.new
|
13
13
|
mock_tweet_finder.expect :find_tweets, [tweet_1, tweet_2, cool_story]
|
14
|
+
mock_tweet_finder.expect :find_tweet, tweet_1, [String]
|
14
15
|
mock_tweet_finder.expect :find_random_word, 'amazing', [String]
|
15
16
|
mock_tweet_client = Minitest::Mock.new
|
16
|
-
@blacklist = %w(sri lanka)
|
17
17
|
@max_length = 100
|
18
|
-
@
|
18
|
+
@blacklist = %w(sri lanka)
|
19
|
+
@stop_words = %w(Potato Salad)
|
20
|
+
mock_config = Minitest::Mock.new
|
21
|
+
mock_config.expect :max_length, @max_length
|
22
|
+
mock_config.expect :blacklist, @blacklist
|
23
|
+
mock_config.expect :stop_words, @stop_words
|
24
|
+
@random_composer = RandomComposer.new(mock_config, mock_tweet_client, mock_tweet_finder, mock_emoji_finder)
|
19
25
|
end
|
20
26
|
|
21
27
|
def test_that_tweet_is_short_enough
|
22
|
-
tweet = @random_composer.
|
28
|
+
tweet = @random_composer.compose_tweet
|
23
29
|
assert tweet.with_hashtag.length <= @max_length, true
|
24
30
|
end
|
25
31
|
|
26
|
-
def
|
27
|
-
tweet = @random_composer.
|
32
|
+
def test_that_blacklisted_words_are_removed
|
33
|
+
tweet = @random_composer.compose_tweet
|
28
34
|
refute tweet.with_hashtag.split.any? { |word| @blacklist.include?(word) }, true
|
29
35
|
end
|
30
36
|
|
31
37
|
def test_that_links_are_removed
|
32
|
-
tweet = @random_composer.
|
38
|
+
tweet = @random_composer.compose_tweet
|
33
39
|
refute tweet.with_hashtag.split.any? { |word| %w(kck.st/1pLytCe http://nyan.cat).include?(word) }, true
|
34
40
|
end
|
35
41
|
end
|
@@ -7,16 +7,11 @@ class TestTweetFinder < Minitest::Unit::TestCase
|
|
7
7
|
mock_client = Minitest::Mock.new
|
8
8
|
3.times { mock_client.expect :search, search_result, [String] }
|
9
9
|
stop_words = search_result.split
|
10
|
-
@finder = TweetFinder.new(mock_client, ['Trumpets', 'Bookmarks', 'Potato Salads']
|
10
|
+
@finder = TweetFinder.new(mock_client, ['Trumpets', 'Bookmarks', 'Potato Salads'])
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_that_it_finds_a_tweet_per_query
|
14
14
|
tweets = @finder.find_tweets
|
15
15
|
assert_equal tweets.size, 3
|
16
16
|
end
|
17
|
-
|
18
|
-
def test_stop_words_are_filtered_from_find_random_word
|
19
|
-
word = @finder.find_random_word('something')
|
20
|
-
assert_equal word, nil
|
21
|
-
end
|
22
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tweetomator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nat Young
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -110,15 +110,19 @@ files:
|
|
110
110
|
- Rakefile
|
111
111
|
- lib/clients/flickr_client.rb
|
112
112
|
- lib/clients/twitter_client.rb
|
113
|
+
- lib/composers/composer.rb
|
114
|
+
- lib/composers/markov_composer.rb
|
113
115
|
- lib/composers/random_composer.rb
|
114
116
|
- lib/config/config.rb
|
115
117
|
- lib/finders/emoji_finder.rb
|
116
118
|
- lib/finders/flickr_findr.rb
|
117
119
|
- lib/finders/tweet_finder.rb
|
118
120
|
- lib/io/downloader.rb
|
121
|
+
- lib/io/text_reader.rb
|
119
122
|
- lib/tweet.rb
|
120
123
|
- lib/tweetomator.rb
|
121
124
|
- lib/tweetomator/version.rb
|
125
|
+
- test/lib/composers/markov_composer_test.rb
|
122
126
|
- test/lib/composers/random_composer_test.rb
|
123
127
|
- test/lib/finders/flickr_findr_test.rb
|
124
128
|
- test/lib/finders/tweet_finder_test.rb
|
@@ -148,6 +152,8 @@ signing_key:
|
|
148
152
|
specification_version: 4
|
149
153
|
summary: Twitter automation for the time poor.
|
150
154
|
test_files:
|
155
|
+
- test/lib/composers/markov_composer_test.rb
|
151
156
|
- test/lib/composers/random_composer_test.rb
|
152
157
|
- test/lib/finders/flickr_findr_test.rb
|
153
158
|
- test/lib/finders/tweet_finder_test.rb
|
159
|
+
has_rdoc:
|