rubot 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,16 +5,25 @@ require "optparse"
5
5
  module Rubot
6
6
  module Core
7
7
  # Base class that handles the dirty work for IRC commands.
8
- # All commands belong in the /commands directory and
9
- # inherit this class.
10
- #
11
- # Since:: 0.0.1
12
8
  class Command
13
9
 
10
+ # Takes an instance of Rubot::Core::Dispatcher. Any
11
+ # child class that needs a constructor should override
12
+ # this method.
13
+ #
14
+ # ==== Parameters
15
+ # dispatcher<Rubot::Core::Dispatcher>:: The dispatcher that was used to create
16
+ # the instance of the command.
14
17
  def initialize(dispatcher)
15
18
  @dispatcher = dispatcher
16
19
  end
17
20
 
21
+ # Runs the command with the given server and message.
22
+ #
23
+ # ==== Parameters
24
+ # server<Rubot::Irc::Server>:: Server instance the command should use for
25
+ # messaging and information.
26
+ # message<Rubot::Irc::Message>:: The message that invoked the command.
18
27
  def run(server, message)
19
28
  if protected? && !message.authenticated
20
29
  server.msg(message.destination, "unauthorized")
@@ -34,29 +43,64 @@ module Rubot
34
43
  end
35
44
  end
36
45
 
46
+ # Whether the command is marked as protected or not (using Command::acts_as_protected).
37
47
  def protected?
38
48
  false
39
49
  end
40
50
 
41
51
  private
42
52
 
43
- # commands override this
53
+ # The internal execution of a command, called by #run. This is the method all
54
+ # children classes should override.
55
+ #
56
+ # ==== Paramters
57
+ # server<Rubot::Irc::Server>:: Server instance the command should use for messaging and information.
58
+ # message<Rubot::Irc:Message>:: The message that invoked the command.
59
+ # options<OpenStruct>:: The options that were parsed from the message that invoked the command. The
60
+ # parsing is handled in #run
44
61
  def execute(server, message, options)
45
62
  server.msg(message.destincation, "unimplemented")
46
63
  end
47
64
 
65
+ # Marks the command as protected. Commands that are protected can only be accessed by users who
66
+ # are authenticated. If you only need pieces of the command to be protected, this is not the method
67
+ # you're looking for. In that case, use Message#authenticated, which is populated automatically
68
+ # depending on the invoking user.
69
+ #
70
+ # ==== Example
71
+ # class Quit < Rubot::Core::Command
72
+ # acts_as_protected
73
+ #
74
+ # def execute(server, message, options)
75
+ # server.quit
76
+ # exit
77
+ # end
78
+ # end
48
79
  def self.acts_as_protected
49
80
  define_method(:protected?) do
50
81
  true
51
82
  end
52
83
  end
53
84
 
85
+ # Allows a command to be called by different names (aliases).
86
+ #
87
+ # ==== Example
88
+ # This command can be invoked by <em>!hi</em>, <em>!hello</em>, or <em>!whats_up</em>.
89
+ # class Hi < Rubot::Core::Command
90
+ # aliases :hello, :whats_up
91
+ #
92
+ # def execute(server, message, options)
93
+ # server.msg(message.destination, "hi everybody!")
94
+ # end
95
+ # end
54
96
  def self.aliases(*aliases)
55
97
  define_method(:aliases) do
56
98
  aliases
57
99
  end
58
100
  end
59
101
 
102
+ # Parses the given array using the default options (help) and any options specified
103
+ # by the child class by overriding #options.
60
104
  def parse(args)
61
105
  options = OpenStruct.new
62
106
  @parser = OptionParser.new do |parser|
@@ -73,7 +117,25 @@ module Rubot
73
117
  options
74
118
  end
75
119
 
76
- # override this to add more options in a command class
120
+ # Method to be overriden by child class if extra options are to be used. Options are
121
+ # parsed with OptParse.
122
+ #
123
+ # ==== Example
124
+ # class Hi < Rubot::Core::Command
125
+ #
126
+ # def execute(server, message, options)
127
+ # if options.bye
128
+ # server.msg(message.destination, "bye everybody!")
129
+ # else
130
+ # server.msg(message.destination, "hi everybody!")
131
+ # end
132
+ # end
133
+ #
134
+ # def options(parser, options)
135
+ # parser.on("-b", "--bye", "Instead of saying hi, say goodbye") do |bye|
136
+ # options.bye = bye
137
+ # end
138
+ # end
77
139
  def options(parser, options)
78
140
  end
79
141
  end
@@ -2,14 +2,36 @@ require "thread"
2
2
 
3
3
  module Rubot
4
4
  module Core
5
- # The middle man. The Dispatcher takes incomming messages
6
- # from the server and determines the appropriate action to
7
- # take, handing the messages off to commands or listeners.
8
- #
9
- # Since:: 0.0.1
5
+ # The middle man. The Dispatcher takes incomming messages from the server and
6
+ # determines the appropriate action to take, handing the messages off to commands
7
+ # and listeners.
10
8
  class Dispatcher
11
- attr_reader :commands, :listeners, :function_character, :config, :resource_lock
9
+ # Hash that holds instances of commands registered with the dispatcher. There
10
+ # is a single instance of each command, with its name, and aliases, as keys
11
+ # to that instance.
12
+ attr_reader :commands
13
+
14
+ # Hash that holds instances of listeners registered with the dispatcher. There
15
+ # is a single instance of each listener, with its name as the key to that instance.
16
+ attr_reader :listeners
17
+
18
+ # The value used to denote a command, this is pulled from config.
19
+ attr_reader :function_character
20
+
21
+ # The config hash that was used to create the Dispatcher instance.
22
+ attr_reader :config
23
+
24
+ # Mutex that should be used when accessing anything in <em>/resources</em>. This
25
+ # will most likely be moved to the resource manager when it is implemented.
26
+ attr_reader :resource_lock
12
27
 
28
+ # Creates an instance of Dispatcher using the given config hash. Values expected to
29
+ # be in this hash are:
30
+ # * function_character - The character used to denote a command
31
+ # * auth_list - Comma separated string of authenticated users
32
+ #
33
+ # ==== Parameters
34
+ # config<Hash>:: Hash containing config values
13
35
  def initialize(config)
14
36
  @config = config
15
37
  @function_character = @config["function_character"]
@@ -22,15 +44,28 @@ module Rubot
22
44
  load_dir "runners", @runners = {}
23
45
  end
24
46
 
47
+ # Called when successful connection is made to a server. This is when the runners are
48
+ # executed.
49
+ #
50
+ # ==== Parameters
51
+ # server<Rubot::Irc::Server>:: The server instance that has successfully connected
25
52
  def connected(server)
26
53
  run_runners(server)
27
54
  end
28
55
 
56
+ # Exposed method to reload all commands and listeners.
29
57
  def reload
30
58
  load_dir "commands", @commands = {}
31
59
  load_dir "listeners", @listeners = {}
32
60
  end
33
61
 
62
+ # Determines how to handle a message from the server. If the message fits the format
63
+ # of a command, an attempt is made to find and execute that command. Otherwise, the
64
+ # message is passed to the listeners.
65
+ #
66
+ # ==== Parameters
67
+ # server<Rubot::Irc::Server>:: The server where the messge was received
68
+ # message<Rubot:Irc::Message>:: The message to handle
34
69
  def handle_message(server, message)
35
70
  if message.body =~ /^#{@function_character}([a-z_]+)( .+)?$/i
36
71
  message.body = $2.nil? ? "" : $2.strip # remove the function name from the message
@@ -45,30 +80,57 @@ module Rubot
45
80
  end
46
81
  end
47
82
 
83
+ # Finds a command based on the message alias, and determines if the invoking user
84
+ # is authenticated.
85
+ #
86
+ # ==== Parameters
87
+ # message<Rubot::Irc::Message>:: The message to be used
48
88
  def command_from_message(message)
49
- command = @commands[message.alias]
89
+ command = @commands[message.alias]
50
90
  message.authenticated = authenticated?(message.from) unless command.nil?
51
91
  command
52
92
  end
53
93
 
94
+ # Determines if the given nick has authenticated with the bot.
95
+ #
96
+ # ==== Parameters
97
+ # nick<String>:: The nick to check for auth privledges
54
98
  def authenticated?(nick)
55
99
  @auth_list.include?(nick)
56
100
  end
57
101
 
102
+ # Adds nick to the authenticated list.
103
+ #
104
+ # ==== Parameters
105
+ # nick<String>:: The nick to add to the authenticated list
58
106
  def add_auth(nick)
59
107
  @auth_list << nick unless authenticated?(nick)
60
108
  end
61
109
 
110
+ # Removes nick from the authenticated list.
111
+ #
112
+ # ==== Parameters
113
+ # nick<String>:: The nick to remove from the authenticated list
62
114
  def remove_auth(nick)
63
115
  @auth_list.delete nick
64
116
  end
65
117
 
66
118
  private
67
119
 
120
+ # Runs all runners using the given server instance.
121
+ #
122
+ # ==== Parameters
123
+ # server<Rubot::Irc::Server>:: Server instance
68
124
  def run_runners(server)
69
125
  @runners.each_value {|runner| runner.run(server)}
70
126
  end
71
127
 
