Class: Twibot::Bot

Inherits:
Object
  • Object
show all
Includes:
Handlers
Defined in:
lib/twibot/bot.rb

Overview

Main bot “controller” class

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Handlers

#add_handler, #dispatch, #handlers, #handlers=, #handlers_for_type

Constructor Details

#initialize(options = nil, prompt = false) ⇒ Bot

Returns a new instance of Bot.



14
15
16
17
18
19
20
21
22
# File 'lib/twibot/bot.rb', line 14

def initialize(options = nil, prompt = false)
  @prompt = prompt
  @conf = nil
  @config = options || Twibot::Config.default << Twibot::FileConfig.new << Twibot::CliConfig.new
  @log = nil
  @abort = false
rescue Exception => krash
  raise SystemExit.new(krash.message)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object (private)

Map configuration settings



337
338
339
340
341
342
# File 'lib/twibot/bot.rb', line 337

def method_missing(name, *args, &block)
  return super unless config.key?(name)

  self.class.send(:define_method, name) { config[name] }
  config[name]
end

Instance Attribute Details

#prompt=(value) ⇒ Object (writeonly)

Sets the attribute prompt

Parameters:

  • value

    the value to set the attribute prompt to.



12
13
14
# File 'lib/twibot/bot.rb', line 12

def prompt=(value)
  @prompt = value
end

#twitterObject (readonly)

Returns the value of attribute twitter.



11
12
13
# File 'lib/twibot/bot.rb', line 11

def twitter
  @twitter
end

Instance Method Details

#add_follower!(user_or_id) ⇒ Object



147
148
149
# File 'lib/twibot/bot.rb', line 147

def add_follower!(user_or_id)
  follower_ids[id_for_user_or_id(user_or_id)] = true
end

#add_friend!(user_or_id, only_local = false) ⇒ Object



127
128
129
130
131
# File 'lib/twibot/bot.rb', line 127

def add_friend!(user_or_id, only_local=false)
  id = id_for_user_or_id(user_or_id)
  sandbox(0) { twitter.friend(:add, id) } unless only_local
  friend_ids[id] = true
end

#add_hook(event, &blk) ⇒ Object

registers a block to be called at the given event



193
194
195
# File 'lib/twibot/bot.rb', line 193

def add_hook(event, &blk)
  hooks[event.to_sym] = blk
end

#configure {|@config| ... } ⇒ Object

Configure bot

Yields:

  • (@config)


327
328
329
330
331
# File 'lib/twibot/bot.rb', line 327

def configure
  yield @config
  @conf = nil
  @twitter = nil
end

#dispatch_messages(type, messages, labels) ⇒ Object

Dispatch a collection of messages



286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/twibot/bot.rb', line 286

def dispatch_messages(type, messages, labels)
  messages.each {|message| with_hooks(type) { dispatch(type, message) } }
  # Avoid picking up messages over again
  if type.is_a? Array            # [TODO] (mikedemers) this is an ugly hack
    processed[type.first][type.last] = messages.first.id if messages.length > 0
  else
    processed[type] = messages.first.id if messages.length > 0
  end

  num = messages.length
  log.info "#{config[:host]}: Received #{num} #{num == 1 ? labels[0] : labels[1]}"
  num
end

#follower_idsObject



143
144
145
# File 'lib/twibot/bot.rb', line 143

def follower_ids
  @follower_ids ||= {}
end

#friend_idsObject



123
124
125
# File 'lib/twibot/bot.rb', line 123

def friend_ids
  @friend_ids ||= {}
end

#hooksObject

returns a Hash of all registered hooks



186
187
188
# File 'lib/twibot/bot.rb', line 186

def hooks
  @hooks ||= {}
end

#id_for_user_or_id(user_or_id) ⇒ Object



159
160
161
# File 'lib/twibot/bot.rb', line 159

def id_for_user_or_id(user_or_id)
  (user_or_id.respond_to?(:screen_name) ? user_or_id.id : user_or_id).to_i
end

#is_follower?(user_or_id) ⇒ Boolean

Returns:

  • (Boolean)


