Skip to content

Commit 32a3b4a

Browse files
ExireldgwSnoopJ
committed
docs: basic tutorials
This starts a new set of tutorials for Plugin Author: * "Your first plugin" introduce the very concept of a Plugin * "Playing with your command" talks about commands and triggers * "Configuration and plugin setup" adds the plugin config section Combined, they provide a first introduction to Sopel Plugin development. They don't talk about everything, trying to introduce very few notions at the same time. Further tutorials would be welcome in future release of Sopel. Co-authored-by: dgw <[email protected]> Co-authored-by: James Gerity <[email protected]>
1 parent 271b306 commit 32a3b4a

File tree

5 files changed

+361
-0
lines changed

5 files changed

+361
-0
lines changed

docs/source/plugin.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Plugins: Developer Overview
66
:titlesonly:
77

88
plugin/what
9+
plugin/tutorials
910
plugin/anatomy
1011
plugin/bot
1112
plugin/time

docs/source/plugin/tutorials.rst

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=========
2+
Tutorials
3+
=========
4+
5+
The key feature of Sopel is its plugin system: *everything* Sopel does is
6+
through a plugin. Combining some basic Python knowledge with reading Sopel's
7+
documentation, you can write a plugin too!
8+
9+
These tutorials will guide and help you to begin your journey as a plugin
10+
author, i.e. someone who can write plugins for Sopel. Not every plugin is
11+
easy however, and you will probably need to hone your Python skills, learn more
12+
about the IRC protocol, and learn more about software programming in general.
13+
But let's not get ahead of ourselves; you are here for the basics.
14+
15+
.. toctree::
16+
:titlesonly:
17+
18+
tutorials/first-plugin
19+
tutorials/playing-with-commands
20+
tutorials/configuration-and-setup
21+
22+
23+
Requirements
24+
============
25+
26+
Before you can dive into these tutorials, you will need the following:
27+
28+
* to install and run Sopel on your development environment
29+
* to have write access to Sopel's configuration and plugin directory
30+
* a beginner level in Python (e.g. how to write a function, what is a variable,
31+
how to perform string formatting, how to access an object's attributes, how
32+
to import a module, etc.)
33+
34+
Since you'll be running Sopel, we invite you to create a configuration file
35+
that connects to a friendly IRC server and joins a private testing channel.
36+
That way, when you restart your bot or run your command for the hundredth
37+
time, you won't spam other users.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
==============================
2+
Configuration and plugin setup
3+
==============================
4+
5+
Maybe you :doc:`played with commands <playing-with-commands>` for your
6+
plugin and now you want to make your plugin configurable. If you run an
7+
instance of Sopel yourself, you probably had to open and edit its
8+
:doc:`configuration</configuration>` file.
9+
10+
Usually located in the ``.sopel/`` folder under your home directory, the
11+
configuration file is an INI file with sections defined by Sopel's core and by
12+
plugins. In this tutorial, let's see how to declare and use a configuration
13+
section dedicated to your plugin.
14+
15+
16+
Declaring your configuration
17+
============================
18+
19+
To declare a configuration section, you must first create a subclass of
20+
:class:`~sopel.config.types.StaticSection`, and define attributes::
21+
22+
from sopel.config import types
23+
24+
class MyPluginSection(types.StaticSection):
25+
fruits = types.ListAttribute('fruits')
26+
27+
28+
Telling Sopel about it
29+
======================
30+
31+
Now, having a class in your plugin doesn't achieve much: you need to tell the
32+
bot about it by using the :meth:`~sopel.config.Config.define_section` method.
33+
The best place to do so is in the :func:`setup` function hook of your plugin::
34+
35+
def setup(bot):
36+
bot.settings.define_section('myplugin', MyPluginSection)
37+
38+
This way, you tell Sopel that the ``[myplugin]`` section in the **configuration
39+
file** is used by your plugin, and to parse this section Sopel must use your
40+
class, i.e. ``MyPluginSection``.
41+
42+
43+
Using your section
44+
==================
45+
46+
Now that you have told Sopel about your custom section, you can add the
47+
following lines in your configuration file:
48+
49+
.. code-block:: ini
50+
51+
[myplugin]
52+
fruits =
53+
banana
54+
apple
55+
peach
56+
strawberry
57+
58+
And Sopel will expose that for you through ``bot.settings.myplugin``. For
59+
example, you can write this command::
60+
61+
import random
62+
63+
@plugin.command('fruits')
64+
def fruits(bot, trigger):
65+
fruit = random.choice(bot.settings.myplugin.fruits)
66+
bot.say(f'I want a {fruit}!')
67+
68+
And whenever someone triggers this command, the bot will say that it wants one
69+
of the configured fruits. If you want to list 50 fruits or only 2 is up to you,
70+
and to the bot owners who will install your plugin.
71+
72+
73+
Putting everything together
74+
===========================
75+
76+
We can combine all of this into one plugin file, located at the same place as
77+
before (``~/.sopel/plugins/myplugin.py``, assuming the default location)::
78+
79+
import random
80+
from sopel.config import types
81+
82+
83+
class MyPluginSection(types.StaticSection):
84+
"""Declaration of your plugin's configuration."""
85+
fruits = types.ListAttribute('fruits')
86+
87+
88+
def setup(bot):
89+
"""Telling the bot about the plugin's configuration."""
90+
bot.settings.define_section('myplugin', MyPluginSection)
91+
92+
93+
@plugin.command('fruits')
94+
def fruits(bot, trigger):
95+
"""Using the plugin's configuration in our command."""
96+
fruit = random.choice(bot.settings.myplugin.fruits)
97+
bot.say(f'I want a {fruit}!')
98+
99+
As you can see, there are **several steps** when it comes to configuration:
100+
101+
* creating a class to represent your configuration section
102+
* telling Sopel about it in a ``setup`` function
103+
* using your plugin's configuration in your plugin
104+
105+
Sopel tries to make it as straightforward and flexible as possible for you to
106+
declare and to setup your plugin configuration, and you can read more about
107+
:ref:`plugin configuration <plugin-anatomy-config>`,
108+
which includes a section about the configuration wizard as well. You can also
109+
see Sopel's own configuration in
110+
:doc:`the configuration chapter </configuration>`.
111+
112+
Once you are familiar with the concept, you can also read deeper into the
113+
reference documentation for the :mod:`sopel.config` module.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
=================
2+
Your first plugin
3+
=================
4+
5+
Sopel's most interesting features come from its plugins, either published by
6+
Sopel's developers or by third-party developers, and you can write your own
7+
plugins. But where do you start?
8+
9+
Here is a very short example of code for your first plugin that contains one
10+
and only one command::
11+
12+
from sopel import plugin
13+
14+
@plugin.command('hello')
15+
def hello(bot, trigger):
16+
"""Reply with Hello!"""
17+
bot.reply('Hello!')
18+
19+
You can put this code in a Python file, placed into your Sopel plugin
20+
directory, such as ``~/.sopel/plugins/myplugin.py``. Once this is done, you can
21+
check if the bot can see the plugin, by using the ``sopel-plugins`` command
22+
line tool::
23+
24+
$ sopel-plugins show myplugin
25+
Plugin: myplugin
26+
Status: enabled
27+
Type: python-file
28+
Source: /path/to/home/.sopel/plugins/myplugin.py
29+
Label: myplugin plugin
30+
Loaded successfully
31+
Setup: no
32+
Shutdown: no
33+
Configure: no
34+
35+
Notice how the filename (without the extension) is also the name of the plugin:
36+
if you were to name your file ``hello.py``, it would be the ``hello`` plugin.
37+
38+
If ``status`` is not ``enabled``, you can enable your plugin with
39+
``sopel-plugins enable hello``.
40+
41+
Then, you can start your bot and trigger the command like this::
42+
43+
<YourNick> .hello
44+
<Sopel> YourNick: Hello!
45+
46+
And voilà! This is your first plugin. Sure, it doesn't do much, and yet it uses
47+
the key elements that you'll need to understand to write your own plugins.
48+
49+
.. seealso::
50+
51+
To interact with the list of plugins installed, read the documentation
52+
of :ref:`sopel-plugins`.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
=====================
2+
Playing with commands
3+
=====================
4+
5+
Now that you have started :doc:`your first plugin <first-plugin>`, maybe you
6+
want to write a more interesting command than the basic ``.hello`` one. Not
7+
that there is anything wrong with that command! The emoticons plugin is
8+
composed of commands like this one::
9+
10+
@plugin.command('shrug')
11+
@plugin.action_command('shrugs')
12+
def shrug(bot, trigger):
13+
bot.say('¯\\_(ツ)_/¯')
14+
15+
Which is one of the maintainers' favorite commands to use. However, let's see
16+
if we can do something a bit more *complex* than that.
17+
18+
19+
Greeting a user by their name
20+
=============================
21+
22+
Have you noticed that a :ref:`plugin callable <Plugin callables>` takes **two
23+
arguments?** The first one is the ``bot``, an instance of Sopel that you can
24+
use to :doc:`interact with the bot </plugin/bot>`.
25+
26+
In the previous tutorial, we used ``bot.reply``, which is convenient when
27+
responding directly to a user, but not always what you want. Maybe you want the
28+
bot to say something more complex::
29+
30+
<YourNick> .hello
31+
<Sopel> Hello YourNick, have a nice day!
32+
33+
For that, you need **the second argument**: the ``trigger``. It is an object
34+
with information about the message that triggered your
35+
callable, such as the **message** itself, the **channel**, the type of message,
36+
etc.—and what we need for now is the
37+
:attr:`trigger.nick <sopel.trigger.Trigger.nick>` attribute::
38+
39+
from sopel import plugin
40+
41+
@plugin.command('hello')
42+
def hello(bot, trigger):
43+
"""Say Hello <user>, have a nice day!"""
44+
bot.say(f'Hello {trigger.nick}, have a nice day!')
45+
46+
.. important::
47+
48+
If you want to test this with your bot, and your bot is already running,
49+
restart the bot so it will load the new version of your plugin.
50+
51+
.. seealso::
52+
53+
You can learn much more about the :class:`trigger <sopel.trigger.Trigger>`
54+
object by reading its documentation.
55+
56+
57+
Command with arguments
58+
======================
59+
60+
The trigger object can do much more for you: if a user adds arguments to the
61+
command, like ``.hello morning``, you can detect and use that argument::
62+
63+
from sopel import plugin
64+
65+
@plugin.command('hello')
66+
def hello(bot, trigger):
67+
"""Say Hello <user>, have a nice day!"""
68+
# group 1 is the name of the command that was triggered
69+
# group 2 is the entire rest of the message
70+
# groups 3 to 6 are the first, second, third, and fourth command arg
71+
when = trigger.group(3)
72+
# select a different greeting depending on when
73+
greeting = {
74+
'morning': 'and good morning!',
75+
'noon': 'are you having lunch?',
76+
'night': 'I hope it was a good day!',
77+
'evening': 'good evening to you!'
78+
}.get(when, 'have a nice day!') # default to "nice day"
79+
# say hello
80+
bot.say(f'Hello {trigger.nick}, {greeting}')
81+
82+
Now the command will be able to react a bit more to your user::
83+
84+
<YourNick> .hello morning
85+
<Sopel> Hello YourNick, and good morning!
86+
<YourNick> .hello noon
87+
<Sopel> Hello YourNick, are you having lunch?
88+
89+
How does that work? Well, the short version is that Sopel uses regex
90+
(`REGular EXpressions`__) to match a message to a plugin callable, and the
91+
``trigger`` object exposes the match result.
92+
93+
.. seealso::
94+
95+
You can learn much more about the :class:`~sopel.plugin.command` decorator
96+
by reading its documentation.
97+
98+
.. note::
99+
100+
In the case of a command, the regex is entirely managed by Sopel itself,
101+
while the generic :func:`@plugin.rule <sopel.plugin.rule>` decorator
102+
allows you to define your own regex.
103+
104+
.. __: https://en.wikipedia.org/wiki/Regular_expression
105+
106+
107+
And... action!
108+
==============
109+
110+
Some users say ``.hello`` out loud, and others will say it with an action. How
111+
do you react to these? Let's go back to the example of the ``shrug`` command::
112+
113+
@plugin.command('shrug')
114+
@plugin.action_command('shrugs')
115+
def shrug(bot, trigger):
116+
bot.say('¯\\_(ツ)_/¯')
117+
118+
Notice that it also uses a second decorator, ``action_command('shrugs')``,
119+
with a different name. How does that work?
120+
121+
Sopel knows how to register the same plugin callable for different types of
122+
trigger, so both ``.shrug`` and ``/me shrugs`` work. For example, you could do
123+
this for your hello plugin::
124+
125+
@plugin.command('hello')
126+
@plugin.action_command('waves')
127+
def hello(bot, trigger):
128+
...
129+
130+
And so, in chat, you will see that::
131+
132+
<YourNick> .hello
133+
<Sopel> Hello YourNick, have a nice day!
134+
* YourNick waves
135+
<Sopel> Hello YourNick, have a nice day!
136+
137+
138+
Summing it up
139+
=============
140+
141+
In this tutorial, we talked briefly about ``bot.say()`` and ``bot.reply()``,
142+
and explored a few more ways to :doc:`interact with the bot </plugin/bot>`.
143+
144+
We saw that you can use the :class:`trigger <sopel.trigger.Trigger>` argument
145+
of a plugin callable to get more information on the message that triggered the
146+
command. Don't hesitate to read the documentation of that object and discover
147+
all its properties.
148+
149+
We also saw that you have more ways to trigger a callable, and you can read
150+
more in :doc:`the plugin anatomy chapter </plugin/anatomy>` (see
151+
:ref:`how to define rules <plugin-anatomy-rules>`, in particular).
152+
153+
Throughout this tutorial, we also linked to various sections of the
154+
documentation: as we improve the documentation with every release, we invite
155+
you to read it to discover more features of Sopel and what is available to you
156+
as a plugin author.
157+
158+
And if you have come this far, thank you for reading this!

0 commit comments

Comments
 (0)