rubot 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/core/command.rb +68 -6
- data/lib/core/dispatcher.rb +77 -7
- data/lib/core/listener.rb +23 -0
- data/lib/core/runner.rb +24 -0
- data/lib/extensions/object.rb +1 -0
- data/lib/extensions/string.rb +2 -0
- data/lib/irc/message.rb +21 -1
- data/lib/irc/message_queue.rb +83 -0
- data/lib/irc/server.rb +67 -11
- metadata +23 -31
- data/bin/rubot~ +0 -48
- data/lib/core/runner.rb~ +0 -13
- data/lib/generators/command.rb~ +0 -5
- data/lib/generators/command.template~ +0 -5
- data/lib/generators/listener.template~ +0 -7
- data/lib/generators/runner.template~ +0 -5
- data/lib/irc/server.rb~ +0 -162
- data/lib/template.rb~ +0 -52
data/lib/core/command.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
data/lib/core/dispatcher.rb
CHANGED
@@ -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
|
-
#
|
7
|
-
#
|
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
|
-
|
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
|
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)
|
data/lib/core/listener.rb
CHANGED
@@ -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
|
data/lib/core/runner.rb
CHANGED
@@ -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
|
data/lib/extensions/object.rb
CHANGED
data/lib/extensions/string.rb
CHANGED
@@ -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
|
data/lib/irc/message.rb
CHANGED
@@ -1,9 +1,29 @@
|
|
1
1
|
module Rubot
|
2
2
|
module Irc
|
3
|
+
# Represents an IRC message.
|
3
4
|
class Message
|
4
|
-
|
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
|
data/lib/irc/message_queue.rb
CHANGED
@@ -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
|
data/lib/irc/server.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
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
|
-
-
|
9
|
-
version: 0.1.
|
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-
|
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
|
-
-
|
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/
|
48
|
-
- lib/
|
49
|
-
- lib/
|
50
|
-
- lib/
|
51
|
-
- lib/
|
52
|
-
- lib/
|
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
|
-
|
data/lib/core/runner.rb~
DELETED
data/lib/generators/command.rb~
DELETED
data/lib/irc/server.rb~
DELETED
@@ -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
|
-
|
data/lib/template.rb~
DELETED
@@ -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
|