forked from seveas/chanserv.py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chanserv.py
633 lines (550 loc) · 24.6 KB
/
chanserv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# Simple chanserv helper script for Xchat
# (c) 2006-2013 Dennis Kaarsemaker
#
# Latest version can be found on http://github.com/seveas/chanserv.py
#
# This script is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 3, as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Usage instructions:
# - Place in ~/.xchat2 for it to be autoloaded
# - Use /py load chanserv.py if you already started X-chat
# - Connect to freenode, if not connected (other networks will not work)
#
# It adds one command to xchat: /cs
# /cs understands the following arguments
#
# To give/take ops/voice:
#
# o or op - Let chanserv op you/others (/cs op, /cs op nick)
# v or voice - Let chanserv give you/others voice
# d or deop - Let chanserv deop you/others (/cs deop, /cs deop nick)
# dv or devoice - Let chanserv decoice you/others (/cs devoice, /cs devoice nick)
#
# To op yourself, perform an action, and deop:
#
# k or kick - Kick a user, possibly with comment (/cs kick nick [comment])
# b or ban - Ban a user (/cs ban [-nihar] nick)
# kb or kickban - Kick and ban a user (/cs ban [-nihar] nick)
# f or forward - Ban a user with a forward (/cs forward [-nihar] nick chan)
# kf or kickforward - Kickban a user with a forward (/cs forward [-nihar] nick chan)
# m or mute - Mute a user (/cs mute [-nihar] nick)
# l or lart - A combination of kick and ban on all fields
# u or unban - Remove all bans for a user (/cs u nick)
# t or topic - Set channel topic (/cs t New topic here)
# m or mode - Change channel mode (/cs mode modes here)
# i or invite - Invite yourself or someone else (/cs invite [nick])
# bans - Show bans that apply to someone without removing them (/cs bans nick)
#
# * Bans, forwards and mute take an extra optional argument that specifies
# what should be banned: nickname, ident, host, account and/or realname.
# /cs ban -nah nick -- Ban nick, account and host
# /cs forward -nihra nick #somewhere -- Forward all
#
# * These commands also take an extra argument to specify when bans/mutes
# should be lifted automatically.
# /cs ban -t600 nick -- Ban nick for 10 minutes
# /cs ban -nah -t3600 -- Ban nick, account and hostname for an hour
#
# * Unban will remove all bans matching the nick or mask you give as argument
# (* and ? wildcards work)
# * It won't actually kick, but use the /remove command
#
# The following additional features are implemented
# - Autorejoin for /remove
# - Auto-unmute when muted
# - Auto-unban via chanserv
# - Auto-invite via chanserv
# - Auto-getkey via chanserv
__module_name__ = "chanserv"
__module_version__ = "2.3.3"
__module_description__ = "Chanserv helper"
import collections
import xchat
import time
import re
import os
import math
# Event queue
pending = []
# /whois cache
users = {}
# /mode bq 'cache'
bans = collections.defaultdict(list)
quiets = collections.defaultdict(list)
akicks = collections.defaultdict(list)
collecting_bans = []
current_akick = None
can_do_akick = []
abbreviations = {'kick': 'k', 'ban': 'b', 'kickban': 'kb', 'forward': 'f',
'kickforward': 'kf', 'mute': 'm', 'topic': 't', 'unban': 'u',
'mode': 'm', 'invite': 'i', 'op': 'o', 'deop': 'd', 'lart': 'l',
'voice': 'v', 'devoice': 'dv', 'bans': 'bans'}
expansions = dict([x[::-1] for x in abbreviations.items()])
simple_commands = ['op', 'deop', 'voice', 'devoice']
kick_commands = ['kick', 'kickforward', 'kickban', 'lart']
forward_commands = ['kickforward', 'forward']
ban_commands = ['ban', 'forward', 'mute', 'lart', 'kickban', 'kickforward']
simple_commands += [abbreviations[x] for x in simple_commands]
kick_commands += [abbreviations[x] for x in kick_commands]
ban_commands += [abbreviations[x] for x in ban_commands]
forward_commands += [abbreviations[x] for x in forward_commands]
all_commands = list(abbreviations.keys()) + list(abbreviations.values())
ban_sentinel = '!'
debug = os.path.exists(os.path.join(xchat.get_info('xchatdir'), 'chanserv.py-debug'))
def cs(word, word_eol, userdata):
"""Main command dispatcher"""
if len(word) == 1:
return xchat.EAT_ALL
command = word[1].lower()
if command not in all_commands:
return xchat.EAT_NONE
args = dict(enumerate(word_eol[2:]))
me = xchat.get_info('nick')
action = Action(channel = xchat.get_info('channel'),
me = me,
context = xchat.get_context())
# The simple ones: op/voice
if command in simple_commands:
action.target = args.get(0, me)
action.deop = (action.target != me)
action.needs_op = False
command = expansions.get(command,command)
action.actions.append('chanserv %s %%(channel)s %%(target_nick)s' % command)
return action.schedule()
# Usage check
if len(word) < 3:
if command in all_commands:
xchat.emit_print("Server Error", "Not enough arguments for %s" % command)
return xchat.EAT_ALL
return xchat.EAT_NONE
if command in ('t','topic'):
action.actions.append('chanserv TOPIC %%(channel)s %s' % args[0])
action.needs_op = False
return action.schedule()
if command in ('m','mode') and args[0][0] in '+=-':
action.actions.append('MODE %%(channel)s %s' % args[0])
return action.schedule()
if command in ('i','invite'):
target = args[0]
if target.startswith('#'):
action.needs_op = False
action.actions.append('chanserv INVITE %s' % target)
else:
if target.lower() in [x.nick.lower() for x in action.context.get_list('users')]:
xchat.emit_print("Server Error", "%s is already in %s" % (target, action.channel))
return xchat.EAT_ALL
action.actions.append('INVITE %s %%(channel)s' % target)
return action.schedule()
# Kick/ban/forward/mute handling
if len(word) < 4 and command in forward_commands:
xchat.emit_print("Server Error", "Not enough arguments for %s" % command)
return xchat.EAT_ALL
# Command dispatch
# Check for -nihra argument
if command in ban_commands:
args_start = 3
while args[0].startswith('-'):
if args[0].startswith('-t'):
try:
action.timer = int(args[0][2:].split(None, 1)[0])
except ValueError:
pass
else:
action.bans = args[0][1:].split(None, 1)[0]
args = dict(enumerate(word_eol[args_start:]))
args_start += 1
if command in ('lart','l'):
action.bans = 'nihra'
# Set target
action.target = args[0].split(None,1)[0]
if not valid_nickname(action.target) and not valid_mask(action.target):
xchat.emit_print("Server Error", "Invalid target: %s" % action.target)
return xchat.EAT_ALL
if action.bans and not valid_nickname(action.target):
xchat.emit_print("Server Error", "Ban types and lart can only be used with nicks, not with complete masks")
return xchat.EAT_ALL
if valid_mask(action.target):
action.bans = 'f'
if not action.bans:
action.bans = 'h'
# Find forward channel
if command in forward_commands:
action.forward_to = '$' + args[1].split(None,1)[0] # Kludge
if not valid_channel(action.forward_to[1:]):
xchat.emit_print("Server Error", "Invalid channel: %s" % action.forward_to[1:])
return xchat.EAT_ALL
# Check if target is there and schedule kick
if command in kick_commands:
if action.target.lower() not in [x.nick.lower() for x in action.context.get_list('users')]:
xchat.emit_print("Server Error", "%s is not in %s" % (action.target, action.channel))
return xchat.EAT_ALL
action.reason = args.get(1, 'Goodbye')
action.actions.append('remove %(channel)s %(target_nick)s :%(reason)s')
if command in ('m','mute'):
action.banmode = 'q'
if command in ban_commands:
action.do_ban = True
if 'n' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s %(target_nick)s!*@*%(forward_to)s')
if 'i' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s *!%(target_ident)s@*%(forward_to)s')
if 'h' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s *!*@%(target_host)s%(forward_to)s')
if 'r' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s $r:%(target_name_bannable)s%(forward_to)s')
if 'a' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s $a:%(target_account)s%(forward_to)s')
if 'f' in action.bans: action.actions.append('mode %(channel)s +%(banmode)s %(target)s%(forward_to)s')
if command in ('u','unban'):
action.do_unban = True
if command == 'bans':
action.do_bans = True
action.needs_op = False
return action.schedule()
xchat.hook_command('cs',cs,"For help with /cs, please read the comments in the script")
class Action(object):
"""A list of actions to do, and information needed for them"""
def __init__(self, channel, me, context):
self.channel = channel
self.me = me
self.context = context
self.stamp = time.time()
# Defaults
self.deop = True
self.needs_op = True
self.do_ban = self.do_unban = self.do_bans = False
self.banmode = 'b'
self.reason = ''
self.bans = ''
self.actions = []
self.resolved = True
self.target = ''
self.forward_to = ''
self.timer = 0
def __str__(self):
ctx = {'channel': self.channel, 'target': self.target}
if hasattr(self, 'target_ident'):
ctx['target'] = '%s!%s@%s (r: %s a: %s)' % (self.target_nick, self.target_ident, self.target_host, self.target_name, self.target_account)
ctx['actions'] = ' | '.join(self.actions)
return "C: %(channel)s T: %(target)s A: %(actions)s" % ctx
def schedule(self, update_stamp=False):
"""Request information and add ourselves to the queue"""
if debug:
xchat.emit_print('Server Text', "Scheduling " + str(self))
if update_stamp:
self.stamp = time.time()
pending.append(self)
# Am I opped?
self.am_op = False
for user in self.context.get_list('users'):
if user.nick == self.me and user.prefix == '@':
self.am_op = True
self.deop = False
if self.needs_op and not self.am_op:
self.context.command("chanserv op %s" % self.channel)
# Find needed information
if ('a' in self.bans or 'r' in self.bans) and valid_mask(self.target) and not self.target.startswith('$'):
xchat.emit_print('Server Error', "Invalid argument %s for account/realname ban" % self.target)
return xchat.EAT_ALL
if self.do_ban or self.do_unban or self.do_bans:
self.resolve_nick()
else:
self.target_nick = self.target
if self.do_unban or self.do_bans:
self.fetch_bans()
run_pending()
return xchat.EAT_ALL
def resolve_nick(self, request=True):
"""Try to find nickname, ident and host"""
self.target_nick = None
self.target_ident = None
self.target_host = None
self.target_name = None
self.target_name_bannable = None
self.target_account = None
self.resolved = False
if valid_mask(self.target):
if self.target.startswith('$a:'):
self.target_account = self.target[3:]
elif self.target.startswith('$r:'):
self.target_name = self.target[3:]
self.target_name_bannable = re.sub('[^a-zA-Z0-9]', '?', self.target_name)
else:
self.target_nick, self.target_mask, self.target_host = re.split('[!@]', self.target)
self.resolved = True
return
self.target_nick = self.target.lower()
if self.target_nick in users:
if users[self.target_nick].time < time.time() - 10:
del users[self.target_nick]
if request:
self.context.command('whois %s' % self.target_nick)
else:
self.target_ident = users[self.target_nick].ident
self.target_host = users[self.target_nick].host
self.target_name = users[self.target_nick].name
self.target_name_bannable = re.sub('[^a-zA-Z0-9]', '?', self.target_name)
self.target_account = users[self.target_nick].account
self.resolved = True
if 'gateway/' in self.target_host and self.bans == 'h' and self.do_ban:
# For gateway/* users, default to ident ban
self.actions.append('mode %(channel)s +%(banmode)s *!%(target_ident)s@gateway/*%(forward_to)s')
self.actions.remove('mode %(channel)s +%(banmode)s *!*@%(target_host)s%(forward_to)s')
else:
if request:
self.context.command('whois %s' % self.target_nick)
def fetch_bans(self):
"""Read bans for a channel"""
bans[self.channel] = []
quiets[self.channel] = []
collecting_bans.append(self.channel)
self.context.command("mode %s +bq" % self.channel)
def run(self):
"""Perform our actions"""
if debug:
xchat.emit_print('Server Text', "Running " + str(self))
kwargs = dict(self.__dict__.items())
if self.do_bans:
xchat.emit_print('Server Text', "Bans matching %s!%s@%s (r:%s, a:%s)" %
(self.target_nick, self.target_ident, self.target_host, self.target_name, self.target_account))
if self.do_unban or self.do_bans:
for b in bans[self.channel]:
if self.match(b):
if self.do_bans:
xchat.emit_print('Server Text', b)
else:
if '$# akick' in b:
b = b[:b.find('$#')]
if b.endswith('!*@*'):
b = b[:-4]
self.actions.append('quote cs akick %s del %s' % (self.channel, b))
else:
self.actions.append('mode %s -b %s' % (self.channel, b))
for b in quiets[self.channel]:
if self.match(b):
if self.do_bans:
xchat.emit_print('Server Text', b + ' (quiet)')
else:
self.actions.append('mode %s -q %s' % (self.channel, b))
# Perform all registered actions
for action in self.actions:
if '%(target_account)s' in action and not self.target_account:
xchat.emit_print('Server Text', "Can't do an account ban for %s, not identified" % self.target_nick)
continue
action = action % kwargs
if self.channel in can_do_akick and self.timer and ' +b ' in action:
timer = math.ceil(self.timer/60.0)
ban = action.split()[-1]
self.context.command("chanserv akick %s ADD %s !T %d" % (self.channel, ban, timer))
else:
self.context.command(action)
self.done()
def done(self):
"""Finaliazation and cleanup"""
# Done!
if debug:
xchat.emit_print('Server Text', "Done " + str(self))
pending.remove(self)
# Deop?
if not self.am_op or not self.needs_op:
return
for p in pending:
if p.channel == self.channel and p.needs_op or not p.deop:
self.deop = False
break
if self.deop:
self.context.command("chanserv deop %s" % self.channel)
# Schedule removal?
if self.timer and (self.channel not in can_do_akick or self.banmode == 'q'):
action = Action(self.channel, self.me, self.context)
action.deop = self.deop
action.actions = [x.replace('+','-',1) for x in self.actions]
action.target = self.target
action.target_nick = self.target_nick
action.target_ident = self.target_ident
action.target_host = self.target_host
action.target_name = self.target_name
action.target_name_bannable = self.target_name_bannable
action.target_account = self.target_account
action.resolved = True
action.banmode = self.banmode
action.needs_op = True
xchat.hook_timer(self.timer * 1000, lambda act: act.schedule(update_stamp=True) and False, action)
def match(self, ban):
"""Does a ban match this action"""
if ban.startswith('$r:') and self.target_name:
return ban2re(ban[3:]).match(self.target_name)
elif ban.startswith('$a:') and self.target_account:
return ban2re(ban[3:]).match(self.target_account)
else:
if '#' in ban:
ban = ban[:ban.find('$#')]
return ban2re(ban).match('%s!%s@%s' % (self.target_nick, self.target_ident, self.target_host))
def run_pending(just_opped = None):
"""Check all actions and run them if all information is there"""
now = time.time()
for p in pending:
if p.channel == just_opped:
p.am_op = True
if p.target_nick in users and not p.resolved:
p.resolve_nick(request = False)
# Timeout?
if p.stamp < now - 10:
p.done()
continue
can_run = not (p.channel in collecting_bans and (p.do_unban or p.do_bans))
if can_run and p.resolved and (p.am_op or not p.needs_op):
p.run()
# Helper functions
def ban2re(data):
return re.compile('^' + re.escape(data).replace(r'\*','.*').replace(r'\?','.') + '$')
_valid_nickname = re.compile(r'^[-a-zA-Z0-9\[\]{}`|_^\\]{0,30}$')
valid_nickname = lambda data: _valid_nickname.match(data)
_valid_channel = re.compile(r'^[#~].*') # OK, this is cheating
valid_channel = lambda data: _valid_channel.match(data)
_valid_mask = re.compile(r'^([-a-zA-Z0-9\[\]{}`|_^\\*?]{0,30}!.*?@.*?|\$[ar]:.*)$')
valid_mask = lambda data: _valid_mask.match(data)
# Data processing
def do_mode(word, word_eol, userdata):
"""Run pending actions when chanserv opped us"""
ctx = xchat.get_context()
if 'chanserv!' in word[0].lower() and '+o' in word[3] and ctx.get_info('nick') in word:
run_pending(just_opped = ctx.get_info('channel'))
xchat.hook_server('MODE', do_mode)
class User(object):
def __init__(self, nick, ident, host, name):
self.nick = nick; self.ident = ident; self.host = host; self.name = name
self.account = None
self.time = time.time()
def do_whois(word, word_eol, userdata):
"""Store whois replies in global cache"""
nick = word[3].lower()
if word[1] == '330':
users[nick].account = word[4]
else:
users[nick] = User(nick, word[4], word[5], word_eol[7][1:])
xchat.hook_server('311', do_whois)
xchat.hook_server('330', do_whois)
xchat.hook_server('314', do_whois) # This actually is a /whowas reply
def do_missing(word, word_eol, userdata):
"""Fall back to whowas if whois fails"""
for p in pending:
if p.target == word[3]:
p.context.command('whowas %s' % word[3])
break
xchat.hook_server('401', do_missing)
def do_endwas(word, word_eol, userdata):
"""Display error if nickname cannot be resolved"""
for p in pending:
if p.target == word[3]:
xchat.emit_print("Server Error", "%s could not be found" % p.target)
pending.remove(p)
xchat.hook_server('406', do_endwas)
def endofwhois(word, word_eol, userdata):
"""Process the queue after nickname resolution"""
run_pending()
xchat.hook_server('318', endofwhois)
xchat.hook_server('369', endofwhois)
xchat.hook_server('482', lambda word, word_eol, userdata: xchat.emit_print('Server Error', '%s in %s' % (word_eol[4][1:], word[3])))
def do_ban(word, word_eol, userdata):
"""Process banlists"""
channel, ban = word[3:5]
if channel in collecting_bans:
bans[channel].append(ban)
return xchat.EAT_ALL
return xchat.EAT_NONE
xchat.hook_server('367', do_ban)
def do_quiet(word, word_eol, userdata):
"""Process banlists"""
channel, ban = word[3], word[5]
if channel in collecting_bans:
quiets[channel].append(ban)
return xchat.EAT_ALL
return xchat.EAT_NONE
xchat.hook_server('728', do_quiet)
def do_endban(word, word_eol, userdata):
"""Process end-of-ban markers"""
channel = word[3]
if channel in collecting_bans:
return xchat.EAT_ALL
return xchat.EAT_NONE
xchat.hook_server('368', do_endban)
def do_endquiet(word, word_eol, userdata):
"""Process end-of-quiet markers"""
channel = word[3]
if channel in collecting_bans:
xchat.command('quote cs akick %s list' % channel)
return xchat.EAT_ALL
return xchat.EAT_NONE
xchat.hook_server('729', do_endquiet)
# Turn on autorejoin
xchat.command('SET -quiet irc_auto_rejoin ON')
def rejoin(word, word_eol, userdata):
"""Rejoin when /remove'd"""
if word[0][1:word[0].find('!')] == xchat.get_info('nick') and len(word) > 3 and word[3][1:].lower() == 'requested':
xchat.command('join %s' % word[2])
xchat.hook_server('PART', rejoin)
# Unban when muted
xchat.hook_server('404', lambda word, word_eol, userdata: xchat.command('quote cs unban %s' % word[3]))
# Convince chanserv to let me in when key/unban/invite is needed
xchat.hook_server('471', lambda word, word_eol, userdata: xchat.command('quote cs invite %s' % word[3])) # 471 = limit reached
xchat.hook_server('473', lambda word, word_eol, userdata: xchat.command('quote cs invite %s' % word[3]))
xchat.hook_server('474', lambda word, word_eol, userdata: xchat.command('quote cs unban %s' % word[3]))
xchat.hook_server('475', lambda word, word_eol, userdata: xchat.command('quote cs getkey %s' % word[3]))
def on_invite(word, word_eol, userdata):
"""Autojoin when chanserv invites us"""
if word[0] == ':ChanServ!ChanServ@services.':
xchat.command('join %s' % word[-1][1:])
xchat.hook_server('INVITE', on_invite)
def on_notice(word, word_eol, userdata):
global current_akick
if word[0] == ':NickServ!NickServ@services.':
if word[3:5] == [':Access', 'flag(s)'] and 'f' in word[5]:
can_do_akick.append(word[-1])
return
if word[0] != ':ChanServ!ChanServ@services.':
return
if 'Unbanned' in word_eol[0]:
xchat.command('JOIN %s' % word[6].strip()[1:-1])
if 'key is' in word_eol[0]:
xchat.command('JOIN %s %s' % (word[4][1:-1], word[-1]))
# Yay heuristics. Chances are reasonable that only one channel is in
# collecting_bans at any time, so let's assume that. Worst that could
# happen is that non-existing bans are shown or removal of them is tried.
if word_eol[3] == ':You are not authorized to perform this operation.':
# Tried akick list and failed. Just run all bans
for channel in collecting_bans[:]:
collecting_bans.remove(channel)
run_pending()
current_akick = None
return xchat.EAT_ALL
if word_eol[3].startswith(':AKICK list'):
current_akick = word[-1][1:-2]
if current_akick in collecting_bans:
return xchat.EAT_ALL
else:
current_akick = None
if current_akick and '[setter:' in word_eol[0] and 'modified:' in word_eol[0]:
# This looks like a ban to me. So everybody, just follow me.
ban = word[4][1:-1]
if '!' not in ban:
ban = ban + '!*@*'
ban += '$# akick ' + word_eol[5]
bans[current_akick].append(ban)
return xchat.EAT_ALL
if current_akick and word_eol[0].endswith('AKICK list.'):
current_akick = None
channel = word[-3][1:-3]
collecting_bans.remove(channel)
run_pending()
return xchat.EAT_ALL
xchat.hook_server('NOTICE', on_notice)
# Fetch channel access
xchat.command('quote ns listchans')
xchat.hook_server('376', lambda w, we, u: xchat.command('quote ns listchans'))
xchat.emit_print('Server Text',"Loaded %s %s by Seveas <dennis@kaarsemaker.net>" % (__module_description__, __module_version__))