0
# IRC Protocol
1
2
Sopel provides comprehensive IRC protocol handling including connection management, message sending, channel operations, user tracking, capability negotiation, and mode parsing. The IRC layer handles both low-level protocol details and high-level bot operations.
3
4
## Capabilities
5
6
### Bot Communication Methods
7
8
Core methods for sending messages and interacting with IRC channels and users.
9
10
```python { .api }
11
class Sopel:
12
"""Main bot class with IRC communication methods."""
13
14
def say(self, text: str, recipient: str, max_messages: int = 1, truncation: str = '', trailing: str = '') -> None:
15
"""
16
Send a message to a channel or user.
17
18
Args:
19
text (str): Message text to send
20
recipient (str): Channel or nick to send to
21
max_messages (int): Maximum number of messages if text is split
22
truncation (str): String to indicate message was truncated
23
trailing (str): String to append after text
24
25
Returns:
26
None
27
"""
28
29
def reply(self, text: str, destination: str = None, reply_to: str = None, max_messages: int = 1) -> int:
30
"""
31
Reply to a user, optionally mentioning their nick.
32
33
Args:
34
text (str): Reply text
35
destination (str): Channel or nick to reply to
36
reply_to (str): Nick to mention in reply
37
max_messages (int): Maximum number of messages if text is split
38
39
Returns:
40
Number of messages sent
41
"""
42
43
def action(self, text: str, destination: str = None) -> None:
44
"""
45
Send a CTCP ACTION (/me) message.
46
47
Args:
48
text (str): Action text
49
destination (str): Channel or nick to send action to
50
"""
51
52
def notice(self, text: str, destination: str = None) -> None:
53
"""
54
Send a NOTICE message.
55
56
Args:
57
text (str): Notice text
58
destination (str): Channel or nick to send notice to
59
"""
60
61
def msg(self, recipient: str, text: str, max_messages: int = 1) -> int:
62
"""
63
Send a private message to a specific recipient.
64
65
Args:
66
recipient (str): Nick or channel to message
67
text (str): Message text
68
max_messages (int): Maximum number of messages if text is split
69
70
Returns:
71
Number of messages sent
72
"""
73
74
class SopelWrapper:
75
"""Bot wrapper for use in plugin functions."""
76
77
def say(self, message: str, destination: str = None, max_messages: int = 1, truncation: str = '', trailing: str = '') -> None:
78
"""Send message (same as Sopel.say)."""
79
80
def reply(self, message: str, destination: str = None, reply_to: str = None, notice: bool = False) -> None:
81
"""Reply to user (same as Sopel.reply)."""
82
83
def action(self, text: str, destination: str = None) -> None:
84
"""Send action (same as Sopel.action)."""
85
86
def notice(self, text: str, destination: str = None) -> None:
87
"""Send notice (same as Sopel.notice)."""
88
```
89
90
### Channel Operations
91
92
Methods for joining, leaving, and managing IRC channels.
93
94
```python { .api }
95
class Sopel:
96
"""Channel management methods."""
97
98
def join(self, channel: str, password: str = None) -> None:
99
"""
100
Join an IRC channel.
101
102
Args:
103
channel (str): Channel name to join (with # prefix)
104
password (str): Channel password if required
105
"""
106
107
def part(self, channel: str, message: str = None) -> None:
108
"""
109
Leave an IRC channel.
110
111
Args:
112
channel (str): Channel name to leave
113
message (str): Part message
114
"""
115
116
def kick(self, nick: str, channel: str = None, message: str = None) -> None:
117
"""
118
Kick a user from a channel.
119
120
Args:
121
nick (str): Nickname to kick
122
channel (str): Channel to kick from (defaults to current context)
123
message (str): Kick message
124
"""
125
126
def invite(self, nick: str, channel: str) -> None:
127
"""
128
Invite a user to a channel.
129
130
Args:
131
nick (str): Nickname to invite
132
channel (str): Channel to invite to
133
"""
134
135
def mode(self, target: str, modes: str = None) -> None:
136
"""
137
Set channel or user modes.
138
139
Args:
140
target (str): Channel or nick to set modes on
141
modes (str): Mode string (e.g., "+o nick", "-v nick")
142
"""
143
```
144
145
### User and Channel Tracking
146
147
Access to bot's knowledge of users and channels.
148
149
```python { .api }
150
class Sopel:
151
"""User and channel tracking attributes."""
152
153
@property
154
def channels(self) -> 'IdentifierMemory':
155
"""
156
Dictionary of channels the bot is in.
157
158
Keys are Identifier objects for channel names.
159
Values are Channel objects containing user lists and permissions.
160
"""
161
162
@property
163
def users(self) -> 'IdentifierMemory':
164
"""
165
Dictionary of users the bot is aware of.
166
167
Keys are Identifier objects for nicknames.
168
Values are User objects with user information.
169
"""
170
171
@property
172
def nick(self) -> 'Identifier':
173
"""Bot's current nickname."""
174
175
def is_nick(self, nick: str) -> bool:
176
"""
177
Check if a nickname belongs to this bot.
178
179
Args:
180
nick (str): Nickname to check
181
182
Returns:
183
True if nick is bot's nickname
184
"""
185
```
186
187
### Connection Management
188
189
Methods for managing the IRC connection and bot lifecycle.
190
191
```python { .api }
192
class Sopel:
193
"""Connection and lifecycle management."""
194
195
def run(self, host: str = None, port: int = None) -> None:
196
"""
197
Start the bot and connect to IRC.
198
199
Args:
200
host (str): IRC server hostname (overrides config)
201
port (int): IRC server port (overrides config)
202
"""
203
204
def restart(self, message: str = None) -> None:
205
"""
206
Restart the bot.
207
208
Args:
209
message (str): Quit message before restart
210
"""
211
212
def quit(self, message: str = None) -> None:
213
"""
214
Disconnect from IRC and quit.
215
216
Args:
217
message (str): Quit message
218
"""
219
220
def write(self, args: list, text: str = None) -> None:
221
"""
222
Send raw IRC command.
223
224
Args:
225
args (list): IRC command and parameters
226
text (str): Optional message text
227
"""
228
229
def safe_text_length(self, recipient: str) -> int:
230
"""
231
Get maximum safe text length for messages to recipient.
232
233
Args:
234
recipient (str): Target channel or nick
235
236
Returns:
237
Maximum safe message length in bytes
238
"""
239
```
240
241
### Capability Negotiation
242
243
IRC capability negotiation for modern IRC features.
244
245
```python { .api }
246
class Sopel:
247
"""IRC capability handling."""
248
249
@property
250
def server_capabilities(self) -> dict:
251
"""Dictionary of server-supported capabilities."""
252
253
@property
254
def enabled_capabilities(self) -> set:
255
"""Set of currently enabled capabilities."""
256
257
def request_capability(self, capability: str) -> None:
258
"""
259
Request an IRC capability.
260
261
Args:
262
capability (str): Capability name to request
263
"""
264
```
265
266
### Mode Parsing
267
268
IRC mode parsing and interpretation.
269
270
```python { .api }
271
class ModeParser:
272
"""Parser for IRC mode strings."""
273
274
def parse(self, mode_string: str, params: list = None) -> dict:
275
"""
276
Parse IRC mode string.
277
278
Args:
279
mode_string (str): Mode string (e.g., "+ooo-v")
280
params (list): Mode parameters
281
282
Returns:
283
Dictionary of parsed mode changes
284
"""
285
286
class Sopel:
287
@property
288
def modeparser(self) -> ModeParser:
289
"""Mode parser instance for handling IRC modes."""
290
```
291
292
## Usage Examples
293
294
### Basic Message Sending
295
296
```python
297
from sopel import plugin
298
299
@plugin.command('say')
300
@plugin.example('.say #channel Hello everyone!')
301
def say_command(bot, trigger):
302
"""Make the bot say something in a channel."""
303
args = trigger.group(2)
304
if not args:
305
bot.reply("Usage: .say <channel> <message>")
306
return
307
308
parts = args.split(' ', 1)
309
if len(parts) < 2:
310
bot.reply("Usage: .say <channel> <message>")
311
return
312
313
channel, message = parts
314
bot.say(message, channel)
315
bot.reply(f"Message sent to {channel}")
316
317
@plugin.command('me')
318
@plugin.example('.me is excited about IRC bots!')
319
def action_command(bot, trigger):
320
"""Make the bot perform an action."""
321
if not trigger.group(2):
322
bot.reply("Usage: .me <action>")
323
return
324
325
action_text = trigger.group(2)
326
bot.action(action_text, trigger.sender)
327
```
328
329
### Channel Management
330
331
```python
332
@plugin.command('join')
333
@plugin.require_admin()
334
def join_command(bot, trigger):
335
"""Join a channel."""
336
if not trigger.group(2):
337
bot.reply("Usage: .join <channel> [password]")
338
return
339
340
args = trigger.group(2).split(' ', 1)
341
channel = args[0]
342
password = args[1] if len(args) > 1 else None
343
344
if not channel.startswith('#'):
345
channel = '#' + channel
346
347
bot.join(channel, password)
348
bot.reply(f"Joining {channel}")
349
350
@plugin.command('part')
351
@plugin.require_admin()
352
def part_command(bot, trigger):
353
"""Leave a channel."""
354
channel = trigger.group(2) or trigger.sender
355
356
if not channel.startswith('#'):
357
bot.reply("Must specify a channel to leave")
358
return
359
360
bot.part(channel, "Leaving on admin request")
361
if trigger.sender != channel:
362
bot.reply(f"Left {channel}")
363
364
@plugin.command('kick')
365
@plugin.require_privilege(plugin.OP)
366
def kick_command(bot, trigger):
367
"""Kick a user from the channel."""
368
args = trigger.group(2)
369
if not args:
370
bot.reply("Usage: .kick <nick> [reason]")
371
return
372
373
parts = args.split(' ', 1)
374
nick = parts[0]
375
reason = parts[1] if len(parts) > 1 else f"Kicked by {trigger.nick}"
376
377
bot.kick(nick, trigger.sender, reason)
378
```
379
380
### User and Channel Information
381
382
```python
383
@plugin.command('userinfo')
384
def userinfo_command(bot, trigger):
385
"""Show information about a user."""
386
nick = trigger.group(2) or trigger.nick
387
388
if nick in bot.users:
389
user = bot.users[nick]
390
info_parts = [f"User info for {nick}:"]
391
392
if hasattr(user, 'host'):
393
info_parts.append(f"Host: {user.host}")
394
if hasattr(user, 'account') and user.account:
395
info_parts.append(f"Account: {user.account}")
396
if hasattr(user, 'away') and user.away:
397
info_parts.append("Status: Away")
398
399
bot.reply(" | ".join(info_parts))
400
else:
401
bot.reply(f"No information available for {nick}")
402
403
@plugin.command('chaninfo')
404
def chaninfo_command(bot, trigger):
405
"""Show information about current channel."""
406
channel = trigger.sender
407
408
if not channel.startswith('#'):
409
bot.reply("This command only works in channels")
410
return
411
412
if channel in bot.channels:
413
chan = bot.channels[channel]
414
user_count = len(chan.users) if hasattr(chan, 'users') else 0
415
bot.reply(f"Channel {channel} has {user_count} users")
416
417
# Show channel modes if available
418
if hasattr(chan, 'modes'):
419
modes = '+'.join(chan.modes) if chan.modes else "none"
420
bot.reply(f"Channel modes: {modes}")
421
else:
422
bot.reply(f"Not currently in {channel}")
423
```
424
425
### Advanced IRC Features
426
427
```python
428
@plugin.command('whois')
429
def whois_command(bot, trigger):
430
"""Get WHOIS information for a user."""
431
nick = trigger.group(2)
432
if not nick:
433
bot.reply("Usage: .whois <nick>")
434
return
435
436
# Send WHOIS request
437
bot.write(['WHOIS', nick])
438
bot.reply(f"WHOIS request sent for {nick}")
439
440
@plugin.event('RPL_WHOISUSER') # 311 numeric
441
def handle_whois_response(bot, trigger):
442
"""Handle WHOIS response."""
443
# Parse WHOIS response: :server 311 bot_nick target_nick username hostname * :realname
444
if len(trigger.args) >= 6:
445
target_nick = trigger.args[1]
446
username = trigger.args[2]
447
hostname = trigger.args[3]
448
realname = trigger.args[5]
449
450
bot.say(f"WHOIS {target_nick}: {username}@{hostname} ({realname})")
451
452
@plugin.command('mode')
453
@plugin.require_privilege(plugin.OP)
454
def mode_command(bot, trigger):
455
"""Set channel modes."""
456
args = trigger.group(2)
457
if not args:
458
bot.reply("Usage: .mode <modes> [parameters]")
459
return
460
461
# Set modes on current channel
462
bot.mode(trigger.sender, args)
463
bot.reply(f"Mode change requested: {args}")
464
```
465
466
### Connection Monitoring
467
468
```python
469
@plugin.event('001') # RPL_WELCOME - successful connection
470
def on_connect(bot, trigger):
471
"""Handle successful IRC connection."""
472
bot.say("Bot connected successfully!", bot.settings.core.owner)
473
474
@plugin.event('PING')
475
def handle_ping(bot, trigger):
476
"""Handle PING from server."""
477
# Sopel handles PING automatically, but you can add custom logic
478
pass
479
480
@plugin.event('ERROR')
481
def handle_error(bot, trigger):
482
"""Handle ERROR messages from server."""
483
error_msg = trigger.args[0] if trigger.args else "Unknown error"
484
bot.say(f"IRC Error: {error_msg}", bot.settings.core.owner)
485
486
@plugin.command('reconnect')
487
@plugin.require_owner()
488
def reconnect_command(bot, trigger):
489
"""Reconnect to IRC server."""
490
bot.quit("Reconnecting...")
491
# Bot will automatically reconnect based on configuration
492
```
493
494
### Capability Usage
495
496
```python
497
@plugin.capability('account-tag')
498
@plugin.command('account')
499
def account_command(bot, trigger):
500
"""Show user's account information (requires account-tag capability)."""
501
if 'account-tag' not in bot.enabled_capabilities:
502
bot.reply("Account information not available (capability not supported)")
503
return
504
505
account = getattr(trigger, 'account', None)
506
if account:
507
bot.reply(f"You are logged in as: {account}")
508
else:
509
bot.reply("You are not logged in to services")
510
511
@plugin.event('CAP')
512
def handle_capability(bot, trigger):
513
"""Handle capability negotiation messages."""
514
# Sopel handles this automatically, but you can add custom logic
515
subcommand = trigger.args[1]
516
if subcommand == 'ACK':
517
capabilities = trigger.args[2].split()
518
for cap in capabilities:
519
bot.say(f"Capability enabled: {cap}", bot.settings.core.owner)
520
```
521
522
## Types
523
524
### IRC Message Context
525
526
```python { .api }
527
class Trigger:
528
"""Context information for IRC messages."""
529
530
# Message metadata
531
nick: str # Sender's nickname
532
user: str # Sender's username
533
host: str # Sender's hostname
534
hostmask: str # Full hostmask (nick!user@host)
535
sender: str # Channel or nick message came from
536
raw: str # Raw IRC message
537
538
# Message content
539
args: list # Message arguments
540
event: str # IRC event type (PRIVMSG, JOIN, etc.)
541
542
# Message properties
543
is_privmsg: bool # True if private message
544
account: str # Sender's services account (if available)
545
546
# Regex match methods
547
def group(self, n: int) -> str:
548
"""Get regex match group."""
549
550
def groups(self) -> tuple:
551
"""Get all regex match groups."""
552
```
553
554
### Channel and User Objects
555
556
```python { .api }
557
class Channel:
558
"""Represents an IRC channel."""
559
560
users: dict # Users in channel mapped to privilege levels
561
modes: set # Channel modes
562
topic: str # Channel topic
563
564
class User:
565
"""Represents an IRC user."""
566
567
nick: str # Current nickname
568
user: str # Username
569
host: str # Hostname
570
account: str # Services account
571
away: bool # Away status
572
channels: set # Channels user is in
573
574
class Identifier(str):
575
"""IRC identifier with case-insensitive comparison."""
576
577
def lower(self) -> str:
578
"""Get RFC1459 lowercase version."""
579
```