twittbot 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 09e59460e2557a87718a9bad72b57d1b42c2a4c3
4
+ data.tar.gz: 179a82d6023f5b2900d54841538e1aeb1b673564
5
+ SHA512:
6
+ metadata.gz: ed8bedb456e2ff5d26857f0758797e20f9ece05bca381e8125381807c0b91168fb20b90263cd162cb2cb6ce0560169c234743d0e07ff38e846df1bf0aa64dc84
7
+ data.tar.gz: fe77b02ea6703e0b22e8cf8004e10a05a6cae7381fca8120b29a95392cca7934ae4603ea8ab0a23e37a61162534c7da05e957d0345f307b301fb3527a134ebb7
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
16
+ *.gem
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --exclude /templates/*
2
+
3
+ *.rb lib/**/*.rb
4
+ -
5
+ *.md
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in twittbot.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Georg G.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Twittbot
2
+
3
+ Twittbot is the next generation of my old Twitter bot, `twittbot-nd`.
4
+
5
+ ## Installation
6
+
7
+ Installation is just as easy as installing the Rubygem:
8
+
9
+ $ gem install twittbot
10
+
11
+ ## Usage
12
+
13
+ Create a new bot:
14
+
15
+ $ twittbot new bot-name
16
+ $ cd bot-name
17
+
18
+ Authorize with Twitter:
19
+
20
+ $ twittbot auth
21
+
22
+ Add a template, such as for a simple reply bot:
23
+
24
+ $ twittbot generate random-reply
25
+
26
+ `random-reply` is a template that accepts a configuration. You can configure
27
+ it by editing `./etc/random_reply.yml` with a text editor of your choice.
28
+ And, if you want to, you can also change its behaviour by editing
29
+ `./lib/random_reply.rb`.
30
+
31
+ To list available templates, try this:
32
+
33
+ $ twittbot list-templates
34
+
35
+ Finally, run the bot:
36
+
37
+ $ twittbot start
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'yard'
3
+
4
+ YARD::Rake::YardocTask.new do |t|
5
+ t.files = %w(*.rb lib/*.rb lib/**/*.rb - *.md)
6
+ end
data/bin/twittbot ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'twittbot/cli'
4
+
5
+ Twittbot::CLI.start
data/lib/twittbot.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "twittbot/defaults"
2
+
3
+ module Twittbot
4
+ end
@@ -0,0 +1,199 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'twitter'
4
+
5
+ require 'twittbot/defaults'
6
+ require 'twittbot/botpart'
7
+ require 'twittbot/gem_ext/twitter'
8
+
9
+ module Twittbot
10
+ # Class providing the streaming connections and callback logic for the bot.
11
+ class Bot
12
+
13
+ include Thor::Shell
14
+
15
+ def initialize(options = {})
16
+ @options = {
17
+ current_dir: FileUtils.pwd
18
+ }
19
+
20
+ $bot = {
21
+ callbacks: {},
22
+ config: YAML.load_file(File.expand_path("./#{Twittbot::CONFIG_FILE_NAME}", @options[:current_dir])),
23
+ periodic: []
24
+ }.merge!(options)
25
+
26
+ load_bot_code
27
+ end
28
+
29
+ # Authenticates an account with Twitter.
30
+ def auth
31
+ require 'oauth'
32
+ say "This will reset your current access tokens.", :red if already_authed?
33
+
34
+ # get the request token URL
35
+ callback = OAuth::OUT_OF_BAND
36
+ consumer = OAuth::Consumer.new $bot[:config][:consumer_key],
37
+ $bot[:config][:consumer_secret],
38
+ site: Twitter::REST::Client::BASE_URL,
39
+ scheme: :header
40
+ request_token = consumer.get_request_token(oauth_callback: callback)
41
+ url = request_token.authorize_url(oauth_callback: callback)
42
+
43
+ puts "Open this URL in a browser: #{url}"
44
+ pin = ''
45
+ until pin =~ /^\d+$/
46
+ print "Enter PIN =>"
47
+ pin = $stdin.gets.strip
48
+ end
49
+
50
+ access_token = request_token.get_access_token(oauth_verifier: pin)
51
+ $bot[:config][:access_token] = access_token.token
52
+ $bot[:config][:access_token_secret] = access_token.secret
53
+
54
+ # get the bot's user name (screen_name) and print it to the console
55
+ $bot[:config][:screen_name] = get_screen_name access_token
56
+ puts "Hello, #{$bot[:config][:screen_name]}!"
57
+
58
+ save_config
59
+ end
60
+
61
+ # Starts the bot.
62
+ def start
63
+ check_config
64
+ $bot[:client] ||= Twitter::REST::Client.new do |cfg|
65
+ cfg.consumer_key = $bot[:config][:consumer_key]
66
+ cfg.consumer_secret = $bot[:config][:consumer_secret]
67
+ cfg.access_token = $bot[:config][:access_token]
68
+ cfg.access_token_secret = $bot[:config][:access_token_secret]
69
+ end
70
+
71
+ @streamer ||= Twitter::Streaming::Client.new do |cfg|
72
+ cfg.consumer_key = $bot[:config][:consumer_key]
73
+ cfg.consumer_secret = $bot[:config][:consumer_secret]
74
+ cfg.access_token = $bot[:config][:access_token]
75
+ cfg.access_token_secret = $bot[:config][:access_token_secret]
76
+ end
77
+
78
+ @userstream_thread ||= Thread.new do
79
+ puts "connected to user stream"
80
+ @streamer.user do |obj|
81
+ handle_stream_object obj, :user
82
+ end
83
+ puts "lost user stream connection"
84
+ end
85
+
86
+ @tweetstream_thread ||= Thread.new do
87
+ puts "connected to tweet stream"
88
+ @streamer.filter track: $bot[:config][:track].join(",") do |obj|
89
+ handle_stream_object obj, :filter
90
+ end
91
+ puts "lost tweet stream connection"
92
+ end
93
+
94
+ @periodic_thread ||= Thread.new do
95
+ loop do
96
+ do_periodic
97
+ sleep 60
98
+ end
99
+ end
100
+
101
+ @userstream_thread.join
102
+ @tweetstream_thread.join
103
+ @periodic_thread.join
104
+ end
105
+
106
+ # Loads the bot's actual code which is stored in the bot's +lib+
107
+ # subdirectory.
108
+ def load_bot_code
109
+ files = Dir["#{File.expand_path('./lib', @options[:current_dir])}/**/*"]
110
+ files.each do |file|
111
+ require_relative file.sub(/\.rb$/, '') if file.end_with? '.rb'
112
+ end
113
+ end
114
+
115
+ # Saves the bot's config (i.e. not the botpart ones).
116
+ def save_config
117
+ config = $bot[:config].clone
118
+ config.delete :client
119
+ File.open "./#{Twittbot::CONFIG_FILE_NAME}", 'w' do |f|
120
+ f.write config.to_yaml
121
+ end
122
+ end
123
+
124
+ # Checks some configuration values, e.g. if the bot is already authenticated with Twitter
125
+ def check_config
126
+ unless already_authed?
127
+ say "Please authenticate using `twittbot auth' first.", :red
128
+ raise 'Not authenticated'
129
+ end
130
+ end
131
+
132
+ # Handles a object yielded from a Twitter::Streaming::Client.
133
+ # @param object [Object] The object yielded from a Twitter::Streaming::Client connection.
134
+ # @param type [Symbol] The type of the streamer. Should be either :user or :filter.
135
+ def handle_stream_object(object, type)
136
+ opts = {
137
+ stream_type: type
138
+ }
139
+ case object
140
+ when Twitter::Streaming::FriendList
141
+ # object: Array with IDs
142
+ do_callbacks :friend_list, object
143
+ when Twitter::Tweet
144
+ # object: Twitter::Tweet
145
+ is_mention = (object.user.screen_name != $bot[:config][:screen_name] and object.text.include?("@" + $bot[:config][:screen_name]) and not object.retweet?)
146
+ do_callbacks :retweet, object, opts if object.retweet? and object.retweeted_tweet.user.screen_name == $bot[:config][:screen_name]
147
+ do_callbacks :mention, object, opts if is_mention
148
+ do_callbacks :tweet, object, opts.merge({ mention: is_mention, retweet: object.retweet? })
149
+ when Twitter::Streaming::Event
150
+ case object.name
151
+ when :follow, :favorite
152
+ # :follow -- object: Twitter::Streaming::Event(name: :follow, source: Twitter::User, target: Twitter::User)
153
+ # :favorite -- object: Twitter::Streaming::Event(name: :favorite, source: Twitter::User, target: Twitter::User, target_object: Twitter::Tweet)
154
+ do_callbacks object.name, object, opts
155
+ else
156
+ puts "no handler for #{object.class.to_s}/#{object.name}\n -- object data:"
157
+ require 'pp'
158
+ pp object
159
+ do_callbacks object.name, object, opts
160
+ end
161
+ else
162
+ puts "no handler for #{object.class.to_s}\n -- object data:"
163
+ require 'pp'
164
+ pp object
165
+ end
166
+ end
167
+
168
+ # Runs callbacks.
169
+ # @param callback_type [:Symbol] The callback type.
170
+ # @param object [Object] The object
171
+ def do_callbacks(callback_type, object, options = {})
172
+ $bot[:callbacks][callback_type][:block].call object, options unless $bot[:callbacks][callback_type].nil?
173
+ end
174
+
175
+ def do_periodic
176
+ $bot[:periodic].each_with_index do |h, i|
177
+ h[:remaining] = if h[:remaining] == 0
178
+ h[:block].call
179
+ h[:interval]
180
+ else
181
+ h[:remaining] - 1
182
+ end
183
+ $bot[:periodic][i] = h
184
+ end
185
+ end
186
+
187
+ # @return [Boolean] whether the bot is already authenticated or not.
188
+ def already_authed?
189
+ !($bot[:config][:access_token].empty? or $bot[:config][:access_token_secret].empty?)
190
+ end
191
+
192
+ private
193
+
194
+ def get_screen_name(access_token)
195
+ oauth_response = access_token.get('/1.1/account/verify_credentials.json?skip_status=true')
196
+ oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,70 @@
1
+ require 'twittbot/defaults'
2
+ require 'pp'
3
+
4
+ module Twittbot
5
+ class BotPart
6
+ # @param name [Symbol] The name of the botpart. Should be the same as the file name without the extension.
7
+ def initialize(name, &block)
8
+ botpart_config_path = File.expand_path("./etc/#{name}.yml")
9
+ @config = $bot[:config].merge(if File.exist? botpart_config_path
10
+ YAML.load_file botpart_config_path
11
+ else
12
+ {}
13
+ end)
14
+ instance_eval &block
15
+ end
16
+
17
+ # Adds a new callback to +name+.
18
+ # @param name [Symbol] The callback type.
19
+ # Can be (at least) one of:
20
+ #
21
+ # * :tweet
22
+ # * :mention
23
+ # * :retweet
24
+ # * :favorite
25
+ # * :friend_list
26
+ def on(name, *args, &block)
27
+ $bot[:callbacks][name] ||= {
28
+ args: args,
29
+ block: block
30
+ }
31
+ end
32
+
33
+ # Runs +block+ every +interval+ +unit+(s).
34
+ # @param interval [Fixnum]
35
+ # @param unit [Symbol] the time unit.
36
+ # Can be one of:
37
+ #
38
+ # * :minute or :minutes
39
+ # * :hour or :hours
40
+ # @param options [Hash] A customizable set of options.
41
+ # @option options [Boolean] :run_at_start (true) Run the code in +block+ when the bot finished starting.
42
+ def every(interval, unit = :minutes, options = {}, &block)
43
+ raise "Not a Fixnum: #{interval}" unless interval.is_a? Fixnum
44
+ raise "Interval less than 1" if interval < 1
45
+
46
+ opts = {
47
+ run_at_start: true
48
+ }.merge(options)
49
+
50
+ case unit
51
+ when :min, :mins, :minute, :minutes
52
+ when :hr, :hrs, :hour, :hours, :horse
53
+ interval *= 60
54
+ else
55
+ raise "Unknown unit: #{unit}"
56
+ end
57
+ $bot[:periodic] << {
58
+ interval: interval,
59
+ remaining: opts[:run_at_start] ? 0 : interval,
60
+ block: block
61
+ }
62
+ end
63
+
64
+ # @return [Twitter::REST::Client]
65
+ def client
66
+ $bot[:client]
67
+ end
68
+ alias bot client
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ require 'thor'
2
+
3
+ require 'twittbot/defaults'
4
+
5
+ module Twittbot
6
+ class CLI < Thor
7
+ desc 'new APP_NAME', 'Creates a new Twittbot application.'
8
+ method_option :template_dir, type: :string, aliases: '-t', desc: 'Specifies the template directory to use', default: Twittbot::TEMPLATE_DIR
9
+ def new(app_name)
10
+ require 'twittbot/generators/twittbot/app/app_generator'
11
+ generator = Twittbot::Generators::AppGenerator.new app_name, options
12
+ generator.create
13
+ end
14
+
15
+ desc 'auth', 'authorizes with Twitter'
16
+ def auth
17
+ require 'twittbot/bot'
18
+ bot = Twittbot::Bot.new
19
+ bot.auth
20
+ end
21
+
22
+ desc 'start', 'Starts the bot'
23
+ def start
24
+ require 'twittbot/bot'
25
+ bot = Twittbot::Bot.new
26
+ bot.start
27
+ end
28
+
29
+ desc 'generate TEMPLATE_NAME', 'Installs a template'
30
+ def generate(template_name)
31
+ require 'twittbot/generators/templates/template_generator'
32
+ generator = Twittbot::Generators::TemplateGenerator.new template_name, options
33
+ generator.create
34
+ end
35
+
36
+ desc 'list-templates', 'Lists all templates'
37
+ def list_templates
38
+ require 'twittbot/template_lister'
39
+ lister = Twittbot::TemplateLister.new options
40
+ lister.list
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module Twittbot
2
+ # The version of Twittbot.
3
+ VERSION = "0.0.1"
4
+
5
+ CONSUMER_KEY = 'FYRuQcDbPAXAyVjuPZMuw' # :nodoc:
6
+ CONSUMER_SECRET = 'KiLCYTftPdxNebl5DNcj7Ey2Y8YVZu7hfqiFRYkcg' # :nodoc:
7
+
8
+ # Contains the path to the initial bot template.
9
+ TEMPLATE_DIR = File.expand_path '../generators/twittbot/app/templates', __FILE__
10
+ # The name of the bot's config file.
11
+ CONFIG_FILE_NAME = 'config.yml'
12
+
13
+ # Hash containing the default bot configuration.
14
+ DEFAULT_BOT_CONFIG = {
15
+ consumer_key: Twittbot::CONSUMER_KEY,
16
+ consumer_secret: Twittbot::CONSUMER_SECRET,
17
+ access_token: '',
18
+ access_token_secret: '',
19
+ track: []
20
+ }
21
+ end
@@ -0,0 +1,2 @@
1
+ require 'twittbot/gem_ext/twitter/tweet'
2
+ require 'twittbot/gem_ext/twitter/rest/tweets'
@@ -0,0 +1,7 @@
1
+ module Twitter
2
+ module REST
3
+ module Tweets
4
+ alias tweet update
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ module Twitter
2
+ class Tweet
3
+ # Creates a reply to this tweet.
4
+ # @param tweet_text [:String] tweet text
5
+ # @param options [Hash] A customizable set of options.
6
+ # @option options [Boolean] :reply_all (false) Add all users mentioned in the tweet text to the reply.
7
+ def reply(tweet_text, options = {})
8
+ return if $bot.nil? or $bot[:client].nil?
9
+ opts = {
10
+ reply_all: false
11
+ }.merge(options)
12
+
13
+ mentions = self.mentioned_users(opts[:reply_all])
14
+
15
+ result = "@#{mentions.join(" @")} #{tweet_text}"[(0...140)]
16
+
17
+ $bot[:client].update result, in_reply_to_status_id: self.id
18
+ rescue Twitter::Error => e
19
+ puts "caught Twitter error while retweeting: #{e.message}"
20
+ end
21
+
22
+ # Retweets this tweet.
23
+ def retweet
24
+ return if $bot.nil? or $bot[:client].nil?
25
+ $bot[:client].retweet self.id
26
+ rescue Twitter::Error => e
27
+ puts "caught Twitter error while retweeting: #{e.message}"
28
+ end
29
+
30
+ # Scans the tweet text for screen names.
31
+ # @param reply_all [Boolean] Include all users in the reply.
32
+ # @param screen_name [String] The user's screen name (i.e. that one who clicked "Reply")
33
+ # @return [Array] An array of user names.
34
+ def mentioned_users(reply_all = true, screen_name = $bot[:config][:screen_name])
35
+ userlist = [ self.user.screen_name ]
36
+ if reply_all
37
+ self.text.scan /@([A-Za-z0-9_]{1,16})/ do |user_name|
38
+ user_name = user_name[0]
39
+ userlist << user_name unless userlist.include?(user_name) or screen_name == user_name
40
+ end
41
+ end
42
+ userlist
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
1
+ Twittbot::BotPart.new :<%= @template_name %> do
2
+ on :follow do |obj|
3
+ if obj.target.screen_name == @config[:screen_name] # only follow source if the target was the bot
4
+ bot.follow(obj.source.id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ <%
2
+ # This is the config for this botpart.
3
+ # It is a hash and will be serialized to YAML and saved to 'etc/template_name.yml'.
4
+ botpart_config = {
5
+ replies: [
6
+ "Yes.",
7
+ "No.",
8
+ "Absolutely.",
9
+ "I don't think so.",
10
+ "Maybe.",
11
+ ]
12
+ }
13
+ %>
14
+ Twittbot::BotPart.new :<%= @template_name %> do
15
+ # When someone mentions the bot...
16
+ on :mention do |tweet|
17
+ # ... reply to the tweet with a random entry of the :replies list that you
18
+ # configured and also include every person that was mentioned in the tweet.
19
+ tweet.reply @config[:replies].sample, reply_all: true
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ <%
2
+ # This is the config for this botpart.
3
+ # It is a hash and will be serialized to YAML and saved to 'etc/template_name.yml'.
4
+ botpart_config = {
5
+ tweets: [
6
+ "I'm a bot!",
7
+ "Fun fact: I run on @nilsding's Twittbot. https://github.com/nilsding/twittbot",
8
+ "My admin is too lazy to change the default values.",
9
+ "things to do when you're drunk:\n\n1. drink more\n2. repeat step 1",
10
+ "#{'fox ' * 25}"
11
+ ]
12
+ }
13
+ %>
14
+ Twittbot::BotPart.new :<%= @template_name %> do
15
+ # Every 15 minutes...
16
+ every 15, :minutes do
17
+ # ... tweet a random entry of the :tweets list that you configured
18
+ bot.tweet @config[:tweets].sample
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ <%
2
+ # This is the config for this botpart.
3
+ # It is a hash and will be serialized to YAML and saved to 'etc/template_name.yml'.
4
+ botpart_config = {
5
+ tags: [
6
+ 'twittbot',
7
+ '#nilsding'
8
+ ]
9
+ }
10
+
11
+ # Display a message after installation template installation
12
+ post_install_message <<-MSG
13
+ Note: This botpart may require some changes to the bot's config:
14
+
15
+ If you want to include tweets from users other than those your bot follows,
16
+ add the same tags you have in your etc/#{@template_name}.yml to the ':track:'
17
+ section of your #{::Twittbot::CONFIG_FILE_NAME}.
18
+ MSG
19
+ %>
20
+ Twittbot::BotPart.new :<%= @template_name %> do
21
+ # When there's a new tweet in the timeline...
22
+ on :tweet do |tweet, opts|
23
+ # ... check if the tweet text contains at least one of the tags defined in your config...
24
+ @config[:tags].each do |tag|
25
+ if tweet.text.downcase.include? tag.downcase
26
+ # ... and finally retweet it, if the tweet is not a retweet.
27
+ tweet.retweet unless opts[:retweet]
28
+ break
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ require 'fileutils'
2
+ require 'erubis'
3
+ require 'yaml'
4
+
5
+ module Twittbot
6
+ module Generators
7
+ # Class to install a template to the bot.
8
+ class TemplateGenerator
9
+ include Thor::Shell
10
+
11
+ def initialize(template_name, options)
12
+ @template_name = template_name.gsub('-', '_')
13
+ @options = {
14
+ 'template_options' => {}
15
+ }.merge!(options)
16
+ @options['template_dir'] = File.expand_path "../#{@template_name}", __FILE__
17
+ @post_install_messages = []
18
+ end
19
+
20
+ def create
21
+ files = Dir["#{@options['template_dir']}/**/*"]
22
+ files.each do |file|
23
+ real_filename = "lib/#{file.sub(/^#{@options['template_dir']}\//, '').sub(/^_/, '.')}"
24
+ real_path = File.expand_path "./#{real_filename}"
25
+
26
+ botpart_config = {}
27
+ erb = Erubis::Eruby.new File.read(file)
28
+ final_result = erb.result(binding)
29
+
30
+ if File.exist? real_path
31
+ say_status :exists, real_filename, :red
32
+ else
33
+ say_status :create, real_filename, :green
34
+ if File.directory? file
35
+ FileUtils.mkdir_p real_path
36
+ else
37
+ File.open real_path, 'w' do |f|
38
+ f.write final_result
39
+ end
40
+ end
41
+ end
42
+
43
+ save_config botpart_config, real_filename unless botpart_config.empty?
44
+ end
45
+ unless @post_install_messages.empty?
46
+ say "Post install messages:", :yellow
47
+ puts @post_install_messages
48
+ end
49
+ end
50
+
51
+ # Adds an optional post-install message.
52
+ # @param msg [String] message to display
53
+ def post_install_message(msg)
54
+ @post_install_messages << msg
55
+ end
56
+
57
+ private
58
+
59
+ def save_config(config, path)
60
+ path = "etc/#{path.sub(/^lib\//, '').sub(/\.rb$/, '.yml')}"
61
+ real_path = File.expand_path "./#{path}"
62
+
63
+ if File.exist? real_path
64
+ say_status :merge, path, :yellow
65
+ existing_config = YAML.load_file real_path
66
+ config.merge! existing_config
67
+ else
68
+ say_status :create, path, :green
69
+ end
70
+
71
+ File.open real_path, 'w' do |f|
72
+ f.write config.to_yaml
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,59 @@
1
+ require 'fileutils'
2
+ require 'erubis'
3
+ require 'yaml'
4
+
5
+ require 'twittbot/defaults'
6
+
7
+ module Twittbot
8
+ module Generators
9
+ # Class to generate a new Twittbot bot.
10
+ class AppGenerator
11
+ include Thor::Shell
12
+
13
+ def initialize(app_name, options = {})
14
+ @options = {
15
+ 'template_dir' => Twittbot::TEMPLATE_DIR
16
+ }.merge!(options)
17
+ @app_name = app_name
18
+ @options['template_dir'] = File.expand_path @options['template_dir']
19
+ end
20
+
21
+ # Creates the bot.
22
+ def create
23
+ path = File.expand_path "./#{@app_name}"
24
+ if File.exist?(@app_name)
25
+ say "#{File.directory?(@app_name) ? 'Directory' : 'File'} #{@app_name} already exists", :red
26
+ exit 1
27
+ end
28
+ FileUtils.mkdir_p(path)
29
+
30
+ # build the template
31
+ files = Dir["#{@options['template_dir']}/**/*"]
32
+ files.each do |file|
33
+ real_filename = file.sub(/^#{@options['template_dir']}\//, '').sub(/^_/, '.')
34
+ real_path = "#{path}/#{real_filename}"
35
+ say_status :create, real_filename, :green
36
+ if File.directory? file
37
+ FileUtils.mkdir_p real_path
38
+ else
39
+ erb = Erubis::Eruby.new File.read(file)
40
+ File.open real_path, 'w' do |f|
41
+ f.write erb.result(binding)
42
+ end
43
+ end
44
+ end
45
+ generate_config path
46
+ end
47
+
48
+ private
49
+
50
+ def generate_config(base_path)
51
+ say_status :create, Twittbot::CONFIG_FILE_NAME, :green
52
+
53
+ File.open "#{base_path}/#{Twittbot::CONFIG_FILE_NAME}", 'w' do |f|
54
+ f.write Twittbot::DEFAULT_BOT_CONFIG.to_yaml
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'twittbot'
4
+
@@ -0,0 +1,20 @@
1
+ # <%= @app_name %>
2
+
3
+ <%= @app_name %> is a bot created using Twittbot <%= ::Twittbot::VERSION %>.
4
+
5
+ See [nilsding/twittbot](https://github.com/nilsding/twittbot) to find out
6
+ more about Twittbot.
7
+
8
+ ## Installation and usage
9
+
10
+ Install this bot's dependencies:
11
+
12
+ $ bundle install
13
+
14
+ Authorize with Twitter for the first time:
15
+
16
+ $ bundle exec twittbot auth
17
+
18
+ And finally run the bot:
19
+
20
+ $ bundle exec twittbot start
@@ -0,0 +1,2 @@
1
+ # ignore the bot's config as its credentials are saved there
2
+ config.yml
@@ -0,0 +1,22 @@
1
+ module Twittbot
2
+ # Class to list available templates
3
+ class TemplateLister
4
+ include Thor::Shell
5
+
6
+ # @param options [Hash] The CLI options from Thor
7
+ def initialize(options)
8
+ @options = {}.merge(options)
9
+ @options['templates_dir'] = File.expand_path "../generators/templates", __FILE__
10
+ end
11
+
12
+ # Prints the available templates to stdout.
13
+ def list
14
+ dirs = Dir["#{@options['templates_dir']}/*"]
15
+ dirs.each do |dir|
16
+ if File.exist? dir and File.directory? dir
17
+ say File.basename(dir).gsub('_', '-')
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
data/twittbot.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'twittbot/defaults'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'twittbot'
8
+ spec.version = Twittbot::VERSION
9
+ spec.authors = ['nilsding']
10
+ spec.email = ['nilsding@nilsding.org']
11
+ spec.summary = %q{An advanced Twitter bot.}
12
+ spec.description = %q{Twittbot is an advanced Twitter bot. See the README for more info.}
13
+ spec.homepage = 'https://github.com/nilsding/twittbot'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'thor', '~> 0.19'
22
+ spec.add_dependency 'erubis', '~> 2.7', '>= 2.7.0'
23
+ spec.add_dependency 'oauth', '~> 0.4'
24
+ spec.add_dependency 'twitter', '~> 5.14'
25
+ spec.add_development_dependency 'yard', '~> 0.8'
26
+ spec.add_development_dependency 'bundler', '~> 1.7'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: twittbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - nilsding
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '0.19'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '0.19'
27
+ - !ruby/object:Gem::Dependency
28
+ name: erubis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ - - '>='
35
+ - !ruby/object:Gem::Version
36
+ version: 2.7.0
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '2.7'
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: 2.7.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: oauth
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.4'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '0.4'
61
+ - !ruby/object:Gem::Dependency
62
+ name: twitter
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: '5.14'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ version: '5.14'
75
+ - !ruby/object:Gem::Dependency
76
+ name: yard
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ~>
80
+ - !ruby/object:Gem::Version
81
+ version: '0.8'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '0.8'
89
+ - !ruby/object:Gem::Dependency
90
+ name: bundler
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ~>
94
+ - !ruby/object:Gem::Version
95
+ version: '1.7'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: '1.7'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '10.0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ~>
115
+ - !ruby/object:Gem::Version
116
+ version: '10.0'
117
+ description: Twittbot is an advanced Twitter bot. See the README for more info.
118
+ email:
119
+ - nilsding@nilsding.org
120
+ executables:
121
+ - twittbot
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - .gitignore
126
+ - .yardopts
127
+ - Gemfile
128
+ - LICENSE.txt
129
+ - README.md
130
+ - Rakefile
131
+ - bin/twittbot
132
+ - lib/twittbot.rb
133
+ - lib/twittbot/bot.rb
134
+ - lib/twittbot/botpart.rb
135
+ - lib/twittbot/cli.rb
136
+ - lib/twittbot/defaults.rb
137
+ - lib/twittbot/gem_ext/twitter.rb
138
+ - lib/twittbot/gem_ext/twitter/rest/tweets.rb
139
+ - lib/twittbot/gem_ext/twitter/tweet.rb
140
+ - lib/twittbot/generators/templates/followback/followback.rb
141
+ - lib/twittbot/generators/templates/random_reply/random_reply.rb
142
+ - lib/twittbot/generators/templates/random_tweet/random_tweet.rb
143
+ - lib/twittbot/generators/templates/retweet_bot/retweet_bot.rb
144
+ - lib/twittbot/generators/templates/template_generator.rb
145
+ - lib/twittbot/generators/twittbot/app/app_generator.rb
146
+ - lib/twittbot/generators/twittbot/app/templates/Gemfile
147
+ - lib/twittbot/generators/twittbot/app/templates/README.md
148
+ - lib/twittbot/generators/twittbot/app/templates/_gitignore
149
+ - lib/twittbot/generators/twittbot/app/templates/etc/.keep
150
+ - lib/twittbot/generators/twittbot/app/templates/lib/.keep
151
+ - lib/twittbot/template_lister.rb
152
+ - twittbot.gemspec
153
+ homepage: https://github.com/nilsding/twittbot
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - '>='
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.4.5
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: An advanced Twitter bot.
177
+ test_files: []
178
+ has_rdoc: