.. Copyright (C) 2016, 2021 taylor.fish <contact@taylor.fish>

.. This file is part of pyrcb2-docs, documentation for pyrcb2.

.. pyrcb2-docs is licensed under the GNU Lesser General Public License
   as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

.. As an additional permission under GNU GPL version 3 section 7, you
   may distribute non-source forms of pyrcb2-docs without the copy of
   the GNU GPL normally required by section 4, provided you include a
   URL through which recipients can obtain a copy of the Corresponding
   Source and the GPL at no charge.

.. pyrcb2-docs is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

.. You should have received a copy of the GNU Lesser General Public License
   along with pyrcb2-docs.  If not, see <http://www.gnu.org/licenses/>.

.. currentmodule:: pyrcb2

Getting started
===============

.. note::

   Throughout pyrcb2, all parameters and attributes that represent nicknames
   or channels are case-insensitive strings (type `IStr`)---equality
   comparisons with those parameters and attributes are case-insensitive.

Begin by importing `IRCBot` and `Event` from ``pyrcb2``::

    from pyrcb2 import IRCBot, Event

We'll also import ``asyncio``, which we'll need later::

    import asyncio

Then we'll create our bot class, called ``MyBot``::

    class MyBot:
        def __init__(self):
            self.bot = IRCBot(log_communication=True)
            self.bot.load_events(self)

``MyBot`` objects will create an `IRCBot` object when initialized and store it
in ``self.bot``. ``log_communication=True`` will cause communication with the
server to be logged to standard output (often useful when debugging).

``self.bot.load_events(self)`` loads events that are part of the current
``MyBot`` object. We'll add some events later. First, we'll create a method
that starts and runs our bot::

    class MyBot:
        def __init__(self):
            self.bot = IRCBot(log_communication=True)
            self.bot.load_events(self)

        async def run(self):
            async def init():
                await self.bot.connect("irc.example.com", 6667)
                await self.bot.register("mybot")
                # More code here (optional)...
            await self.bot.run(init())

:meth:`IRCBot.run` (``self.bot.run()``) must be called before we use any other
methods like :meth:`~IRCBot.connect` and :meth:`~IRCBot.register`. It accepts a
coroutine as a parameter---that coroutine can call :meth:`~IRCBot.connect` and
so on. :meth:`IRCBot.run` will keep running until the bot is disconnected from
the server.

In place of the "More code here" comment in the inner ``init()`` function, we
could call methods like ``self.bot.join("#mybot")`` if we wanted the bot to do
something right after starting up.

Next, let's add an event to our bot. Events are methods decorated with
`Event` decorators. To add an event that gets called every time a message
(``PRIVMSG``) is received, we could add the following method to ``MyBot``::

    @Event.privmsg
    async def on_privmsg(self, sender, channel, message):
        if channel is None:
            # Message was sent in a private query.
            self.bot.privmsg(sender, "You said: " + message)
            return

        # Message was sent in a channel.
        self.bot.privmsg(channel, sender + " said: " + message)

Let's also add an event handler for ``JOIN`` messages. We can use ``await``
inside ``async def`` event handlers::

    @Event.join
    async def on_join(self, sender, channel):
        if sender == self.nickname:
            # Don't do anything if this bot is the user who joined.
            return
        self.privmsg(channel, sender + " has joined " + channel)

        # This will execute a WHOIS request for `sender` and will
        # block until the request is complete. Since this is a
        # coroutine, the rest of the bot won't freeze up.
        result = await self.bot.whois(sender)
        if result.success:
            whois_reply = result.value
            server = whois_reply.server or "an unknown server"
            msg = sender + " is connected to " + server
            self.privmsg(channel, msg)

When someone joins a channel, our bot will perform a WHOIS query on them. We
could have written ``self.bot.whois(sender)`` without the ``await``, but
using ``await`` allows us to wait for the WHOIS response before continuing.
Otherwise, we would need to add an `Event.whois` event handler, which would
split up our code and introduce problems keeping state.

Our ``main()`` function is simple; it simply creates a ``MyBot`` object and
calls ``MyBot.run()``::

    async def main():
        mybot = MyBot()
        await mybot.run()

However, since our ``main()`` function is ``async``, we can't just call it
directly---we have to run it in an event loop. The simplest way to do this is
to use :func:`asyncio.run`::

    if __name__ == "__main__":
        asyncio.run(main())

Our finished bot now looks like this::

    from pyrcb2 import IRCBot, Event
    import asyncio


    class MyBot:
        def __init__(self):
            self.bot = IRCBot(log_communication=True)
            self.bot.load_events(self)

        async def run(self):
            async def init():
                await self.bot.connect("irc.example.com", 6667)
                await self.bot.register("mybot")
                # More code here (optional)...
            await self.bot.run(init())

        @Event.privmsg
        async def on_privmsg(self, sender, channel, message):
            if channel is None:
                # Message was sent in a private query.
                self.bot.privmsg(sender, "You said: " + message)
                return

            # Message was sent in a channel.
            self.bot.privmsg(channel, sender + " said: " + message)

        @Event.join
        async def on_join(self, sender, channel):
            if sender == self.nickname:
                # Don't do anything if this bot is the user who joined.
                return
            self.privmsg(channel, sender + " has joined " + channel)

            # This will execute a WHOIS request for `sender` and will
            # block until the request is complete. Since this is a
            # coroutine, the rest of the bot won't freeze up.
            result = await self.bot.whois(sender)
            if result.success:
                whois_reply = result.value
                server = whois_reply.server or "an unknown server"
                msg = sender + " is connected to " + server
                self.privmsg(channel, msg)


    async def main():
        mybot = MyBot()
        await mybot.run()


    if __name__ == "__main__":
        asyncio.run(main())

If we run our bot, it will work like this in channels:

.. code-block:: none

   [#mybot] --> mybot has joined #mybot
   [#mybot] --> user1234 has joined #mybot
   [#mybot] <mybot> user1234 has joined #mybot
   <a few seconds later...>
   [#mybot] <mybot> user1234 is connected to xyz.example.com
   [#mybot] <user1234> Test message
   [#mybot] <mybot> user1234 said: Test message

And it will work like this in private queries:

.. code-block:: none

   [query] <user1234> Test message
   [query] <mybot> You said: Test message

.. seealso::

   :doc:`reference/index`
      Complete API documentation for pyrcb2.

   :doc:`custom-commands`
      An introduction to sending and handling custom commands.

   `Examples`__
      Example pyrcb2 bots.

   `examples/skeleton.py`__
      A basic template for pyrcb2 bots.

   `examples/example.py`__
      The finished bot created above.

__ https://github.com/taylordotfish/pyrcb2/tree/0.6.2/examples/
__ https://github.com/taylordotfish/pyrcb2/blob/0.6.2/examples/skeleton.py
__ https://github.com/taylordotfish/pyrcb2/blob/0.6.2/examples/example.py
