Class: Twittbot::Bot

Inherits:
Object
  • Object
show all
Includes:
Thor::Shell
Defined in:
lib/twittbot/bot.rb

Overview

Class providing the streaming connections and callback logic for the bot.

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Bot

Returns a new instance of Bot.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 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: []
  }.merge!(options)

  load_bot_code

  at_exit do
    save_config
    $bot[:botparts].each { |b| b.save_config }
  end
end

Instance Method Details

#already_authed?Boolean

Returns whether the bot is already authenticated or not.

Returns:

  • (Boolean)

    whether the bot is already authenticated or not.



242
243
244
# File 'lib/twittbot/bot.rb', line 242

def already_authed?
  !($bot[:config][:access_token].empty? or $bot[:config][:access_token_secret].empty?)
end

#authObject

Authenticates an account with Twitter.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/twittbot/bot.rb', line 39

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

#check_configObject

Checks some configuration values, e.g. if the bot is already authenticated with Twitter



154
155
156
157
158
159
# File 'lib/twittbot/bot.rb', line 154

def check_config
  unless already_authed?
    say "Please authenticate using `twittbot auth' first.", :red
    raise 'Not authenticated'
  end
end

#do_callbacks(callback_type, object, options = {}) ⇒ Object

Runs callbacks.

Parameters:

  • callback_type (:Symbol)

    The callback type.

  • object (Object)

    The object



202
203
204
205
206
207
# File 'lib/twittbot/bot.rb', line 202

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

#do_direct_message(dm, opts = {}) ⇒ Object

Processes a direct message.

Parameters:



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/twittbot/bot.rb', line 223

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

#do_periodicObject



209
210
211
212
213
214
215
216
217
218
219
# File 'lib/twittbot/bot.rb', line 209

def do_periodic
  $bot[:periodic].each_with_index do |h, i|
    h[:remaining] = if h[:remaining] == 0
                      h[:block].call
                      h[:interval]
                    else
                      h[:remaining] - 1
                    end
    $bot[:periodic][i] = h
  end
end

#handle_stream_object(object, type) ⇒ Object

Handles a object yielded from a Twitter::Streaming::Client.

Parameters:

  • object (Object)

    The object yielded from a Twitter::Streaming::Client connection.

  • type (Symbol)

    The type of the streamer. Should be either :user or :filter.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/twittbot/bot.rb', line 164

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? })
    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
    else
      puts "no handler for #{object.class.to_s}\n  -- object data:"
      require 'pp'
      pp object
  end
end

#load_bot_codeObject

Loads the bot’s actual code which is stored in the bot’s lib subdirectory.



137
138
139
140
141
142
# File 'lib/twittbot/bot.rb', line 137

def load_bot_code
  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

#modify_admin(screen_name, action = :add) ⇒ Object

Parameters:

  • screen_name (String)

    the user’s screen name

  • action (Symbol) (defaults to: :add)

    :add or :delete



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/twittbot/bot.rb', line 120

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

#save_configObject

Saves the bot’s config (i.e. not the botpart ones).



145
146
147
148
149
150
151
# File 'lib/twittbot/bot.rb', line 145

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

#startObject

Starts the bot.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/twittbot/bot.rb', line 69

def start
  check_config
  init_clients

  @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

  @periodic_thread ||= Thread.new do
    loop do
      begin
        do_periodic
      rescue => _
      end
      sleep 60
    end
  end

  @userstream_thread.join
  @tweetstream_thread.join
  @periodic_thread.join
end