tinyirc 0.1.0

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.
@@ -0,0 +1,109 @@
1
+ class TinyIRC::Command
2
+ class << self
3
+ attr_reader :log
4
+ end
5
+
6
+ @log = ParticleLog.new('commands', ParticleLog::INFO)
7
+
8
+ class Branch
9
+ attr_reader :cmd, :id, :definition, :handler, :last_uses
10
+ attr_accessor :cooldown, :description
11
+
12
+ def initialize(cmd, id, definition, handler)
13
+ @cmd = cmd
14
+ @cooldown = 0
15
+ @id = id
16
+ @definition = definition
17
+ @description = ''
18
+ @handler = handler
19
+ @last_uses = {}
20
+ end
21
+ end
22
+
23
+ attr_reader :plugin, :name, :branches
24
+
25
+ def initialize(plugin, name)
26
+ @plugin = plugin
27
+ @name = name
28
+ @branches = {}
29
+ TinyIRC::Command.log.info "commands += #{@plugin.name}/#{name}"
30
+ end
31
+
32
+ def branch(id, definition, &handler)
33
+ @branches[id] = Branch.new(self, id, ParticleCMD::Definition.from_string(@name, definition), handler)
34
+ TinyIRC::Command.log.info "#{@plugin.name}/#{@name} += #{id}: #{@branches[id].definition.command_signature(true)}"
35
+ @branches[id]
36
+ end
37
+
38
+ def handle_command(h, cmd_info)
39
+ pname = h[:cmd_info][:plugin]
40
+ pname = @plugin.name if pname == :any
41
+ cname = h[:cmd_info][:command]
42
+ bname = h[:cmd_info][:branch]
43
+
44
+ def checkperm(h, fname)
45
+ h[:socket].has_perm(h[:host], fname)
46
+ end
47
+
48
+ def checkcd(h, branch)
49
+ return true if h[:socket].has_group(h[:host], 'admin')
50
+ unless branch.last_uses[h[:socket].name]
51
+ branch.last_uses[h[:socket].name] = {}
52
+ return true
53
+ end
54
+ current = Time.now.to_i
55
+ last = branch.last_uses[h[:socket].name][h[:nick]].to_i
56
+ diff = current - last
57
+ if diff >= branch.cooldown || diff < 0
58
+ branch.last_uses[h[:socket].name][h[:nick]] = current
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ if (bname != :any)
66
+ if @branches.include? bname
67
+ branch = @branches[bname]
68
+ res = branch.definition.match cmd_info
69
+ if res
70
+ bname = branch.id
71
+ fname = pname + '/' + cname + '/' + bname
72
+ unless checkperm(h, fname)
73
+ h.nreply "Sorry, but you cannot execute this command (#{fname})"
74
+ return :denied
75
+ end
76
+ unless checkcd(h, branch)
77
+ h.nreply "Sorry, but you cannot execute this command now (cooldown - #{branch.cooldown}s)"
78
+ return :cooldown
79
+ end
80
+ branch.handler.(h, res)
81
+ true
82
+ else
83
+ false
84
+ end
85
+ else
86
+ false
87
+ end
88
+ else
89
+ @branches.each_pair do |id, branch|
90
+ res = branch.definition.match cmd_info
91
+ if res
92
+ bname = branch.id
93
+ fname = pname + '/' + cname + '/' + bname
94
+ unless checkperm(h, fname)
95
+ h.nreply "Sorry, but you cannot execute this command (#{fname})"
96
+ return :denied
97
+ end
98
+ unless checkcd(h, branch)
99
+ h.nreply "Sorry, but you cannot execute this command now (cooldown - #{branch.cooldown}s)"
100
+ return :cooldown
101
+ end
102
+ branch.handler.(h, res)
103
+ return true
104
+ end
105
+ end
106
+ false
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,12 @@
1
+ module TinyIRC
2
+ def self.define_event_methods(h)
3
+ h.define_singleton_method :reply do |msg|
4
+ raise RuntimeError, ':reply action is not supported' unless self[:reply_to]
5
+ self[:socket].privmsg(self[:reply_to], msg)
6
+ end
7
+ h.define_singleton_method :nreply do |msg|
8
+ raise RuntimeError, ':nreply action is not supported' unless self[:nick]
9
+ self[:socket].notice(self[:nick], msg)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,231 @@
1
+ class TinyIRC::IRCSocket
2
+ attr_accessor :name, :host, :port, :nick, :user, :pass, :rnam, :autojoin, :prefix
3
+ attr_accessor :bot, :reconnects, :running, :last_write
4
+ attr_reader :mtx, :log, :sock, :queue, :usercache
5
+
6
+ def initialize(bot, name, opts)
7
+ @bot = bot
8
+ @name = name
9
+ @host = opts['host'] || '127.0.0.1'
10
+ @port = opts['port'] || 6667
11
+ @nick = opts['nick'] || 'TinyBot'
12
+ @user = opts['user'] || @nick
13
+ @pass = opts['pass'] || nil
14
+ @rnam = opts['rnam'] || 'An IRC bot in Ruby'
15
+ @autojoin = opts['autojoin'] || []
16
+ @prefix = opts['prefix'] || bot.prefix || '!'
17
+
18
+ @reconnects = 0
19
+ @running = false
20
+ @last_write = Time.now
21
+
22
+ @mtx = Mutex.new
23
+ @log = ParticleLog.new("!#{name}", ParticleLog::INFO)
24
+ @log.level_table[ParticleLog::IO] = 'IRC'
25
+ @log.important "Hello, #{name}!"
26
+
27
+ @sock = nil
28
+ @queue = Queue.new
29
+ @usercache = TinyIRC::UserCache.new
30
+ end
31
+
32
+ def connect(reconnect=false)
33
+ @log.important 'Connecting...'
34
+ @mtx.synchronize do
35
+ begin
36
+ @sock = TCPSocket.new @host, @port
37
+ @running = true
38
+ @bot.handle_event type: :connect, socket: self
39
+ Thread.new do
40
+ authenticate
41
+ end
42
+ rescue => e
43
+ @log.error "#{e.class.name} - #{e.message}"
44
+ @bot.handle_event type: :disconnect, socket: self
45
+ end
46
+ end
47
+ end
48
+
49
+ def disconnect
50
+ @mtx.synchronize do
51
+ @running = false
52
+ sock.close
53
+ end
54
+ end
55
+
56
+ def authenticate
57
+ direct_write "PASS #{@pass}" if @pass
58
+ direct_write "NICK #{@nick}"
59
+ direct_write "USER #{@user} 0 * :#{@rnam}"
60
+ end
61
+
62
+ def direct_write(msg)
63
+ log.io "W> #{msg}"
64
+ @sock.write "#{msg}\r\n"
65
+ end
66
+
67
+ def write(msg)
68
+ @queue.push msg
69
+ end
70
+
71
+ def inspect
72
+ "#<TinyIRC::IRCSocket @name=#{@name.inspect}>"
73
+ end
74
+
75
+ def has_perm(host, name)
76
+ if (@bot.db.execute(
77
+ 'SELECT EXISTS(SELECT 1 FROM groupinfo WHERE server=? AND host=? AND name="admin" LIMIT 1)',
78
+ [@name, host]
79
+ ).flatten[0] == 1)
80
+ return true
81
+ end
82
+
83
+ groups = @bot.db.execute("SELECT name FROM groupinfo WHERE server=? AND host=?", [@name, host])
84
+ groups = groups.flatten.to_set
85
+ groups << 'world'
86
+
87
+ def where(gname)
88
+ s = gname.split('/', 2)
89
+ if s.length == 1
90
+ @bot.groups[gname]
91
+ elsif s.length == 2
92
+ return nil unless @bot.plugins.include? s[0]
93
+ @bot.plugins[s[0]].groups[gname]
94
+ else
95
+ nil
96
+ end
97
+
98
+ end
99
+
100
+ groups.each do |gname|
101
+ w = where(gname)
102
+ next unless w
103
+ w.perms.each do |perm|
104
+ if perm == TinyIRC::Permission.new(*TinyIRC::Permission.parse(name))
105
+ return true
106
+ end
107
+ end
108
+ end
109
+
110
+ false
111
+ end
112
+
113
+ def set_group(host, name)
114
+ res = @bot.db.execute(
115
+ 'SELECT EXISTS(SELECT 1 FROM groupinfo WHERE server=? AND host=? AND name=? LIMIT 1)',
116
+ [@name, host, name]
117
+ ).flatten[0]
118
+ if res == 0
119
+ @bot.db.execute('INSERT INTO groupinfo (server, host, name) VALUES (?, ?, ?)', [@name, host, name])
120
+ end
121
+ end
122
+
123
+ def del_group(host, name)
124
+ @bot.db.execute('DELETE FROM groupinfo WHERE server=? AND host=? AND name=?', [@name, host, name])
125
+ end
126
+
127
+ def has_group(host, name)
128
+ if (@bot.db.execute(
129
+ 'SELECT EXISTS(SELECT 1 FROM groupinfo WHERE server=? AND host=? AND name="admin" LIMIT 1)',
130
+ [@name, host]
131
+ ).flatten[0] == 0) then
132
+ @bot.db.execute(
133
+ 'SELECT EXISTS(SELECT 1 FROM groupinfo WHERE server=? AND host=? AND name=? LIMIT 1)',
134
+ [@name, host, name]
135
+ ).flatten[0] == 1
136
+ else
137
+ true
138
+ end
139
+ end
140
+
141
+ def list_groups(host)
142
+ @bot.db.execute(
143
+ 'SELECT name FROM groupinfo WHERE server=? AND host=?',
144
+ [@name, host]
145
+ ).flatten
146
+ end
147
+
148
+ def join(chan, key = nil)
149
+ if key
150
+ write "JOIN #{chan} #{key}"
151
+ else
152
+ write "JOIN #{chan}"
153
+ end
154
+ end
155
+
156
+ def part(chan, reason = 'Bye')
157
+ write "PART #{chan} :#{reason}"
158
+ end
159
+
160
+ def self.ssplit(string)
161
+ out = []
162
+ arr = string.split("\n\r")
163
+ arr.each do |i|
164
+ items = i.scan(/.{,399}/)
165
+ items.delete('')
166
+ items.each do |i2|
167
+ out << i2
168
+ end
169
+ end
170
+ out
171
+ end
172
+
173
+ def self.process_colors(string)
174
+ string
175
+ .gsub("%C%", "%C?")
176
+ .gsub(",%", ",?")
177
+ .gsub("%C", "\x03")
178
+ .gsub("%B", "\x02")
179
+ .gsub("%I", "\x10")
180
+ .gsub("%U", "\x1F")
181
+ .gsub("%N", "\x0F")
182
+ .gsub("?WHITE", "0")
183
+ .gsub("?BLACK", "1")
184
+ .gsub("?BLUE", "2")
185
+ .gsub("?GREEN", "3")
186
+ .gsub("?RED", "4")
187
+ .gsub("?BROWN", "5")
188
+ .gsub("?PURPLE", "6")
189
+ .gsub("?ORANGE", "7")
190
+ .gsub("?YELLOW", "8")
191
+ .gsub("?LGREEN", "9")
192
+ .gsub("?CYAN" , "10")
193
+ .gsub("?LCYAN", "11")
194
+ .gsub("?LBLUE", "12")
195
+ .gsub("?PINK", "13")
196
+ .gsub("?GREY", "14")
197
+ .gsub("?LGREY", "15")
198
+ end
199
+
200
+ def privmsg(target, message)
201
+ self.class.ssplit(self.class.process_colors(message)).each do |m|
202
+ write("PRIVMSG #{target} :\u200B#{m}")
203
+ end
204
+ end
205
+
206
+ def notice(target, message)
207
+ self.class.ssplit(self.class.process_colors(message)).each do |m|
208
+ write("NOTICE #{target} :\u200B#{m}")
209
+ end
210
+ end
211
+
212
+ def ctcp(target, message)
213
+ write("PRIVMSG #{target} :\x01#{self.class.process_colors(message)}\x01")
214
+ end
215
+
216
+ def nctcp(target, message)
217
+ write("NOTICE #{target} :\x01#{self.class.process_colors(message)}\x01")
218
+ end
219
+
220
+ def mode(channel, params)
221
+ write("MODE #{channel} #{params}")
222
+ end
223
+
224
+ def kick(channel, target, reason = 'Bye!')
225
+ write("KICK #{channel} #{target} :#{reason}")
226
+ end
227
+
228
+ def remove(channel, target, reason = 'Bye!')
229
+ write("REMOVE #{channel} #{target} :#{reason}")
230
+ end
231
+ end
@@ -0,0 +1,45 @@
1
+ class TinyIRC::Permission
2
+ attr_reader :plugin, :command, :branch
3
+
4
+ def self.parse(pstr)
5
+ a = pstr.split('/', 3)
6
+ raise RuntimeError, "Invalid permission: #{pstr}" unless a[0] || !a[1] && a[2]
7
+ [a[0], a[1] || :all, a[2] || :all]
8
+ end
9
+
10
+ def initialize(plugin, command, branch)
11
+ @plugin = plugin
12
+ @command = command
13
+ @branch = branch
14
+ end
15
+
16
+ def ==(other)
17
+ @plugin == other.plugin && @command == other.command && @branch == other.branch
18
+ end
19
+
20
+ def to_s
21
+ "#{@plugin}/#{@command}/#{@branch}"
22
+ end
23
+ end
24
+
25
+ class TinyIRC::Group
26
+ class << self
27
+ attr_reader :log
28
+ end
29
+
30
+ @log = ParticleLog.new('groups', ParticleLog::INFO)
31
+
32
+ attr_reader :name, :perms
33
+
34
+ def initialize(name)
35
+ @name = name
36
+ @perms = Set.new
37
+ TinyIRC::Group::log.write "groups += #{name}"
38
+ end
39
+
40
+ def perm(plugin, command, branch)
41
+ if @perms.add?(TinyIRC::Permission.new(plugin, command, branch))
42
+ TinyIRC::Group::log.write "#{@name} += #{plugin}/#{command}/#{branch}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ class TinyIRC::Plugin
2
+ attr_reader :commands, :event_handlers, :fullname, :groups, :loaded, :log, :name
3
+
4
+ def initialize(bot, name)
5
+ @loaded = false
6
+
7
+ @l_init = lambda {}
8
+ @l_postinit = lambda {}
9
+ @l_config = lambda {|cfg|}
10
+
11
+ @bot = bot
12
+ @fullname = name
13
+ @name = File.basename(name)
14
+
15
+ @commands = {}
16
+ @event_handlers = {}
17
+ @groups = {}
18
+
19
+ @log = ParticleLog.new('?' + @name, ParticleLog::INFO)
20
+ @log.info 'Loading...'
21
+
22
+ lp = "./#{name}.rb"
23
+ ok = false
24
+ if File.exists? lp
25
+ instance_eval(File.read(lp), lp)
26
+ ok = true
27
+ else
28
+ $LOAD_PATH.each do |f|
29
+ lp = File.join(f, name + '.rb')
30
+ if File.exists? lp
31
+ instance_eval(File.read(lp), lp)
32
+ ok = true
33
+ end
34
+ end
35
+ end
36
+
37
+ raise RuntimeError, "Cannot find the `#{name}` plugin" unless ok
38
+
39
+ _l_init
40
+ end
41
+
42
+ def init(&b) @l_init = b end
43
+ def _l_init()
44
+ @log.info 'Initializing...'
45
+ @l_init.()
46
+ end
47
+
48
+ def postinit(&b) @l_postinit = b end
49
+ def _l_postinit()
50
+ @log.info 'Post-Initializing...'
51
+ @l_postinit.()
52
+ @loaded = true
53
+ @log.important "Hello, bot!"
54
+ end
55
+
56
+ def configure(&b) @l_config = b end
57
+ def _l_config(cfg)
58
+ @log.info 'Configuring...'
59
+ @l_config.(cfg)
60
+ end
61
+
62
+ def on(type, pattern, &block)
63
+ pattern[:type] = type
64
+ pattern[:_block] = block
65
+ @event_handlers[type] = pattern
66
+ end
67
+
68
+ def on(type, **pattern, &handler)
69
+ @event_handlers[type] ||= []
70
+ @event_handlers[type] << {
71
+ pattern: pattern,
72
+ handler: handler
73
+ }
74
+ end
75
+
76
+ def cmd(name)
77
+ @commands[name] = TinyIRC::Command.new(self, name)
78
+ @commands[name]
79
+ end
80
+
81
+ def handle_command(h, cmd_info)
82
+ return false if h[:cmd_info][:plugin] != @name && h[:cmd_info][:plugin] != :any
83
+ x = if @commands.include? h[:cmd_info][:command]
84
+ @commands[h[:cmd_info][:command]].handle_command(h, cmd_info)
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def group(name)
91
+ n = "#{@name}/#{name}"
92
+ @groups[n] = TinyIRC::Group.new n
93
+ @groups[n]
94
+ end
95
+ end