tweetomator 1.0.3 → 1.1.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.
- 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:
|