twittbot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: