class Twittbot::Bot
Class providing the streaming connections and callback logic for the bot.
Public Class Methods
# File lib/twittbot/bot.rb, line 15 def initialize(options = {}) @options = { current_dir: FileUtils.pwd } $bot = { botparts: [], callbacks: {}, commands: {}, config: Twittbot::DEFAULT_BOT_CONFIG.merge( YAML.load_file(File.expand_path("./#{Twittbot::CONFIG_FILE_NAME}", @options[:current_dir])) ), periodic: [], save_config: true, tasks: {}, stream: options.fetch("stream", true) } load_bot_code at_exit do save_config $bot[:botparts].each { |b| b.save_config } end if $bot[:save_config] end
Public Instance Methods
@return [Boolean] whether the bot is already authenticated or not.
# File lib/twittbot/bot.rb, line 270 def already_authed? !($bot[:config][:access_token].empty? or $bot[:config][:access_token_secret].empty?) end
Authenticates an account with Twitter
.
# File lib/twittbot/bot.rb, line 42 def auth require 'oauth' say "This will reset your current access tokens.", :red if already_authed? # get the request token URL callback = OAuth::OUT_OF_BAND consumer = OAuth::Consumer.new $bot[:config][:consumer_key], $bot[:config][:consumer_secret], site: Twitter::REST::Client::BASE_URL, scheme: :header request_token = consumer.get_request_token(oauth_callback: callback) url = request_token.authorize_url(oauth_callback: callback) puts "Open this URL in a browser: #{url}" pin = '' until pin =~ /^\d+$/ print "Enter PIN =>" pin = $stdin.gets.strip end access_token = request_token.get_access_token(oauth_verifier: pin) $bot[:config][:access_token] = access_token.token $bot[:config][:access_token_secret] = access_token.secret # get the bot's user name (screen_name) and print it to the console $bot[:config][:screen_name] = get_screen_name access_token puts "Hello, #{$bot[:config][:screen_name]}!" end
Checks some configuration values, e.g. if the bot is already authenticated with Twitter
# File lib/twittbot/bot.rb, line 180 def check_config unless already_authed? say "Please authenticate using `twittbot auth' first.", :red raise 'Not authenticated' end end
Runs the task `task_name`. @param task_name [Symbol] The name of the task to be run.
# File lib/twittbot/bot.rb, line 131 def cron(task_name) task = $bot[:tasks][task_name] unless task say "Task \"#{task_name}\" does not exist. Use \"twittbot cron list\" for a list of available tasks." return end #check_config init_clients task[:block].call end
Runs callbacks. @param callback_type [:Symbol] The callback type. @param object [Object] The object
# File lib/twittbot/bot.rb, line 230 def do_callbacks(callback_type, object, options = {}) return if $bot[:callbacks][callback_type].nil? $bot[:callbacks][callback_type].each do |c| c[:block].call object, options end end
Processes a direct message. @param dm [Twitter::DirectMessage] received direct message
# File lib/twittbot/bot.rb, line 251 def do_direct_message(dm, opts = {}) return if dm.sender.screen_name == $bot[:config][:screen_name] return do_callbacks(:direct_message, dm, opts) unless dm.text.start_with? $bot[:config][:dm_command_prefix] dm_text = dm.text.sub($bot[:config][:dm_command_prefix], '').strip return if dm_text.empty? return unless /(?<command>[A-Za-z0-9]+)(?:\s*)(?<args>.*)/m =~ dm_text command = Regexp.last_match(:command).to_sym args = Regexp.last_match :args cmd = $bot[:commands][command] return say_status :dm, "#{dm.sender.screen_name} tried to issue non-existent command :#{command}, ignoring", :cyan if cmd.nil? return say_status :dm, "#{dm.sender.screen_name} tried to issue admin command :#{command}, ignoring", :cyan if cmd[:admin] and !dm.sender.admin? say_status :dm, "#{dm.sender.screen_name} issued command :#{command}", :cyan cmd[:block].call(args, dm.sender) end
# File lib/twittbot/bot.rb, line 237 def do_periodic $bot[:periodic].each_with_index do |h, i| h[:remaining] = if h[:remaining] - 1 <= 0 h[:block].call h[:interval] else h[:remaining] - 1 end $bot[:periodic][i] = h end end
Handles a object yielded from a Twitter::Streaming::Client. @param object [Object] The object yielded from a Twitter::Streaming::Client connection. @param type [Symbol] The type of the streamer. Should be either :user or :filter.
# File lib/twittbot/bot.rb, line 190 def handle_stream_object(object, type) opts = { stream_type: type } case object when Twitter::Streaming::FriendList # object: Array with IDs do_callbacks :friend_list, object when Twitter::Tweet # object: Twitter::Tweet is_mention = (object.user.screen_name != $bot[:config][:screen_name] and object.text.include?("@" + $bot[:config][:screen_name]) and not object.retweet?) do_callbacks :retweet, object, opts if object.retweet? and object.retweeted_tweet.user.screen_name == $bot[:config][:screen_name] do_callbacks :mention, object, opts if is_mention do_callbacks :tweet, object, opts.merge({ mention: is_mention, retweet: object.retweet?, favorite: object.favorited? }) when Twitter::Streaming::Event case object.name when :follow, :favorite # :follow -- object: Twitter::Streaming::Event(name: :follow, source: Twitter::User, target: Twitter::User) # :favorite -- object: Twitter::Streaming::Event(name: :favorite, source: Twitter::User, target: Twitter::User, target_object: Twitter::Tweet) do_callbacks object.name, object, opts else puts "no handler for #{object.class.to_s}/#{object.name}\n -- object data:" require 'pp' pp object do_callbacks object.name, object, opts end when Twitter::DirectMessage do_direct_message object, opts when Twitter::Streaming::DeletedTweet do_callbacks :deleted, object, opts else puts "no handler for #{object.class.to_s}\n -- object data:" require 'pp' pp object end end
Loads the bot's actual code which is stored in the bot's lib
subdirectory.
# File lib/twittbot/bot.rb, line 162 def load_bot_code load_special_tasks files = Dir["#{File.expand_path('./lib', @options[:current_dir])}/**/*"] files.each do |file| require_relative file.sub(/\.rb$/, '') if file.end_with? '.rb' end end
@param screen_name [String] the user's screen name @param action [Symbol] :add or :delete
# File lib/twittbot/bot.rb, line 145 def modify_admin(screen_name, action = :add) init_clients user = $bot[:client].user screen_name case action when :add $bot[:config][:admins] << user.id unless $bot[:config][:admins].include? user.id when :del, :delete $bot[:config][:admins].delete user.id if $bot[:config][:admins].include? user.id else say "Unknown action " + action.to_s, :red end rescue Twitter::Error::NotFound say "User not found.", :red end
Saves the bot's config (i.e. not the botpart ones).
# File lib/twittbot/bot.rb, line 171 def save_config config = $bot[:config].clone config.delete :client File.open "./#{Twittbot::CONFIG_FILE_NAME}", 'w' do |f| f.write config.to_yaml end end
Starts the bot.
# File lib/twittbot/bot.rb, line 72 def start check_config init_clients if $bot[:stream] puts "connecting to streaming APIs" @userstream_thread ||= Thread.new do loop do begin puts "connected to user stream" @streamer.user do |obj| handle_stream_object obj, :user end rescue => e puts "lost user stream connection: " + e.message end puts "reconnecting in #{Twittbot::RECONNECT_WAIT_TIME} seconds..." sleep Twittbot::RECONNECT_WAIT_TIME end end @tweetstream_thread ||= Thread.new do loop do begin puts "connected to tweet stream" @streamer.filter track: $bot[:config][:track].join(",") do |obj| handle_stream_object obj, :filter end rescue puts "lost tweet stream connection: " + e.message end puts "reconnecting in #{Twittbot::RECONNECT_WAIT_TIME} seconds..." sleep Twittbot::RECONNECT_WAIT_TIME end end end @periodic_thread ||= Thread.new do loop do begin Thread.new { do_periodic } rescue => _ end sleep 60 end end do_callbacks :load, nil if $bot[:stream] @userstream_thread.join @tweetstream_thread.join end @periodic_thread.join end
Private Instance Methods
# File lib/twittbot/bot.rb, line 292 def get_screen_name(access_token) oauth_response = access_token.get('/1.1/account/verify_credentials.json?skip_status=true') oauth_response.body.match(/"screen_name"\s*:\s*"(.*?)"/).captures.first end
# File lib/twittbot/bot.rb, line 276 def init_clients $bot[:client] ||= Twitter::REST::Client.new do |cfg| cfg.consumer_key = $bot[:config][:consumer_key] cfg.consumer_secret = $bot[:config][:consumer_secret] cfg.access_token = $bot[:config][:access_token] cfg.access_token_secret = $bot[:config][:access_token_secret] end @streamer ||= Twitter::Streaming::Client.new do |cfg| cfg.consumer_key = $bot[:config][:consumer_key] cfg.consumer_secret = $bot[:config][:consumer_secret] cfg.access_token = $bot[:config][:access_token] cfg.access_token_secret = $bot[:config][:access_token_secret] end end
Lists all tasks
# File lib/twittbot/bot.rb, line 308 def list_tasks tasks = $bot[:tasks].map do |name, value| [name, value[:desc]] end.to_h longest_name = tasks.keys.map(&:to_s).max { |a, b| a.length <=> b.length }.length tasks = tasks.map do |name, desc| "twittbot cron %-*s # %s" % [longest_name + 2, name, desc] end.sort puts tasks end
Load special tasks (e.g. list)
# File lib/twittbot/bot.rb, line 298 def load_special_tasks $bot[:tasks][:list] = { block: -> do list_tasks end, desc: 'List all available tasks' } end