or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mddatabase.mdindex.mdirc-protocol.mdplugin-development.mdutilities.md

irc-protocol.mddocs/

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

```