155
156
157
# File 'lib/twibot/bot.rb', line 155

def is_follower?(user_or_id)
  !!follower_ids[id_for_user_or_id(user_or_id)]
end

#is_friend?(user_or_id) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/twibot/bot.rb', line 139

def is_friend?(user_or_id)
  !!friend_ids[id_for_user_or_id(user_or_id)]
end

#load_followersObject

retrieve a list of friend ids and store it as a Hash



176
177
178
179
180
# File 'lib/twibot/bot.rb', line 176

def load_followers
  sandbox(0) do
    twitter.graph(:followers, config[:login]).each {|id| add_follower!(id) }
  end
end

#load_friendsObject

retrieve a list of friend ids and store it as a Hash



167
168
169
170
171
# File 'lib/twibot/bot.rb', line 167

def load_friends
  sandbox(0) do
    twitter.graph(:friends, config[:login]).each {|id| add_friend!(id, true) }
  end
end

#logObject

Return logger instance



315
316
317
318
319
320
321
322
# File 'lib/twibot/bot.rb', line 315

def log
  return @log if @log
  os = config[:log_file] ? File.open(config[:log_file], "a") : $stdout
  os.sync = !!config[:log_flush]
  @log = Logger.new(os)
  @log.level = Logger.const_get(config[:log_level] ? config[:log_level].upcase : "INFO")
  @log
end

#pollObject

Poll Twitter API in a loop and pass on messages and tweets when they appear



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/twibot/bot.rb', line 98

def poll
  max = max_interval
  step = interval_step
  interval = min_interval

  while !@abort do
    run_hook :before_all
    message_count = 0
    message_count += receive_messages || 0
    message_count += receive_replies || 0
    message_count += receive_tweets || 0
    message_count += receive_searches || 0

    receive_followers

    run_hook :after_all, message_count

    interval = message_count > 0 ? min_interval : [interval + step, max].min

    log.debug "#{config[:host]} sleeping for #{interval}s"
    sleep interval
  end
end

#processedObject



28
29
30
31
32
33
34
35
# File 'lib/twibot/bot.rb', line 28

def processed
  @processed ||= {
    :message => nil,
    :reply => nil,
    :tweet => nil,
    :search => {}
  }
end

#prompt?Boolean

Returns:

  • (Boolean)


24
25
26
# File 'lib/twibot/bot.rb', line 24

def prompt?
  @prompt
end

#receive_followersObject

Receive any new followers



271
272
273
274
275
276
277
278
279
280
281
# File 'lib/twibot/bot.rb', line 271

def receive_followers
  newbies = []
  sandbox(0) do
    twitter.graph(:followers, config[:login]).each {|id| newbies << id unless is_friend?(id) or is_follower?(id) }
    newbies.each do |id|
      add_follower!(id)
      with_hooks(:follower) { handlers_for_type(:follower).each {|h| h.handle(id, {}) } }
    end
  end
  log.info "#{config[:host]}: Received #{newbies.size} new #{newbies.size == 1 ? 'follower' : 'followers'}"
end

#receive_messagesObject

Receive direct messages



208
209
210
211
212
213
214
215
216
217
# File 'lib/twibot/bot.rb', line 208

def receive_messages
  type = :message
  return false unless handlers_for_type(type).length > 0
  options = {}
  options[:since_id] = processed[type] if processed[type]

  sandbox(0) do
    dispatch_messages(type, twitter.messages(:received, options), %w{message messages})
  end
end

#receive_repliesObject

Receive tweets that start with @<login>



236
237
238
239
240
241
242
243
244
245
# File 'lib/twibot/bot.rb', line 236

def receive_replies
  type = :reply
  return false unless handlers_for_type(type).length > 0
  options = {}
  options[:since_id] = processed[type] if processed[type]

  sandbox(0) do
    dispatch_messages(type, twitter.status(:replies, options), %w{reply replies})
  end
end

#receive_searchesObject

Receive tweets that match the query parameters



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/twibot/bot.rb', line 250