128
+ # Loads all files in the given directory and stores the class instances
129
+ # in the given set. This is used to load commands, listeners, and runners.
130
+ #
131
+ # ==== Parameters
132
+ # dir<String>:: Directory of files to load
133
+ # set<Hash>:: Hash to store class instances in
72
134
  def load_dir(dir, set)
73
135
  Dir["#{dir}/*.rb"].each do |file|
74
136
  load file
@@ -76,6 +138,14 @@ module Rubot
76
138
  end
77
139
  end
78
140
 
141
+ # Takes a file and creates an instance of the class within the file and stores it
142
+ # in hash, with the base filename as the key. If the class instance responds to
143
+ # :aliases, these are used as keys to the instance as well.
144
+ #
145
+ # ==== Parameters
146
+ # file<String>:: Filename to use for instantiation
147
+ # hash<Hash>:: Hash to store the instance in
148
+ # ext<String>:: File extension of the file to load
79
149
  def file_to_instance_hash(file, hash, ext = ".rb")
80
150
  name = File.basename(file, ext)
81
151
  clazz = eval(name.camelize).new(self)
@@ -1,10 +1,33 @@
1
1
  module Rubot
2
2
  module Core
3
+ # Base class for all listeners. A listener cannot be called directly, but
4
+ # <em>listens</em> to messages on a server.
5
+ #
6
+ # ==== Example
7
+ # This listener responds when the bot is greeted.
8
+ # class Greet < Rubot::Core::Listener
9
+ # def execute(server, message)
10
+ # server.msg(message.destination, "hi #{message.from}") if message.body == "hi #{server.nick}"
11
+ # end
12
+ # end
3
13
  class Listener
14
+ # Takes an instance of Rubot::Core::Dispatcher. Any
15
+ # child class that needs a constructor should override
16
+ # this method.
17
+ #
18
+ # ==== Parameters
19
+ # dispatcher<Rubot::Core::Dispatcher>:: The dispatcher that was used to create
20
+ # the instance of the listener.
4
21
  def initialize(dispatcher)
5
22
  @dispatcher = dispatcher
6
23
  end
7
24
 
25
+ # Runs the listener with the given server and message.
26
+ #
27
+ # ==== Paramters
28
+ # server<Rubot::Irc::Server>:: Server instance the listener should use for
29
+ # messaging and information.
30
+ # message<Rubot::Irc::Message>:: The message that invoked the command.
8
31
  def execute(server, message)
9
32
  puts "#{self} listener does not implement execute method"
10
33
  end
@@ -1,11 +1,35 @@
1
1
  module Rubot
2
2
  module Core
3
+ # Base class for all runners. Runners are intended to be executed upon
4
+ # successful connection to a server, and cannot be invoked from a user.
5
+ #
6
+ # ==== Example
7
+ # This runner greets every channel the bot is in upon server connection.
8
+ # class Greet < Rubot::Core::Runner
9
+ # def run(server)
10
+ # server.channels.each do |channel|
11
+ # server.msg(channel, "hi everybody!")
12
+ # end
13
+ # end
14
+ # end
3
15
  class Runner
4
16
 
17
+ # Takes an instance of Rubot::Core::Dispatcher. Any
18
+ # child class that needs a constructor should override
19
+ # this method.
20
+ #
21
+ # ==== Parameters
22
+ # dispatcher<Rubot::Core::Dispatcher>:: The dispatcher that was used to create
23
+ # the instance of the runner.
5
24
  def initialize(dispatcher)
6
25
  @dispatcher = dispatcher
7
26
  end
8
27
 
28
+ # Runs the runner with the given server.
29
+ #
30
+ # ==== Paramters
31
+ # server<Rubot::Irc::Server>:: Server instance the runner should use for
32
+ # messaging and information.
9
33
  def run(server)
10
34
  end
11
35
  end
@@ -1,4 +1,5 @@
1
1
  class Object
2
+ # Returns a reference to the obkect's eigen (singleton) class.
2
3
  def eigen_class
3
4
  class << self
4
5
  self
@@ -1,8 +1,10 @@
1
1
  class String
2
+ # Converts a string camel case. Taken from Rails source.
2
3
  def camelize
3
4
  self.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
4
5
  end
5
6
 
7
+ # Converts a string to underscore form. Taken from Rails source.
6
8
  def underscore
7
9
  self.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
8
10
  end
@@ -1,9 +1,29 @@
1
1
  module Rubot
2
2
  module Irc
3
+ # Represents an IRC message.
3
4
  class Message
4
- attr_accessor :destination, :body, :alias, :authenticated
5
+ # The location where the reply to this message should be sent. This defaults
6
+ # to the source of the message
7
+ attr_accessor :destination
8
+
9
+ # The body of the message.
10
+ attr_accessor :body
11
+
12
+ # If the message was used to invoke a command, this will be the command name.
13
+ attr_accessor :alias
14
+
15
+ # If the user that sent this message is authenticated, this will be true.
16
+ attr_accessor :authenticated
17
+
18
+ # The user that sent this message.
5
19
  attr_reader :from
