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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +44 -0
- data/LICENSE.txt +21 -0
- data/README.md +90 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tinyirc +24 -0
- data/lib/tinyirc/app.rb +77 -0
- data/lib/tinyirc/bot.rb +363 -0
- data/lib/tinyirc/command.rb +109 -0
- data/lib/tinyirc/event.rb +12 -0
- data/lib/tinyirc/ircsocket.rb +231 -0
- data/lib/tinyirc/perms.rb +45 -0
- data/lib/tinyirc/plugin.rb +95 -0
- data/lib/tinyirc/plugins/admin-utils.rb +501 -0
- data/lib/tinyirc/plugins/cookies.rb +77 -0
- data/lib/tinyirc/plugins/core.rb +429 -0
- data/lib/tinyirc/usercache.rb +24 -0
- data/lib/tinyirc/version.rb +3 -0
- data/lib/tinyirc/views/404.erb +4 -0
- data/lib/tinyirc/views/500.erb +5 -0
- data/lib/tinyirc/views/index.erb +31 -0
- data/lib/tinyirc/views/layout.erb +62 -0
- data/lib/tinyirc/views/plugin.erb +42 -0
- data/lib/tinyirc.rb +26 -0
- data/tinyirc.gemspec +32 -0
- data/tinyirc.sample.yaml +40 -0
- metadata +172 -0
@@ -0,0 +1,429 @@
|
|
1
|
+
@webaddr = "http://#{ENV['PUBLIC_HOST'] || ENV['HOST'] || '0.0.0.0'}:#{ENV['PUBLIC_PORT'] || ENV['PORT'] || 8080}"
|
2
|
+
|
3
|
+
#
|
4
|
+
# Helper methods
|
5
|
+
#
|
6
|
+
|
7
|
+
self.define_singleton_method :handle_event do |e|
|
8
|
+
def notify_plugin(plugin, e)
|
9
|
+
(plugin.event_handlers[e[:type]] || []).each do |h|
|
10
|
+
begin
|
11
|
+
h[:handler].(e) if e >= h[:pattern]
|
12
|
+
rescue => e
|
13
|
+
e.backtrace.each do |l|
|
14
|
+
@log.error "- #{l}"
|
15
|
+
end
|
16
|
+
@log.error "#{e.class.name} - #{e.message}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
notify_plugin(self, e)
|
22
|
+
|
23
|
+
@bot.plugins.each do |pname, plugin|
|
24
|
+
next if plugin == self
|
25
|
+
Thread.new do
|
26
|
+
notify_plugin(plugin, e)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Event handlers
|
33
|
+
#
|
34
|
+
|
35
|
+
# Connect
|
36
|
+
on :connect do |e|
|
37
|
+
socket = e[:socket]
|
38
|
+
socket.log.important 'Connected'
|
39
|
+
socket.reconnects = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
# Disconnect
|
43
|
+
on :disconnect do |e|
|
44
|
+
socket = e[:socket]
|
45
|
+
socket.sock.close if socket.sock
|
46
|
+
socket.log.important 'Disconnected'
|
47
|
+
if (socket.reconnects < 5)
|
48
|
+
socket.log.important 'Reconnecting in 5 seconds'
|
49
|
+
sleep 5
|
50
|
+
socket.reconnects += 1
|
51
|
+
socket.connect
|
52
|
+
else
|
53
|
+
socket.log.important 'Reconnect limit reached'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
rgx_ping = /^PING :(.+)$/i
|
58
|
+
rgx_code = /^:.+? (\d\d\d) .+? (.+)$/i
|
59
|
+
rgx_join = /^:(.+?)!(.+?)@(.+?) JOIN (.+)$/i
|
60
|
+
rgx_part = /^:(.+?)!(.+?)@(.+?) PART (.+?) :(.+)$/i
|
61
|
+
rgx_nick = /^:(.+?)!(.+?)@(.+?) NICK :(.+)$/i
|
62
|
+
rgx_privmsg = /^:(.+?)!(.+?)@(.+?) PRIVMSG (.+?) :(.+)$/i
|
63
|
+
rgx_notice = /^:(.+?)!(.+?)@(.+?) NOTICE (.+?) :(.+)$/i
|
64
|
+
|
65
|
+
# Raw
|
66
|
+
on :raw do |e|
|
67
|
+
d = e[:raw_data]
|
68
|
+
if m = rgx_ping.match(d)
|
69
|
+
self.handle_event(e.merge!(type: :ping, target: m[1]))
|
70
|
+
elsif m = rgx_code.match(d)
|
71
|
+
self.handle_event(e.merge!(type: :code, code: m[1].to_i, extra: m[2]))
|
72
|
+
elsif m = rgx_join.match(d)
|
73
|
+
self.handle_event(e.merge!(
|
74
|
+
type: :join,
|
75
|
+
nick: m[1],
|
76
|
+
user: m[2],
|
77
|
+
host: m[3],
|
78
|
+
channel: m[4]
|
79
|
+
))
|
80
|
+
elsif m = rgx_part.match(d)
|
81
|
+
self.handle_event(e.merge!(
|
82
|
+
type: :part,
|
83
|
+
nick: m[1],
|
84
|
+
user: m[2],
|
85
|
+
host: m[3],
|
86
|
+
channel: m[4],
|
87
|
+
reason: m[5]
|
88
|
+
))
|
89
|
+
elsif m = rgx_nick.match(d)
|
90
|
+
self.handle_event(e.merge!(
|
91
|
+
type: :nick,
|
92
|
+
nick: m[1],
|
93
|
+
user: m[2],
|
94
|
+
host: m[3],
|
95
|
+
new_nick: m[4]
|
96
|
+
))
|
97
|
+
elsif m = rgx_privmsg.match(d)
|
98
|
+
self.handle_event(e.merge!(
|
99
|
+
type: :privmsg,
|
100
|
+
nick: m[1],
|
101
|
+
user: m[2],
|
102
|
+
host: m[3],
|
103
|
+
target: m[4],
|
104
|
+
message: m[5],
|
105
|
+
reply_to: if m[4] == e[:socket].nick then m[1] else m[4] end
|
106
|
+
))
|
107
|
+
elsif m = rgx_notice.match(d)
|
108
|
+
self.handle_event(e.merge!(
|
109
|
+
type: :privmsg,
|
110
|
+
nick: m[1],
|
111
|
+
user: m[2],
|
112
|
+
host: m[3],
|
113
|
+
target: m[4],
|
114
|
+
message: m[5],
|
115
|
+
reply_to: if m[4] == e[:socket].nick then m[1] else m[4] end
|
116
|
+
))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Ping
|
121
|
+
on :ping do |e|
|
122
|
+
e[:socket].write "PONG :#{e[:target]}"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Welcome Code
|
126
|
+
on :code, code: 001 do |e|
|
127
|
+
s = e[:socket]
|
128
|
+
s.autojoin.each do |chan|
|
129
|
+
s.join chan
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# WHOREPLY code
|
134
|
+
rgx_whoreply = /^.+? (.+?) (.+?) .+? (.+?) .*$/
|
135
|
+
on :code, code: 352 do |e|
|
136
|
+
m = rgx_whoreply.match(e[:extra])
|
137
|
+
if m
|
138
|
+
u = e[:socket].usercache.get(m[3])
|
139
|
+
u[:user] = m[1]
|
140
|
+
u[:host] = m[2]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Join
|
145
|
+
on :join do |e|
|
146
|
+
s = e[:socket]
|
147
|
+
if e[:nick] == s.nick
|
148
|
+
s.write "WHO #{e[:channel]}"
|
149
|
+
end
|
150
|
+
|
151
|
+
u = s.usercache.get(e[:nick])
|
152
|
+
u[:user] = e[:user]
|
153
|
+
u[:host] = e[:host]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Nick
|
157
|
+
on :nick do |e|
|
158
|
+
s = e[:socket]
|
159
|
+
if e[:nick] == s.nick
|
160
|
+
s.nick = e[:new_nick]
|
161
|
+
end
|
162
|
+
|
163
|
+
u = s.usercache.rename(e[:nick])
|
164
|
+
u[:user] = e[:user]
|
165
|
+
u[:host] = u[:host]
|
166
|
+
end
|
167
|
+
|
168
|
+
# PRIVMSG
|
169
|
+
on :privmsg do |e|
|
170
|
+
s = e[:socket]
|
171
|
+
|
172
|
+
u = s.usercache.get(e[:nick])
|
173
|
+
u[:user] = e[:user]
|
174
|
+
u[:host] = e[:host]
|
175
|
+
|
176
|
+
if e[:message][0, s.prefix.length] == s.prefix
|
177
|
+
s = Shellwords.split(e[:message][s.prefix.length, e[:message].length-1])
|
178
|
+
e[:type] = :cmd
|
179
|
+
e[:cmd] = s[0]
|
180
|
+
|
181
|
+
c = s[0].split('/', 3)
|
182
|
+
if c.length == 1
|
183
|
+
e[:cmd_info] = { plugin: :any, command: c[0], branch: :any }
|
184
|
+
elsif c.length == 2
|
185
|
+
e[:cmd_info] = { plugin: c[0], command: c[1], branch: :any }
|
186
|
+
elsif c.length == 3
|
187
|
+
e[:cmd_info] = { plugin: c[0], command: c[1], branch: c[2] }
|
188
|
+
else
|
189
|
+
raise RuntimeError, "Invalid command: #{s[0]}"
|
190
|
+
end
|
191
|
+
s.shift
|
192
|
+
|
193
|
+
info = ParticleCMD::Info.new(s)
|
194
|
+
e[:bot].handle_command(e, info || '')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
#
|
199
|
+
# Groups
|
200
|
+
#
|
201
|
+
|
202
|
+
group('world').tap do |g|
|
203
|
+
g.perm @name, 'help', 'root'
|
204
|
+
g.perm @name, 'help', 'what'
|
205
|
+
|
206
|
+
g.perm @name, 'key', 'generate'
|
207
|
+
g.perm @name, 'key', 'use'
|
208
|
+
end
|
209
|
+
|
210
|
+
group('admin').tap do |g|
|
211
|
+
g.perm @name, 'reload', 'root'
|
212
|
+
|
213
|
+
g.perm @name, 'groups', 'root'
|
214
|
+
g.perm @name, 'groups-add', 'root'
|
215
|
+
g.perm @name, 'groups-del', 'root'
|
216
|
+
|
217
|
+
g.perm @name, 'flushq', 'root'
|
218
|
+
g.perm @name, 'flushq', 'targeted'
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# help command
|
223
|
+
#
|
224
|
+
|
225
|
+
help_cmd = cmd 'help'
|
226
|
+
|
227
|
+
help_cmd.branch('root', '') do |e, c|
|
228
|
+
e.nreply "Open #{@webaddr}/ to get info about all available commands and groups"
|
229
|
+
end.description = 'Sends a link to the index help page'
|
230
|
+
|
231
|
+
help_cmd.branch('what', 'what') do |e, c|
|
232
|
+
arr = c.positionals['what'].split('/', 3)
|
233
|
+
if arr.length == 1
|
234
|
+
ok = false
|
235
|
+
e[:bot].plugins.each_pair do |pname, plugin|
|
236
|
+
plugin.commands.each do |cname, command|
|
237
|
+
if cname == arr[0]
|
238
|
+
e.nreply "Help for #{pname}/#{cname}: #{@webaddr}/#{pname}##{cname}"
|
239
|
+
ok = true
|
240
|
+
break
|
241
|
+
end
|
242
|
+
end
|
243
|
+
break if ok
|
244
|
+
end
|
245
|
+
e.nreply "Cannot find such command" unless ok
|
246
|
+
elsif arr.length == 2
|
247
|
+
unless e[:bot].plugins.include? arr[0]
|
248
|
+
e.nreply "Cannot find such plugin"
|
249
|
+
next
|
250
|
+
end
|
251
|
+
pname = arr[0]
|
252
|
+
plugin = e[:bot].plugins[pname]
|
253
|
+
cname = arr[1]
|
254
|
+
if plugin.commands.include? cname
|
255
|
+
e.nreply "Help for #{pname}/#{cname}: #{@webaddr}/#{pname}##{cname}"
|
256
|
+
else
|
257
|
+
e.nreply "Cannot find such command"
|
258
|
+
end
|
259
|
+
elsif arr.length == 3
|
260
|
+
pname = arr[0]
|
261
|
+
plugin = e[:bot].plugins[pname]
|
262
|
+
cname = arr[1]
|
263
|
+
unless plugin.commands.include? cname
|
264
|
+
e.nreply "Cannot find such command"
|
265
|
+
next
|
266
|
+
end
|
267
|
+
command = plugin.commands[cname]
|
268
|
+
bname = arr[2]
|
269
|
+
unless command.branches.include? bname
|
270
|
+
e.nreply "Cannot find such command branch"
|
271
|
+
next
|
272
|
+
end
|
273
|
+
e.nreply "Help for #{pname}/#{cname}/#{bname}: #{@webaddr}/#{pname}##{cname}/#{bname}"
|
274
|
+
end
|
275
|
+
end.tap do |b|
|
276
|
+
b.description = 'Sends a link to the help page for the given command'
|
277
|
+
b.definition.tap do |d|
|
278
|
+
d.description :positional, 'what', <<~HELP
|
279
|
+
Command name. Can be in these forms:
|
280
|
+
- command
|
281
|
+
- plugin/command
|
282
|
+
- plugin/command/branch
|
283
|
+
HELP
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
#
|
288
|
+
# key command
|
289
|
+
#
|
290
|
+
|
291
|
+
key_cmd = cmd 'key'
|
292
|
+
|
293
|
+
key_cmd.branch('generate', '') do |e, c|
|
294
|
+
@key = Random.rand(44**44..55**55).to_s(36)
|
295
|
+
@log.important @key
|
296
|
+
e.nreply 'Done!'
|
297
|
+
end.tap do |b|
|
298
|
+
b.description = 'Generates an unique key and prints it to the console'
|
299
|
+
end
|
300
|
+
|
301
|
+
key_cmd.branch('use', 'key') do |e, c|
|
302
|
+
if c.positionals['key'] == @key
|
303
|
+
e[:socket].set_group e[:host], 'admin'
|
304
|
+
e.nreply 'Done!'
|
305
|
+
else
|
306
|
+
e.nreply 'Invalid!'
|
307
|
+
end
|
308
|
+
@key = nil
|
309
|
+
end.tap do |b|
|
310
|
+
b.description = 'Gives you admin status if the given key is correct'
|
311
|
+
b.definition.tap do |d|
|
312
|
+
d.description :positional, 'key', 'The generated key'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# reload command
|
318
|
+
#
|
319
|
+
|
320
|
+
reload_cmd = cmd 'reload'
|
321
|
+
|
322
|
+
reload_cmd.branch('root', '-all -plugins! -plugins -groups -cooldowns') do |e, c|
|
323
|
+
all = c.flags['all']
|
324
|
+
plugins = c.flags['plugins!'] || all
|
325
|
+
plugin_config = c.flags['plugins'] || plugins || all
|
326
|
+
groups = c.flags['groups'] || plugins || all
|
327
|
+
cooldowns = c.flags['cooldowns'] || plugins || all
|
328
|
+
|
329
|
+
bot = e[:bot]
|
330
|
+
bot.config_mtx.synchronize do
|
331
|
+
bot.config = YAML.parse_file(bot.config_file).to_ruby
|
332
|
+
|
333
|
+
bot.prefix = bot.config['prefix'] || '!'
|
334
|
+
bot.log.info "prefix = #{bot.prefix}"
|
335
|
+
|
336
|
+
bot.plugins = {} if plugins
|
337
|
+
|
338
|
+
bot.load_plugin_config if plugin_config
|
339
|
+
bot.load_group_config if groups
|
340
|
+
bot.load_cooldown_config if cooldowns
|
341
|
+
end
|
342
|
+
|
343
|
+
e.nreply 'Done!'
|
344
|
+
end.tap do |b|
|
345
|
+
b.description = 'Reloads given parts of the bot'
|
346
|
+
b.definition.tap do |d|
|
347
|
+
d.description :flag, 'all', 'Relaod everything'
|
348
|
+
d.description :flag, 'plugins!', 'Reload plugins (also reloads plugin configs, groups and cooldowns)'
|
349
|
+
d.description :flag, 'plugins', 'Reload plugin configs'
|
350
|
+
d.description :flag, 'groups', 'Reload groups'
|
351
|
+
d.description :flag, 'cooldowns', 'Reload cooldowns'
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
#
|
356
|
+
# groups command
|
357
|
+
#
|
358
|
+
|
359
|
+
groups_cmd = cmd 'groups'
|
360
|
+
|
361
|
+
groups_cmd.branch('root', 'who') do |e, c|
|
362
|
+
s = e[:socket]
|
363
|
+
who = e[:socket].usercache.get(c.positionals['who'], false)[:host] || c.positionals['who']
|
364
|
+
e.nreply "#{who}'s groups: #{s.list_groups(who).join(', ')}"
|
365
|
+
end.tap do |b|
|
366
|
+
b.description = 'Lists groups of the given user'
|
367
|
+
b.definition.description :positional, 'who', 'Command target'
|
368
|
+
end
|
369
|
+
|
370
|
+
#
|
371
|
+
# groups-add command
|
372
|
+
#
|
373
|
+
|
374
|
+
groups_add_cmd = cmd 'groups-add'
|
375
|
+
|
376
|
+
groups_add_cmd.branch('root', 'who ...') do |e, c|
|
377
|
+
s = e[:socket]
|
378
|
+
who = s.usercache.get(c.positionals['who'], false)[:host] || c.positionals['who']
|
379
|
+
c.extra.each do |gname|
|
380
|
+
s.set_group(who, gname)
|
381
|
+
end
|
382
|
+
e.nreply 'Done!'
|
383
|
+
end.tap do |b|
|
384
|
+
b.description = 'Gives user given groups'
|
385
|
+
b.definition.description :positional, 'who', 'Command target'
|
386
|
+
end
|
387
|
+
|
388
|
+
#
|
389
|
+
# groups-del command
|
390
|
+
#
|
391
|
+
|
392
|
+
groups_del_cmd = cmd 'groups-del'
|
393
|
+
|
394
|
+
groups_del_cmd.branch('root', 'who ...') do |e, c|
|
395
|
+
s = e[:socket]
|
396
|
+
who = s.usercache.get(c.positionals['who'], false)[:host] || c.positionals['who']
|
397
|
+
c.extra.each do |gname|
|
398
|
+
s.del_group(who, gname)
|
399
|
+
end
|
400
|
+
e.nreply 'Done!'
|
401
|
+
end.tap do |b|
|
402
|
+
b.description = 'Removes given groups from the user'
|
403
|
+
b.definition.description :positional, 'who', 'Command target'
|
404
|
+
end
|
405
|
+
|
406
|
+
#
|
407
|
+
# flushq command
|
408
|
+
#
|
409
|
+
|
410
|
+
flushq_cmd = cmd 'flushq'
|
411
|
+
|
412
|
+
flushq_cmd.branch('root', '') do |e, c|
|
413
|
+
e[:socket].queue.clear
|
414
|
+
e.nreply 'Done!'
|
415
|
+
end.tap do |b|
|
416
|
+
b.description = 'Flushes the queue of the current server'
|
417
|
+
end
|
418
|
+
|
419
|
+
flushq_cmd.branch('targeted', 'server') do |e, c|
|
420
|
+
bot = e[:bot]
|
421
|
+
if bot.sockets.include? c.positionals['server']
|
422
|
+
bot.sockets[c.positionals['server']].queue.clear
|
423
|
+
e.nreply 'Done!'
|
424
|
+
else
|
425
|
+
e.nreply 'There\'s no such server!'
|
426
|
+
end
|
427
|
+
end.tap do |b|
|
428
|
+
b.description = 'Flushes the queue of the given server'
|
429
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class TinyIRC::UserCache
|
2
|
+
def initialize
|
3
|
+
@cache = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def get(nick, add = true)
|
7
|
+
if add
|
8
|
+
@cache[nick] ||= { nick: nick }
|
9
|
+
@cache[nick]
|
10
|
+
else
|
11
|
+
@cache[nick] || { nick: nick }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(entry)
|
16
|
+
@cache[entry[:nick]] = entry
|
17
|
+
end
|
18
|
+
|
19
|
+
def rename(o, n)
|
20
|
+
@cache[n] = @cache.delete(o) if @cache[o]
|
21
|
+
@cache[n][:nick] = n
|
22
|
+
@cache[n]
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="app-text">
|
2
|
+
<h4 class="app-title">Info</h4>
|
3
|
+
Use sidebar to get help for plugin commands
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div class="app-text">
|
7
|
+
<h4 class="app-title">Groups</h4>
|
8
|
+
<!--<%= ERB::Util::html_escape TinyIRC::App.bot.inspect %>-->
|
9
|
+
<% TinyIRC::App.bot.groups.each_pair do |gname, group| %>
|
10
|
+
<h6 class="group-name"><%= h gname %></h6>
|
11
|
+
<ul class="group-list">
|
12
|
+
<% group.perms.each do |perm| %>
|
13
|
+
<li class="group-list-item">
|
14
|
+
<span class=""><%= h perm.to_s %></span>
|
15
|
+
</li>
|
16
|
+
<% end %>
|
17
|
+
</ul>
|
18
|
+
<% end %>
|
19
|
+
<% TinyIRC::App.bot.plugins.each_pair do |pname, plugin| %>
|
20
|
+
<% plugin.groups.each_pair do |gname, group| %>
|
21
|
+
<h6 class="group-name"><%= h gname %></h6>
|
22
|
+
<ul class="group-list">
|
23
|
+
<% group.perms.each do |perm| %>
|
24
|
+
<li class="group-list-item">
|
25
|
+
<span class=""><%= h perm.to_s %></span>
|
26
|
+
</li>
|
27
|
+
<% end %>
|
28
|
+
</ul>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
</div>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>TinyIRC Help</title>
|
5
|
+
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
+
|
8
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
9
|
+
<link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.blue_grey-blue.min.css">
|
10
|
+
<script defer src="https://code.getmdl.io/1.3.0/material.min.js"></script>
|
11
|
+
|
12
|
+
<style>
|
13
|
+
.app-text {
|
14
|
+
max-width: 50%;
|
15
|
+
margin: 8px;
|
16
|
+
margin-left: auto;
|
17
|
+
margin-right: auto;
|
18
|
+
}
|
19
|
+
|
20
|
+
.app-text .app-title {
|
21
|
+
text-align: center;
|
22
|
+
}
|
23
|
+
|
24
|
+
.group-name {
|
25
|
+
margin-bottom: 0;
|
26
|
+
}
|
27
|
+
|
28
|
+
.group-list {
|
29
|
+
margin-top: 0;
|
30
|
+
}
|
31
|
+
</style>
|
32
|
+
</head>
|
33
|
+
<body>
|
34
|
+
<div class="mdl-layout mdl-js-layout">
|
35
|
+
<header class="mdl-layout__header mdl-layout__header--scroll">
|
36
|
+
<div class="mdl-layout__header-row">
|
37
|
+
<span class="mdl-layout-title">TinyIRC Help</span>
|
38
|
+
<nav class="mdl-navigation">
|
39
|
+
<a class="mdl-navigation__link" href="/">Home</a>
|
40
|
+
</nav>
|
41
|
+
<div class="mdl-layout-spacer"></div>
|
42
|
+
<span>Page: <%= h @pagename %></span>
|
43
|
+
</div>
|
44
|
+
</header>
|
45
|
+
<div class="mdl-layout__drawer">
|
46
|
+
<span class="mdl-layout-title">TinyIRC Help</span>
|
47
|
+
<span style="margin-left: 40px">Page: <%= h @pagename %></span>
|
48
|
+
<nav class="mdl-navigation">
|
49
|
+
<a class="mdl-navigation__link" href="/">Home</a>
|
50
|
+
<% TinyIRC::App.bot.plugins.each_pair do |pname, plugin| %>
|
51
|
+
<a class="mdl-navigation__link" href="/<%= pname %>"><%= pname %></a>
|
52
|
+
<% end %>
|
53
|
+
</nav>
|
54
|
+
</div>
|
55
|
+
<main class="mdl-layout__content">
|
56
|
+
<div class="page-content">
|
57
|
+
<%= yield %>
|
58
|
+
</div>
|
59
|
+
</main>
|
60
|
+
</div>
|
61
|
+
</body>
|
62
|
+
</html>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<div class="app-text">
|
2
|
+
<h3 class="app-title"><%= @pagename %></h3>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<div class="app-text">
|
6
|
+
<h4 class="app-title">Groups</h4>
|
7
|
+
<% @plugin.groups.each_pair do |gname, group| %>
|
8
|
+
<h6 class="group-name"><%= h gname %></h6>
|
9
|
+
<ul class="group-list">
|
10
|
+
<% group.perms.each do |perm| %>
|
11
|
+
<li class="group-list-item">
|
12
|
+
<span class=""><%= h perm.to_s %></span>
|
13
|
+
</li>
|
14
|
+
<% end %>
|
15
|
+
</ul>
|
16
|
+
<% end %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="app-text">
|
20
|
+
<h4 class="app-title">Commands</h4>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<% @plugin.commands.each_pair do |cname, command| %>
|
24
|
+
<div class="app-text">
|
25
|
+
<h5 class="app-title" id="<%= h(cname) %>"><%= h(cname) %></h5>
|
26
|
+
</div>
|
27
|
+
<% command.branches.each do |bname, branch| %>
|
28
|
+
<div class="app-text">
|
29
|
+
<% @sig = h branch.definition.command_signature %>
|
30
|
+
<h6 id="<%= h(cname) + '/' + h(bname) %>"><%= @sig %></h6>
|
31
|
+
<div class="mdl-tooltip mdl-tooltip--left" for="<%= h(cname) + '/' + h(bname) %>">
|
32
|
+
<%= @plugin.name + '/' + cname + '/' + bname %>
|
33
|
+
</div>
|
34
|
+
<% if branch.cooldown != 0 %>
|
35
|
+
<pre>Cooldown: <%= h branch.cooldown %></pre>
|
36
|
+
<% end %>
|
37
|
+
<pre><%= h branch.description %></pre>
|
38
|
+
<pre><%= h branch.definition.command_description %></pre>
|
39
|
+
<hr/>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
data/lib/tinyirc.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'socket'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'particlecmd'
|
7
|
+
require 'particlelog'
|
8
|
+
|
9
|
+
require 'thin'
|
10
|
+
require 'sinatra/base'
|
11
|
+
|
12
|
+
require 'sqlite3'
|
13
|
+
|
14
|
+
require 'tinyirc/version'
|
15
|
+
|
16
|
+
module TinyIRC
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'tinyirc/app'
|
20
|
+
require 'tinyirc/bot'
|
21
|
+
require 'tinyirc/command'
|
22
|
+
require 'tinyirc/event'
|
23
|
+
require 'tinyirc/ircsocket'
|
24
|
+
require 'tinyirc/perms'
|
25
|
+
require 'tinyirc/plugin'
|
26
|
+
require 'tinyirc/usercache'
|
data/tinyirc.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tinyirc/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tinyirc'
|
8
|
+
spec.version = TinyIRC::VERSION
|
9
|
+
spec.authors = ['Nickolay Ilyushin']
|
10
|
+
spec.email = ['nickolay02@inbox.ru']
|
11
|
+
|
12
|
+
spec.summary = 'A modular IRC bot framework'
|
13
|
+
spec.description = 'A modular IRC bot framework'
|
14
|
+
spec.homepage = 'https://github.com/handicraftsman/tinyirc'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
|
27
|
+
spec.add_dependency 'particlecmd', '~> 0.1'
|
28
|
+
spec.add_dependency 'particlelog', '~> 0.1'
|
29
|
+
spec.add_dependency 'sinatra', '~> 2.0'
|
30
|
+
spec.add_dependency 'thin', '~> 1.2'
|
31
|
+
spec.add_dependency 'sqlite3', '~> 1.3'
|
32
|
+
end
|