0
# Plugin Development
1
2
Sopel's plugin system is the core of its extensibility, providing a decorator-based approach for creating bot functionality. Plugins are Python modules that use decorators to register functions as commands, rules, events, and other bot behaviors.
3
4
## Capabilities
5
6
### Command Decorators
7
8
Register functions to respond to specific IRC commands triggered by a command prefix (typically `.` or `!`).
9
10
```python { .api }
11
def command(*command_names: str):
12
"""
13
Register a function as a command.
14
15
Args:
16
*command_names (str): One or more command names that trigger this function
17
18
Example:
19
@plugin.command('hello')
20
def hello_cmd(bot, trigger): ...
21
22
@plugin.command('hi', 'hello', 'hey')
23
def greet_cmd(bot, trigger): ...
24
"""
25
26
commands = command
27
"""
28
Alias to command() decorator.
29
30
This is an alias for the command() decorator, not a separate function.
31
Use @plugin.command() instead.
32
"""
33
34
def action_command(command_name: str):
35
"""
36
Register a function as an action command (CTCP ACTION).
37
38
Args:
39
command_name (str): The command name for ACTION messages
40
"""
41
42
def action_commands(*command_names: str):
43
"""
44
Register a function for multiple action commands.
45
46
Args:
47
*command_names (str): Multiple action command names
48
"""
49
50
def nickname_command(command_name: str):
51
"""
52
Register a function triggered when bot's nick is used as command.
53
54
Args:
55
command_name (str): The command name after bot's nickname
56
57
Example:
58
@plugin.nickname_command('hello')
59
def nick_hello(bot, trigger): ...
60
# Triggered by: "BotName: hello"
61
"""
62
63
def nickname_commands(*command_names: str):
64
"""
65
Register a function for multiple nickname commands.
66
67
Args:
68
*command_names (str): Multiple nickname command names
69
"""
70
```
71
72
### Rule-Based Pattern Matching
73
74
Register functions to respond to messages matching regular expression patterns.
75
76
```python { .api }
77
def rule(pattern: str):
78
"""
79
Register a function to trigger on regex pattern matches.
80
81
Args:
82
pattern (str): Regular expression pattern to match
83
84
Example:
85
@plugin.rule(r'.*\b(hello|hi)\b.*')
86
def greet_rule(bot, trigger): ...
87
"""
88
89
def rule_lazy(lazy_loader):
90
"""
91
Register a function with a lazy-loaded regex pattern.
92
93
Args:
94
lazy_loader (callable): Function returning list of regex patterns
95
"""
96
97
def find(pattern: str):
98
"""
99
Register a function to find pattern anywhere in message.
100
101
Args:
102
pattern (str): Pattern to search for in messages
103
"""
104
105
def find_lazy(lazy_loader):
106
"""
107
Register a function with lazy-loaded find patterns.
108
109
Args:
110
lazy_loader (callable): Function returning list of patterns
111
"""
112
113
def search(pattern: str):
114
"""
115
Register a function for pattern search with match groups.
116
117
Args:
118
pattern (str): Search pattern with capture groups
119
"""
120
121
def search_lazy(lazy_loader):
122
"""
123
Register a function with lazy-loaded search patterns.
124
125
Args:
126
lazy_loader (callable): Function returning list of search patterns
127
"""
128
```
129
130
### Event Handling
131
132
Register functions to respond to specific IRC events and protocol messages.
133
134
```python { .api }
135
def event(*event_types: str):
136
"""
137
Register a function to handle specific IRC events.
138
139
Args:
140
*event_types (str): IRC event types to handle (JOIN, PART, QUIT, etc.)
141
142
Example:
143
@plugin.event('JOIN')
144
def on_join(bot, trigger): ...
145
"""
146
147
def ctcp(ctcp_command: str):
148
"""
149
Register a function to handle CTCP commands.
150
151
Args:
152
ctcp_command (str): CTCP command to handle (VERSION, PING, etc.)
153
154
Example:
155
@plugin.ctcp('VERSION')
156
def ctcp_version(bot, trigger): ...
157
"""
158
```
159
160
### URL Handling
161
162
Register functions to process URLs in messages with automatic URL detection and processing.
163
164
```python { .api }
165
def url(url_pattern: str):
166
"""
167
Register a function to handle URLs matching a pattern.
168
169
Args:
170
url_pattern (str): Regex pattern for URL matching
171
172
Example:
173
@plugin.url(r'https?://example\.com/.*')
174
def handle_example_url(bot, trigger): ...
175
"""
176
177
def url_lazy(lazy_loader):
178
"""
179
Register a function with lazy-loaded URL patterns.
180
181
Args:
182
lazy_loader (callable): Function returning list of URL patterns
183
"""
184
```
185
186
### Scheduled Tasks
187
188
Register functions to run at regular intervals or specific times.
189
190
```python { .api }
191
def interval(seconds: int):
192
"""
193
Register a function to run at regular intervals.
194
195
Args:
196
seconds (int): Interval between executions in seconds
197
198
Example:
199
@plugin.interval(300) # Every 5 minutes
200
def periodic_task(bot): ...
201
"""
202
```
203
204
### Access Control
205
206
Decorators to restrict plugin functions based on user privileges and message context.
207
208
```python { .api }
209
def require_privmsg():
210
"""
211
Restrict function to private messages only.
212
213
Example:
214
@plugin.require_privmsg()
215
@plugin.command('secret')
216
def secret_cmd(bot, trigger): ...
217
"""
218
219
def require_chanmsg():
220
"""
221
Restrict function to channel messages only.
222
"""
223
224
def require_account():
225
"""
226
Require user to be authenticated with services.
227
"""
228
229
def require_admin():
230
"""
231
Require user to be a bot admin.
232
"""
233
234
def require_owner():
235
"""
236
Require user to be a bot owner.
237
"""
238
239
def require_privilege(level: 'AccessLevel'):
240
"""
241
Require specific channel privilege level.
242
243
Args:
244
level (AccessLevel): Minimum required privilege level
245
246
Example:
247
@plugin.require_privilege(plugin.OP)
248
@plugin.command('kick')
249
def kick_cmd(bot, trigger): ...
250
"""
251
252
def require_bot_privilege(level: 'AccessLevel'):
253
"""
254
Require bot to have specific channel privileges.
255
256
Args:
257
level (AccessLevel): Required bot privilege level
258
"""
259
```
260
261
### Rate Limiting
262
263
Control how frequently plugin functions can be triggered to prevent spam and abuse.
264
265
```python { .api }
266
def rate(user: int = 0, channel: int = 0, server: int = 0, *, message: Optional[str] = None):
267
"""
268
Apply rate limiting to plugin function.
269
270
Args:
271
user (int): Seconds between triggers per user (0 = no limit)
272
channel (int): Seconds between triggers per channel (0 = no limit)
273
server (int): Seconds between any triggers (0 = no limit)
274
message (str): Optional notice message when rate limit is reached
275
276
Example:
277
@plugin.rate(user=10, channel=30, message='Please wait!')
278
@plugin.command('expensive')
279
def expensive_cmd(bot, trigger): ...
280
"""
281
282
def rate_user(seconds: int):
283
"""
284
Rate limit per user.
285
286
Args:
287
seconds (int): Seconds between triggers per user
288
"""
289
290
def rate_channel(seconds: int):
291
"""
292
Rate limit per channel.
293
294
Args:
295
seconds (int): Seconds between triggers per channel
296
"""
297
298
def rate_global(seconds: int):
299
"""
300
Global rate limit.
301
302
Args:
303
seconds (int): Seconds between any triggers
304
"""
305
```
306
307
### Plugin Metadata and Behavior
308
309
Decorators to add metadata and control plugin function behavior.
310
311
```python { .api }
312
def label(label_name: str):
313
"""
314
Add a label to a plugin function for identification.
315
316
Args:
317
label_name (str): Label for the function
318
"""
319
320
class example:
321
"""
322
Add usage example to plugin function documentation with testing support.
323
324
Args:
325
msg (str): Example command or message
326
result (str or list, optional): Expected output for testing
327
privmsg (bool, optional): If True, test as private message
328
admin (bool, optional): If True, test as admin user
329
owner (bool, optional): If True, test as owner user
330
repeat (int, optional): Number of times to repeat test
331
re (bool, optional): Use regex matching for result
332
ignore (list, optional): Patterns to ignore in output
333
user_help (bool, optional): Include in user help output
334
online (bool, optional): Mark as online test
335
vcr (bool, optional): Record HTTP requests for testing
336
337
Example:
338
@plugin.example('.weather London', 'Weather in London: 15°C')
339
@plugin.command('weather')
340
def weather_cmd(bot, trigger): ...
341
"""
342
343
def output_prefix(prefix: str):
344
"""
345
Set output prefix for plugin function responses.
346
347
Args:
348
prefix (str): Prefix string for bot responses
349
"""
350
351
def priority(level: str):
352
"""
353
Set execution priority for plugin function.
354
355
Args:
356
level (str): Priority level ('low', 'medium', 'high')
357
"""
358
359
def thread(threaded: bool = True):
360
"""
361
Control whether function runs in separate thread.
362
363
Args:
364
threaded (bool): True to run in thread, False for main thread
365
"""
366
367
def echo():
368
"""
369
Allow function to respond to bot's own messages.
370
"""
371
372
def unblockable():
373
"""
374
Prevent function from being blocked by ignore lists.
375
"""
376
377
def allow_bots():
378
"""
379
Allow function to respond to other bots.
380
"""
381
```
382
383
### Capability Negotiation
384
385
Handle IRC capability negotiation for advanced protocol features.
386
387
```python { .api }
388
def capability(*capabilities: str):
389
"""
390
Register IRC capabilities that the plugin uses.
391
392
Args:
393
*capabilities (str): IRC capability names
394
395
Example:
396
@plugin.capability('account-tag')
397
@plugin.command('whoami')
398
def whoami_cmd(bot, trigger): ...
399
"""
400
```
401
402
## Usage Examples
403
404
### Basic Command Plugin
405
406
```python
407
from sopel import plugin
408
409
@plugin.command('hello')
410
@plugin.example('.hello')
411
def hello_command(bot, trigger):
412
"""Greet a user."""
413
bot.reply(f"Hello, {trigger.nick}!")
414
415
@plugin.command('roll')
416
@plugin.example('.roll 6')
417
@plugin.rate_user(5) # 5 second cooldown per user
418
def roll_dice(bot, trigger):
419
"""Roll a die."""
420
import random
421
sides = 6
422
if trigger.group(2):
423
try:
424
sides = int(trigger.group(2))
425
except ValueError:
426
bot.reply("Please provide a valid number.")
427
return
428
429
result = random.randint(1, sides)
430
bot.reply(f"🎲 Rolled a {result} (1-{sides})")
431
```
432
433
### Advanced Plugin with Configuration and Database
434
435
```python
436
from sopel import plugin, config, db
437
438
class WeatherSection(config.types.StaticSection):
439
api_key = config.types.ValidatedAttribute('api_key')
440
default_location = config.types.ValidatedAttribute('default_location', default='London')
441
442
@plugin.command('weather')
443
@plugin.example('.weather London')
444
@plugin.rate_channel(30) # 30 second channel cooldown
445
def weather_command(bot, trigger):
446
"""Get weather information."""
447
location = trigger.group(2) or bot.settings.weather.default_location
448
449
# Store user's last location query
450
bot.db.set_nick_value(trigger.nick, 'last_weather_location', location)
451
452
# Weather API call would go here
453
bot.reply(f"Weather for {location}: Sunny, 25°C")
454
455
@plugin.rule(r'.*\b(hot|cold|weather)\b.*')
456
@plugin.require_chanmsg()
457
def weather_mention(bot, trigger):
458
"""Respond to weather mentions."""
459
last_location = bot.db.get_nick_value(trigger.nick, 'last_weather_location')
460
if last_location:
461
bot.say(f"The weather in {last_location} is still nice!")
462
```
463
464
### Event Handler Plugin
465
466
```python
467
from sopel import plugin
468
469
@plugin.event('JOIN')
470
def welcome_user(bot, trigger):
471
"""Welcome new users to the channel."""
472
if trigger.nick != bot.nick: # Don't welcome ourselves
473
bot.say(f"Welcome to {trigger.sender}, {trigger.nick}!")
474
475
@plugin.event('PART', 'QUIT')
476
def goodbye_user(bot, trigger):
477
"""Say goodbye to leaving users."""
478
if trigger.nick != bot.nick:
479
bot.say(f"Goodbye, {trigger.nick}!")
480
```
481
482
## Types
483
484
```python { .api }
485
from typing import Optional
486
```
487
488
### Plugin Function Parameters
489
490
```python { .api }
491
# Standard plugin function signature
492
def plugin_function(bot: SopelWrapper, trigger: Trigger):
493
"""
494
Standard plugin function.
495
496
Args:
497
bot (SopelWrapper): Bot interface for sending messages and accessing data
498
trigger (Trigger): Context information about the triggering message
499
"""
500
501
# Interval function signature (no trigger)
502
def interval_function(bot: SopelWrapper):
503
"""
504
Interval function for scheduled tasks.
505
506
Args:
507
bot (SopelWrapper): Bot interface
508
"""
509
```
510
511
### Constants
512
513
```python { .api }
514
# Rate limiting bypass constant
515
NOLIMIT: int = 1
516
517
# Access level constants (imported from sopel.privileges)
518
VOICE: AccessLevel
519
HALFOP: AccessLevel
520
OP: AccessLevel
521
ADMIN: AccessLevel
522
OWNER: AccessLevel
523
OPER: AccessLevel
524
525
# Capability negotiation options
526
class CapabilityNegotiation(enum.Enum):
527
NONE: str = "none"
528
OPTIONAL: str = "optional"
529
REQUIRED: str = "required"
530
```