6
20
 
21
+ # Initializes a new object with the given from, destination, and body.
22
+ #
23
+ # ==== Parameters
24
+ # from<String>:: The nick who sent the message
25
+ # destination<String>:: The destination where the reply to this message should be sent.
26
+ # body<String>:: The body of the message
7
27
  def initialize(from, destination, body)
8
28
  @from = from
9
29
  @destination = destination
@@ -1,14 +1,96 @@
1
1
  module Rubot
2
2
  module Irc
3
+ # Used to queue outgoing messages with a delay, so we don't get excess flood
4
+ # kicked for spamming. The actual actions taken can be whatever they need to
5
+ # be. Currently they're just sending a message and sending an action to the
6
+ # server.
7
+ #
8
+ # I'll rehash the Rubot::Irc::Server code with a few modifications to
9
+ # demonstrate how it works.
10
+ #
11
+ # ==== Example
12
+ # This code initializes a message queue with a two second delay, then defines
13
+ # two methods with different actions.
14
+ #
15
+ # @message_queue = MessageQueue.new(2)
16
+ #
17
+ # @message_queue.message do |destination, message|
18
+ # puts "MESSAGE to #{destination} with body #{message}"
19
+ # end
20
+ #
21
+ # @message_queue.action do |destination, action|
22
+ # puts "ACTION to #{destination} with body #{action}"
23
+ # end
24
+ #
25
+ # After executing this code, the :message and :action methods are now available
26
+ # for this queue. Each call to these methods adds another action to the queue
27
+ # with the appropriate action.
28
+ #
29
+ # To use our newly created queue:
30
+ #
31
+ # @message_queue.message "destination_one", "i'm a message"
32
+ # @message_queue.action "destination_two", "i'm an action"
33
+ # @message_queue.message "destination_one", "i'm anoter message"
34
+ #
35
+ # This will yield the following output:
36
+ #
37
+ # MESSAGE to destination_one with body i'm a message
38
+ # # two second delay
39
+ # ACTION to destination_two with body i'm an action
40
+ # # two second delay
41
+ # MESSAGE to destination_one with body i'm another message
42
+ #
43
+ # These created methods are tailored for messages, and can accept string, arrays,
44
+ # or exploded arrays as parameters, as long as the first parameter is the destination.
45
+ # In the case of (exploded) arrays, each element in the array will be treated
46
+ # as a separate call to the block used to create the method.
47
+ #
48
+ # ==== Fancy Pants Paraters
49
+ # Assuming the code above has been executed, we can run the following:
50
+ #
51
+ # @message_queue.message "destination_three", "message one", "message two", "message three"
52
+ # actions = ["action one", "action_two", "action_three"]
53
+ # @message_queue.action "destination_two", actions
54
+ #
55
+ # which will produce:
56
+ #
57
+ # MESSAGE to destination_three with body message one
58
+ # # two second delay
59
+ # MESSAGE to destination_three with body message two
60
+ # # two second delay
61
+ # MESSAGE to destination_three with body message three
62
+ # # two second delay
63
+ # ACTION to destination_two with body action one
64
+ # # two second delay
65
+ # ACTION to destination_two with body action two
66
+ # # two second delay
67
+ # ACTION to destination_two with body action three
3
68
  class MessageQueue
69
+ # The delay in seconds to be used.
4
70
  attr_accessor :delay
5
71
 
72
+ # Initiliazes a new queue with the given delay.
73
+ #
74
+ # ==== Parameters
75
+ # delay<Fixnum>:: Delay in seconds
6
76
  def initialize(delay)
7
77
  @delay = delay
8
78
  @lock = Mutex.new
9
79
  @queue = []
10
80
  end
11
81
 
82
+ # This is where the magic happens. If we're sent a method we don't respond to,
83
+ # and it contains a block, we create a new method using that block. If no block
84
+ # is given, we forward to the base method_missing.
85
+ #
86
+ # This method makes an assumption about the block that's given. It is assumed
87
+ # that the block takes two arguments, a destination and a message (This is a
88
+ # message queue after all).
89
+ #
90
+ # ==== Parameters
91
+ # method<Symbol>:: Method name
92
+ # args<Array>:: Parameters given to the method
93
+ # block<Proc>:: The block that was passed
12
94
  def method_missing(method, *args, &block)
13
95
  if block
14
96
  eigen_class.instance_eval do
@@ -28,6 +110,7 @@ module Rubot
28
110
  end
29
111
 
30
112
  private
113
+ # Flushes the queue, executing each action it contains
31
114
  def flush
32
115
  until @queue.empty?
33
116
  element = @queue.shift