def receive_searches
  result_count = 0

  handlers_for_type(:search).each_pair do |query, search_handlers|
    options = { :q => query, :rpp => 100 }
    [:lang, :geocode].each do |param|
      options[param] = search_handlers.first.options[param] if search_handlers.first.options[param]
    end
    options[:since_id] = processed[:search][query] if processed[:search][query]

    result_count += sandbox(0) do
      dispatch_messages([:search, query], twitter.search(options.merge(options)), %w{tweet tweets}.map {|l| "#{l} for \"#{query}\""})
    end
  end

  result_count
end

#receive_tweetsObject

Receive tweets



222
223
224
225
226
227
228
229
230
231
# File 'lib/twibot/bot.rb', line 222

def receive_tweets
  type = :tweet
  return false unless handlers_for_type(type).length > 0
  options = {}
  options[:since_id] = processed[type] if processed[type]

  sandbox(0) do
    dispatch_messages(type, twitter.timeline_for(config.to_hash[:timeline_for] || :public, options), %w{tweet tweets})
  end
end

#remove_follower!(user_or_id) ⇒ Object



151
152
153
# File 'lib/twibot/bot.rb', line 151

def remove_follower!(user_or_id)
  follower_ids[id_for_user_or_id(user_or_id)] = false
end

#remove_friend!(user_or_id, only_local = false) ⇒ Object



133
134
135
136
137
# File 'lib/twibot/bot.rb', line 133

def remove_friend!(user_or_id, only_local=false)
  id = id_for_user_or_id(user_or_id)
  sandbox(0) { twitter.friend(:remove, id) } unless only_local
  friend_ids[id] = false
end

#run!Object

Run application



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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
# File 'lib/twibot/bot.rb', line 46

def run!
  puts "Twibot #{Twibot::VERSION} imposing as @#{} on #{config[:host]}"

  trap(:INT) do
    puts "\nAnd it's a wrap. See ya soon!"
    exit
  end

  case config[:process]
  when :all, nil
    # do nothing so it will fetch ALL
  when :new
    # Make sure we don't process messages and tweets received prior to bot launch
    messages = twitter.messages(:received, { :count => 1 })
    processed[:message] = messages.first.id if messages.length > 0

    handle_tweets = !handlers.nil? && handlers_for_type(:tweet).length + handlers_for_type(:reply).length + handlers_for_type(:search).keys.length > 0
    # handle_tweets ||= handlers_for_type(:search).keys.length > 0
    tweets = []

    sandbox do
      tweets = handle_tweets ? twitter.timeline_for(config[:timeline_for], { :count => 1 }) : []
    end

    processed[:tweet] = tweets.first.id if tweets.length > 0
    processed[:reply] = tweets.first.id if tweets.length > 0

    # for searches, use latest tweet on public timeline
    #
    if handle_tweets && config[:timeline_for].to_s != "public"
      sandbox { tweets = twitter.timeline_for(:public, { :count => 1 }) }
    end
    if tweets.length > 0
      handlers_for_type(:search).each_key {|q| processed[:search][q] = tweets.first.id }
    end

    load_followers

  when Numeric, /\d+/ # a tweet ID to start from
    processed[:tweet] = processed[:reply] = processed[:message] = config[:process]
    handlers[:search].each_key {|q| processed[:search][q] = config[:process] }
  else abort "Unknown process option #{config[:process]}, aborting..."
  end

  load_friends unless handlers_for_type(:follower).empty?

  poll
end

#run_hook(event, *args) ⇒ Object

calls the hook method for the event if one has been defined



201
202
203
# File 'lib/twibot/bot.rb', line 201

def run_hook(event, *args)
  hooks[event.to_sym].call(*args) if hooks[event.to_sym].respond_to? :call
end

#with_hooks(type, &blk) ⇒ Object

invokes the given block, running the before and after hooks for the given type



304
305
306
307
308
309
310
# File 'lib/twibot/bot.rb', line 304

def with_hooks(type, &blk)
  event = type.is_a?(Array) ? type.first : type
  run_hook :"before_#{event}"
  value = yield
  run_hook :"after_#{event}"
  value
end