Skip to content

Commit

Permalink
docs: basic tutorials
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Exirel committed Jul 20, 2023
1 parent 271b306 commit 34777e2
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Documentation
:maxdepth: 2

run
tutorials
plugin
package
tests
Expand Down
10 changes: 10 additions & 0 deletions docs/source/tutorials.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
=========
Tutorials
=========

.. toctree::
:titlesonly:

tutorials/first-plugin
tutorials/playing-command
tutorials/configurable
106 changes: 106 additions & 0 deletions docs/source/tutorials/configurable.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
==============================
Configuration and plugin setup
==============================

Maybe you :doc:`played with commands </tutorials/playing-command>` for your
plugin and now you want to make your plugin configurable. If you run an
instance of Sopel yourself, you probably had to open and edit its configuration
file.

Usually located in your home directory, under the ``.sopel/`` folder, the
configuration file is an INI file with sections defined by Sopel's core and by
plugins. In this tutorial, let's see how to declare and use a configuration
section dedicated to your plugin.


Declaring your configuration
============================

To declare a configuration section, you must first create a subclass of
:class:`~sopel.config.types.StaticSection`, and define attributes::

from sopel.config import types

class MyFirstPlugin(types.StaticSection):
fruits = types.ListAttribute('fruits')


Telling Sopel about it
======================

Now, having a class in your plugin doesn't achieve much: you need to tell the
bot about it. The best place to do so is in the :func:`setup` function
hook of your plugin::

def setup(bot):
bot.settings.define_section('firstplugin', MyFirstPlugin)

This way, you tell Sopel that the section ``[firstplugin]`` is used by your
plugin, and to parse this section Sopel must use your class, i.e.
``MyFirstPlugin``.


Using your section
==================

In your configuration file, you can add the following lines:

.. code-block:: ini
[firstplugin]
fruits =
banana
apple
peach
strawberry
And Sopel will expose that for you through ``bot.settings``. For example, you
can write this command::

import random

@plugin.command('fruits')
def fruits(bot, trigger):
fruit = random.choice(bot.settings.firstplugin.fruits)
bot.say(f'I want a {fruit}!')

And whenever someone triggers this command, the bot will say that it wants one
of the configured fruits. If you want to list 50 fruits or only 2 is up to you,
and to the bot owners who will install your plugin.


Putting everything together
===========================

We can regroup everything together in one file::

import random
from sopel.config import types


class MyFirstPlugin(types.StaticSection):
"""Declaration of your plugin's configuration."""
fruits = types.ListAttribute('fruits')


def setup(bot):
"""Telling the bot about the plugin's configuration."""
bot.settings.define_section('firstplugin', MyFirstPlugin)


@plugin.command('fruits')
def fruits(bot, trigger):
"""Using the plugin's configuration in our command."""
fruit = random.choice(bot.settings.firstplugin.fruits)
bot.say(f'I want a {fruit}!')

As you can see, there are several steps when it comes to configuration. Sopel
tries to make it as straightforward as possible for you to declare and to setup
your plugin configuration, and it offers various possibilities.

You can read more about :ref:`plugin configuration <plugin-anatomy-config>`,
which includes a section about the configuration wizzard as well. You can also
see Sopel's own configuration in :doc:`/configuration` chapter.

Once you are familiar with the concept, you can also read deeper into the
reference documentation for the :mod:`sopel.config` module.
49 changes: 49 additions & 0 deletions docs/source/tutorials/first-plugin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
=================
Your first plugin
=================

Sopel's most interesting features come from its plugins, either published by
Sopel's developers or by third-party developers, and you can write your own
plugins. But where do you start?

Here is a very short example of code for a "hello" plugin that contains one
and only one command::

from sopel import plugin

@plugin.command('hello')
def hello(bot, trigger):
"""Reply with Hello!"""
bot.reply('Hello!')

You can put this code in a Python file, placed into your Sopel plugin
directory, such as ``~/.sopel/plugins/hello.py``. Once this is done, you can
check if the bot can see the plugin, by using the ``sopel-plugins`` command
line tool::

$ sopel-plugins show hello
Plugin: hello
Status: enabled
Type: python-file
Source: /path/to/home/.sopel/plugins/hello.py
Label: hello plugin
Loaded successfully
Setup: no
Shutdown: no
Configure: no

If ``status`` is not ``enabled``, you can enable your plugin with
``sopel-plugins enable hello``.

Then, you can start your bot and trigger the command like this::