@@ -2,10 +2,26 @@ require "socket"
2
2
 
3
3
  module Rubot
4
4
  module Irc
5
+ # Performs the dirty work of communicating with an IRC server, passing messages
6
+ # back and forth between the server and a dispatcher.
5
7
  class Server
6
8
  include Rubot::Irc::Constants
7
- attr_reader :nick, :connected_at, :channels
8
-
9
+ # Our current nick
10
+ attr_reader :nick
11
+
12
+ # When a successful connection to the server was made
13
+ attr_reader :connected_at
14
+
15
+ # The list of channels we're currently in
16
+ attr_reader :channels
17
+
18
+ # Initializes a Server using the given dispatcher.
19
+ #
20
+ # The connection info to be used is extracted from the dispatcher's config method. (This
21
+ # needs to be changed)
22
+ #
23
+ # ==== Parameters
24
+ # dispatcher<Rubot::Core::Dispatcher>:: The dispatcher to handle the messages we receive
9
25
  def initialize(dispatcher)
10
26
  dispatcher.config["server"].each_pair do |key, value|
11
27
  instance_variable_set("@#{key}".to_sym, value)
@@ -24,6 +40,7 @@ module Rubot
24
40
  end
25
41
  end
26
42
 
43
+ # Attempts to make a connection to the server
27
44
  def connect
28
45
  return if @is_connected
29
46
 
@@ -42,55 +59,88 @@ module Rubot
42
59
  end
43
60
  end
44
61
 
62
+ # Sends the quit command to the IRC server, then closes the connection.
45
63
  def quit
46
64
  raw "QUIT :#{@quit_message}"
47
65
  @conn.close
48
66
  end
49
67
 
68
+ # Changes our nick.
50
69
  def change_nick(new_nick)
51
70
  raw "NICK #{new_nick}"
52
71
  @nick = new_nick
53
72
  end
54
73
 
74
+ # Joins the specified channels, which can be comma separated string or an array.
75
+ #
76
+ # ==== Parameters
77
+ # channels<String,Array>:: The channels to join
55
78
  def join(channels)
56
79
  channels = channels.split(',') if channels.is_a? String
57
80
  @channels.concat(channels).uniq!
58
81
  bulk_command("JOIN %s", channels)
59
82
  end
60
83
 
84
+ # Parts the specified channels, which can be comma separated string or an array.
85
+ #
86
+ # ==== Parameters
87
+ # channels<String,Array>:: The channels to part
61
88
  def part(channels)
62
89
  channels = channels.split(',') if channels.is_a? String
63
90
  @channels.reject! { |channel| channels.include?(channel) }
64
91
  bulk_command("PART %s :#{@quit_message}", channels)
65
92
  end
66
93
 
94
+ # Adds message(s) to the outgoing queue
95
+ #
96
+ # ==== Parameters
97
+ # destination<String>:: Where to send the message
98
+ # message<String,Array>:: The message(s) to be sent
67
99
  def msg(destination, message)
68
100
  @message_queue.message(destination, message)
101
+ # TODO use build_message_array with the queue
69
102
  #message = message.to_s.split("\n") unless message.is_a? Array
70
103
  #build_message_array(message).each do |l|
71
104
  # raw "PRIVMSG #{destination} :#{l}"
72
105
  #end
73
106
  end
74
-
75
- def action(destination, message)
76
- @message_queue.action(destination, message)
77
- #msg(destination, "\001ACTION #{message}\001")
78
- end
79
-
107
+
108
+ # Adds action(s) to the outgoing queue
109
+ #
110
+ # ==== Parameters
111
+ # destination<String>:: Where to send the message
112
+ # action<String,Array>:: The actions(s) to be sent
113
+ def action(destination, action)
114
+ @message_queue.action(destination, action)
115
+ end
116
+
117
+ # Sends a raw line to the server and dumps it the console (to be replaced with a logger)
118
+ #
119
+ # ==== Parameters
120
+ # message<String>:: The raw line to send
80
121
  def raw(message)
81
122
  puts "--> #{message}"
82
123
  @conn.puts "#{message}\n"
83
124
  end
84
125
 
126
+ # Get the list of nicks in the given channel. I'm not sure if the current implementation
127
+ # is really how it should be. TODO investigate
128
+ #
129
+ # ==== Parameters
130
+ # channel<String>:: The channel whose nicks we want
85
131
  def names(channel)
86
132
  raw "NAMES #{channel}"
87
133
  @conn.gets.split(":")[2].split(" ")
88
134
  end
89
135
 
90
136
  private
91
-
137
+
138
+ # The loop that goes on forever, reading input from the server.
92
139
  def main_loop
