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
37
38
39
# 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

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.



270
271
272
# File 'lib/twittbot/bot.rb', line 270

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

#authObject

Authenticates an account with Twitter.



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
67
68
69
# 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

#check_configObject

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



180
181
182
183
184
185
# 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

#cron(task_name) ⇒ Object

Runs the task ‘task_name`.

Parameters:

  • task_name (Symbol)

    The name of the task to be run.



131
132
133
134
135
136
137
138
139
140
141
# 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

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

Runs callbacks.

Parameters:

  • callback_type (:Symbol)

    The callback type.

  • object (Object)

    The object



230
231
232
233
234
235
# 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

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

Processes a direct message.

Parameters:



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# 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

#do_periodicObject



237
238
239
240
241
242
243
244
245
246
247
# 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

#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.



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# 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

#load_bot_codeObject

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



162
163
164
165
166
167
168
# 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

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

Parameters:

  • screen_name (String)

    the user’s screen name

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

    :add or :delete



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# 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

#save_configObject

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



171
172
173
174
175
176
177
# 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

#startObject

Starts the bot.



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
117
118
119
120
121
122
123
124
125
126
127
# 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