<YourNick> .hello
<Sopel> YourNick: Hello!

And voilà! This is your first plugin. Sure, it doesn't do much, and yet it uses
the key elements that you'll need to understand to write your own plugins.

.. seealso::

To interract with the list of plugins installed, read the documentation
of :ref:`sopel-plugins`.
129 changes: 129 additions & 0 deletions docs/source/tutorials/playing-command.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
=========================
Playing with your command
=========================

Now that you have started :doc:`first-plugin`, maybe you want to write a more
interesting command than the basic ``.hello`` one. Not that there is anything
wrong with that command! The emoticons plugin is composed of commands like this
one::

@plugin.command('shrug')
@plugin.action_command('shrugs')
def shrug(bot, trigger):
bot.say('¯\\_(ツ)_/¯')

Which is one of the maintainers' favorite command to use. However, let's see
if we can do a bit more complex than that.

Greeting a user by their name
=============================

Have you noticed that a :func:`plugin_callable` takes two arguments? The first
one is the ``bot``, i.e. an instance of Sopel (wrapped for your convenience)
that you can use to :doc:`/plugin/bot`.

In the previous tutorial, we used ``bot.reply``, which is convenient but not
always what you want. Maybe you want the bot to say this::

<YourNick> .hello
<Sopel> Hello YourNick, have a nice day!

For that, you need the second argument: the ``trigger``. It is an object with
all the information you need about the message that triggered the callable,
such as the message itself, the channel, the type of message, etc. and what we
need for now is the :attr:`trigger.nick <sopel.trigger.Trigger.nick>`
attribute::

from sopel import plugin

@plugin.command('hello')
def hello(bot, trigger):
"""Say Hello <user>, have a nice day!"""
bot.say(f'Hello {trigger.nick}, have a nice day!')

.. seealso::

You can learn much more about the :class:`trigger <sopel.trigger.Trigger>`
object by reading its documentation.

Command with arguments
======================

The trigger object can do much more for you: if a user add arguments to the
command, like this: ``.hello morning``, you can detect and use that argument::

from sopel import plugin

@plugin.command('hello')
def hello(bot, trigger):
"""Say Hello <user>, have a nice day!"""
# groups 3 to 6 are the first, second, third, and fourth command arg
when = trigger.group(3)
# select a different greeting depending on when
greeting = {
'morning': 'and good morning!',
'noon': 'are you having lunch?',
'night': 'I hope it was a good day!',
'evening': 'good evening to you!'
}.get(when, 'have a nice day!') # default to "nice day"
# say hello
bot.say(f'Hello {trigger.nick}, {greeting}')

Now the command will be able to react a bit more to your user::

<YourNick> .hello morning
<Sopel> Hello YourNick, and good morning!
<YourNick> .hello noon
<Sopel> Hello YourNick, are you having lunch?

.. seealso::

You can learn much more about the :class:`~sopel.plugin.command` decorator
by reading its documentation.

And... action!
==============

Some users say ``.hello`` out loud, and other will say it with an action. How
do you react to these? Let's go back to our initial example of the ``shrug``
command: it uses a second decorator, ``action_command('shrugs')``, with a
different name. How does that work?

Sopel knows how to register the same plugin callable for different types of
trigger, so both ``.shrug`` and ``/me shrugs`` work. For example, you could do
that for your hello plugin::

@plugin.command('hello')
@plugin.action_command('hi')
def hello(bot, trigger):
...

And so, in chat, you will see that::

<YourNick> .hello
<Sopel> Hello YourNick, have a nice day!
*YourNick hi*
<Sopel> Hello YourNick, have a nice day!


Summing It Up
=============

In this tutorial, we talked briefly about ``bot.say()`` and ``bot.reply()``,
and there are more ways to :doc:`interact with the bot </plugin/bot>`.

We saw that you can use the :class:`trigger <sopel.trigger.Trigger>` argument
of a plugin callable to get more information on the message that triggered the
command. Don't hesitate to read the documentation of that object and discover
all its properties.

We also saw that you have more ways to trigger a callable, and you can read
more in the :doc:`/plugin/anatomy` chapter (see :ref:`plugin-anatomy-rules` in
particular).

Throughout this tutorial, we also linked to various sections of the
documentation: as we improve the documentation with every release, we invite
you to read it to discover more features of Sopel and what is available to you
as a plugin author.

And if you have come this far, thank you for reading this!

0 comments on commit 34777e2

Please sign in to comment.