93
- while true
140
+ loop do
141
+ # something about this doesn't seem right. why are we using select and creating
142
+ # the ready object if we're never using it. TODO investigate wtf is going on here
143
+ # and find a better solution
94
144
  ready = select([@conn])
95
145
  next unless ready
96
146
 
@@ -100,8 +150,9 @@ module Rubot
100
150
  end
101
151
  end
102
152
 
153
+ # Determines what to do with the input from the server
103
154
  def handle_server_input(s)
104
- puts s
155
+ puts s # TODO logger
105
156
 
106
157
  case s.strip
107
158
  when /^PING :(.+)$/i
@@ -126,6 +177,7 @@ module Rubot
126
177
  end
127
178
  end
128
179
 
180
+ # Handles predefined events from the server.
129
181
  def handle_meta(server, code, message)
130
182
  case code
131
183
  when ERR_NICK_IN_USE
@@ -143,6 +195,8 @@ module Rubot
143
195
  end
144
196
  end
145
197
 
198
+ # Preps the string for sending to the server by breaking it down into
199
+ # an array if it's too long
146
200
  def string_to_irc_lines(str)
147
201
  str.split(" ").inject([""]) do |arr, word|
148
202
  arr.push("") if arr.last.size > MAX_MESSAGE_LENGTH
@@ -151,6 +205,8 @@ module Rubot
151
205
  end.map(&:strip)
152
206
  end
153
207
 
208
+ # Ensures each string in the array is shorter than the max allowed message
209
+ # length, breaking the string if necessary
154
210
  def build_message_array(arr)
155
211
  arr.each_with_index.map do |message, index|
156
212
  message.size > MAX_MESSAGE_LENGTH ? string_to_irc_lines(message) : arr[index]
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 1
9
+ version: 0.1.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Chris Thorn
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-23 00:00:00 -08:00
17
+ date: 2010-04-07 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -28,42 +28,34 @@ extra_rdoc_files:
28
28
  - README.rdoc
29
29
  files:
30
30
  - bin/rubot
31
- - bin/rubot~
31
+ - lib/core/command.rb
32
+ - lib/core/dispatcher.rb
33
+ - lib/core/listener.rb
34
+ - lib/core/runner.rb
32
35
  - lib/core.rb
33
- - lib/irc/message.rb
34
- - lib/irc/server.rb
35
- - lib/irc/constants.rb
36
- - lib/irc/message_queue.rb
37
- - lib/irc/server.rb~
38
- - lib/generators/command.rb~
39
- - lib/generators/listener.template~
40
- - lib/generators/listeners.template
41
- - lib/generators/commands.template
42
- - lib/generators/command.template~
43
- - lib/generators/runner.template~
44
- - lib/generators/runners.template
45
36
  - lib/extensions/object.rb
46
37
  - lib/extensions/string.rb
47
- - lib/template/commands/reload.rb
48
- - lib/template/commands/quit.rb
49
- - lib/template/commands/part.rb
50
- - lib/template/commands/msg.rb
51
- - lib/template/commands/up_time.rb
52
- - lib/template/commands/join.rb
38
+ - lib/extensions.rb
39
+ - lib/generators/commands.template
40
+ - lib/generators/listeners.template
41
+ - lib/generators/runners.template
42
+ - lib/irc/constants.rb
43
+ - lib/irc/message.rb
44
+ - lib/irc/message_queue.rb
45
+ - lib/irc/server.rb
46
+ - lib/irc.rb
47
+ - lib/rubot.rb
53
48
  - lib/template/commands/auth.rb
54
49
  - lib/template/commands/help.rb
50
+ - lib/template/commands/join.rb
51
+ - lib/template/commands/msg.rb
52
+ - lib/template/commands/part.rb
53
+ - lib/template/commands/quit.rb
54
+ - lib/template/commands/reload.rb
55
+ - lib/template/commands/up_time.rb
55
56
  - lib/template/main.rb
56
57
  - lib/template/resources/config.yml
57
- - lib/core/dispatcher.rb
58
- - lib/core/listener.rb
59
- - lib/core/command.rb
60
- - lib/core/runner.rb
61
- - lib/core/runner.rb~
62
- - lib/irc.rb
63
58
  - lib/template.rb
64
- - lib/extensions.rb
65
- - lib/rubot.rb
66
- - lib/template.rb~
67
59
  - README.rdoc
68
60
  has_rdoc: true
69
61
  homepage: http://github.com/thorncp/rubot
