rubot 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|