data/bin/rubot~ DELETED
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "optparse"
4
- require "ostruct"
5
- require "template"
6
-
7
- options = OpenStruct.new
8
- parser = OptionParser.new do |parser|
9
- parser.banner = "Usage: #{__FILE__} <options>"
10
-
11
- parser.separator ""
12
- parser.separator "Initialization"
13
-
14
- parser.on("-i", "--init ProjectName", "Initializes a new rubot project in the ProjectName directory") do |name|
15
- init name
16
- exit
17
- end
18
-
19
- parser.separator ""
20
- parser.separator "Generators"
21
-
22
- parser.on("-c", "--command Name", "Generates a new command named Name") do |name|
23
- generate_command(name)
24
- exit
25
- end
26
-
27
- parser.on("-l", "--listener Name", "Generates a new listener named Name") do |listener|
28
- generate_listener(name)
29
- exit
30
- end
31
-
32
- parser.on("-r", "--runner Name", "Generates a new listener named Name") do |runner|
33
- generate_runner(name)
34
- exit
35
- end
36
-
37
- parser.separator ""
38
- parser.separator "Common options:"
39
-
40
- parser.on_tail("-h", "--help", "Show this message") do
41
- puts parser
42
- exit
43
- end
44
- end
45
-
46
- parser.parse!(ARGV)
47
- puts parser
48
-
@@ -1,13 +0,0 @@
1
- module Rubot
2
- module Core
3
- class BaseRunner
4
-
5
- def initialize(dispatcher)
6
- @dispatcher = dispatcher
7
- end
8
-
9
- def run(server)
10
- end
11
- end
12
- end
13
- end
@@ -1,5 +0,0 @@
1
- class < Rubot::Core::Command
2
- def execute(server, message, options)
3
- server.msg(message.destination, "unimplemented")
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class $NAME$ < Rubot::Core::Command
2
- def execute(server, message, options)
3
- server.msg(message.destination, "unimplemented")
4
- end
5
- end
@@ -1,7 +0,0 @@
1
- class --NAME-- < Rubot::Core::Listener
2
- def execute(server, message)
3
- if message.from == server.nick
4
- server.msg(message.destination, "hi #{message.from}")
5
- end
6
- end
7
- end
@@ -1,5 +0,0 @@
1
- class --NAME-- < Rubot::Core::Runner
2
- def run(server)
3
- server.msg(message.destination, "unimplemented")
4
- end
5
- end
@@ -1,162 +0,0 @@
1
- require "socket"
2
-
3
- module Rubot
4
- module Irc
5
- class Server
6
- include Rubot::Irc::Constants
7
- attr_reader :nick, :connected_at, :channels
8
-
9
- def initialize(dispatcher)
10
- dispatcher.config["server"].each_pair do |key, value|
11
- instance_variable_set("@#{key}".to_sym, value)
12
- end
13
- @channels = @channels.split(",").collect(&:strip)
14
- @dispatcher = dispatcher
15
-
16
- @message_queue = MessageQueue.new(@message_delay)
17
-
18
- @message_queue.message do |destination, message|
19
- raw "PRIVMSG #{destination} :#{message}"
20
- end
21
-
22
- @message_queue.action do |destination, action|
23
- raw "PRIVMSG #{destination} :\001ACTION #{action}\001"
24
- end
25
- end
26
-
27
- def connect
28
- return if @is_connected
29
-
30
- @conn = TCPSocket.open(@host, @port, @vhost)
31
- raw "USER #{@nick} #{@nick} #{@nick} :#{@real_name}"
32
- change_nick @nick
33
- join @channels
34
-
35
- begin
36
- main_loop()
37
- rescue Interrupt
38
- rescue Exception => detail
39
- puts detail.message()
40
- print detail.backtrace.join("\n")
41
- retry
42
- end
43
- end
44
-
45
- def quit
46
- raw "QUIT :#{@quit_message}"
47
- @conn.close
48
- end
49
-
50
- def change_nick(new_nick)
51
- raw "NICK #{new_nick}"
52
- @nick = new_nick
53
- end
54
-
55
- def join(channels)
56
- channels = channels.split(',') if channels.is_a? String
57
- @channels.concat(channels).uniq!
58
- bulk_command("JOIN %s", channels)
59
- end
60
-
61
- def part(channels)
62
- channels = channels.split(',') if channels.is_a? String
63
- @channels.reject! { |channel| channels.include?(channel) }
64
- bulk_command("PART %s :#{@quit_message}", channels)
65
- end
66
-
67
- def msg(destination, message)
68
- @message_queue.message(destination, message)
69
- #message = message.to_s.split("\n") unless message.is_a? Array
70
- #build_message_array(message).each do |l|
71
- # raw "PRIVMSG #{destination} :#{l}"
72
- #end
73
- end
74
-
75
- def action(destination, message)
76
- @message_queue.action(destination, message)
77
- #msg(destination, "\001ACTION #{message}\001")
78
- end
79
-
80
- def raw(message)
81
- puts "--> #{message}"
82
- @conn.puts "#{message}\n"
83
- end
84
-
85
- def names(channel)
86
- raw "NAMES #{channel}"
87
- @conn.gets.split(":")[2].split(" ")
88
- end
89
-
90
- private
91
-
92
- def main_loop
93
- while true
94
- ready = select([@conn])
95
- next unless ready
96
-
97
- return if @conn.eof
98
- s = @conn.gets
99
- handle_server_input(s)
100
- end
101
- end
102
-
103
- def handle_server_input(s)
104
- puts s
105
-
106
- case s.strip
107
- when /^PING :(.+)$/i
108
- raw "PONG :#{$1}"
109
- when /^:([-.0-9a-z]+)\s([0-9]+)\s(.+)\s(.*)$/i
110
- handle_meta($1, $2.to_i, $4)
111
- when /^:(.+?)!(.+?)@(.+?)\sPRIVMSG\s(.+)\s:(.+)$/i
112
- message = Rubot::Irc::Message.new($1, $4 == @nick ? $1 : $4, $5)
113
- # TODO add ability to pass events other than privmsg to dispatcher. ie, nick changes, parts, joins, quits, bans, etc, etc
114
- @dispatcher.handle_message(self, message)
115
- end
116
- end
117
-
118
- # performs the same command on each element in the given collection, separated by comma
119
- def bulk_command(formatted_string, elements)
120
- if elements.is_a? String
121
- elements = elements.split(',')
122
- end
123
-
124
- elements.each do |e|
125
- raw sprintf(formatted_string, e.to_s.strip)
126
- end
127
- end
128
-
129
- def handle_meta(server, code, message)
130
- case code
131
- when ERR_NICK_IN_USE
132
- if @nick == @alt_nick
133
- puts "all nicks used, don't know how to name myself."
134
- quit
135
- exit!
136
- end
137
- change_nick @alt_nick
138
- join @channels
139
- when WELLCOME
140
- @dispatcher.connected(self)
141
- @is_connected = true
142
- @connected_at = Time.now
143
- end
144
- end
145
-
146
- def string_to_irc_lines(str)
147
- str.split(" ").inject([""]) do |arr, word|
148
- arr.push("") if arr.last.size > MAX_MESSAGE_LENGTH
149
- arr.last << "#{word} "
150
- arr
151
- end.map(&:strip)
152
- end
153
-
154
- def build_message_array(arr)
155
- arr.each_with_index.map do |message, index|
156
- message.size > MAX_MESSAGE_LENGTH ? string_to_irc_lines(message) : arr[index]
157
- end.flatten
158
- end
159
- end
160
- end
161
- end
162
-
@@ -1,52 +0,0 @@
1
- require "fileutils"
2
- require "extensions/string"
3
-
4
- def init(name)
5
- if Dir.exist? name
6
- puts "directory '#{name}' already exists"
7
- return
8
- end
9
-
10
- template_dir = File.join(File.dirname(__FILE__), "template")
11
-
12
- FileUtils.cp_r(template_dir, name)
13
-
14
- dir_structure = %w{resources commands listeners runners}
15
-
16
- # for some reason gem doesn't include empty folders during the install.
17
- # this is to make sure these directories exist
18
- dir_structure.each do |dir|
19
- FileUtils.mkdir File.join(name, dir) unless File.exist? File.join(name, dir)
20
- end
21
- end
22
-
23
- def generate_command(name)
24
- #command = File.join("commands", "#{name.underscore}.rb")
25
- #template_file = File.join(File.dirname(__FILE__), "generators", "command.template")
26
- #source = IO.read(template_file).gsub(/--NAME--/, name.camelize)
27
- #File.open(command, "w") {|file| file.write(source)}
28
- generate("commands", name)
29
- end
30
-
31
- def generate_listener(name)
32
- #template_file = File.join(File.dirname(__FILE__), "generators", "listener.template")
33
- #command = IO.read(template_file).gsub(/--NAME--/, name.camelize)
34
- #File.open("listeners/#{name.underscore}.rb", "w") {|file| file.write(command)}
35
- generate("listeners", name)
36
- end
37
-
38
- def generate_runner(name)
39
- #template_file = File.join(File.dirname(__FILE__), "generators", "runner.template")
40
- #command = IO.read(template_file).gsub(/--NAME--/, name.camelize)
41
- #File.open("runners/#{name.underscore}.rb", "w") {|file| file.write(command)}
42
- generate("runners", name)
43
- end
44
-
45
- def generate(template, name)
46
- filename = File.join(template, "#{name.underscore}.rb")
47
- puts "file '#{filename}' already exists" and return if File.exist?(filename)
48
-
49
- template_file = File.join(File.dirname(__FILE__), "generators", "#{template}.template")
50
- source = IO.read(template_file).gsub(/--NAME--/, name.camelize)
51
- File.open(filename, "w") {|file| file.write(source)}
52
- end