Write your own modules

Have you created a module that you would like to submit to BudaBot? Post it here, and it might make it into the next release.
Captank
Member
Posts: 77
Joined: Sun Sep 09, 2012 6:36 pm
antispam: Rimor
Location: irc.funcom.com #budabot

Write your own modules

Postby Captank » Wed May 15, 2013 11:59 am

(ToDo list: all events, rest of annotations, link list, better formatting, make it in different posts to create anchors)
From time to time there are questions related to the massive API changes between v2.3 and v3.0 and people get confused by some stuff. So I try to explain the basics.

I'll explain how I do it, other people may have other ways to code.

make your environment
If you are running your bots on your local machine, better don't use same database nor the same install, so what ever you fuck up doesn't matter. Use a clean install.

In your budabot directory create another directory for your modules you work on. I named mine 'proprietary'.

If you don't have a test bot, create one.

Now its time to create the configs. You should create your test bot config twice, once for sqlite and once for mysql.

Code: Select all

cd ~/budadev/
./chatbot.sh testsqlite
./chatbot.sh testmysql


Run for both bots through the setup, modules should be enabled by default.

The config files need an edit. The following code you will find in budadev/conf/testsqlite.php and budadev/conf/testmysql.php on line 61-63:

Code: Select all

   $vars['module_load_paths'] = array(
      './modules'
   );


There we define your extra module folder. Modify the code like this:

Code: Select all

   $vars['module_load_paths'] = array(
      './modules', './proprietary'
   );


That it was.

create a module
Okay, the environment is set, now we create an example module called BLOB_MODULE.
The use of the module is to create and save blob messages and spam them on demand.
There for we will use 3 commands !createblob, !deleteblob and !blob
!createblob will obviously create your blob and stores it in database,
!deleteblob for deleting them again and
!blob, which will be more complex, !blob will give you a list of your blobs, !blobs 'id' will spam a blob

So first we create the directory ./budadev/proprietary/BLOB_MODULE/
In this directory all our files go, you may want to initialize any version control or what ever.

The module contains 3 files, BlobController.class.php, blob.sql and blob.txt

blob.sql
This file is for creating the database tables, etc.

Code: Select all

CREATE TABLE IF NOT EXISTS `blobs` (`id` INT PRIMARY KEY AUTO_INCREMENT, `owner` VARCHAR(25), `title` VARCHAR(50), `content` VARCHAR(500));


blob.txt
This file is the help file, I don't bother to explain it more detailed, just check how other modules do it.

BlobController.class.php
This is the most important part of your module, since its the logic. First of all here an empty template:

Code: Select all

<?php
/**
 * Authors:
 *  - OneWhoIsNotSober
 *
 * @Instance
 */
class BlobController {
   public $moduleName;
   /**
    * @Setup
    */
   public function setup() {
      
   }
}


The most confusing part will be those 'weird' @blabla things. Those are called annotations, PHP itself doesnt support annotations, but is using a library + annotations in comments as work around. Some of these are very important (data for commands, events, etc) some of them aren't needed (annotations to create documentations)

Annotations
@Instance

Code: Select all

<?php
/*
 * @Instance
 */
class BlobController {

This annotation is needed, that the bot will create an instance of your class.

@Setup

Code: Select all

   /**
    * @Setup
    */
   public function setup() {

The following function will be executed when its time to initialize the modules. Think of it as it were a constructor.

@Inject

Code: Select all

   /** @Inject */
   public $db;

This annotation is quite important. Its like the interface to the API, if you need to mess with the database you need to get an DB object, if you have to bother with the different access ranks you need an initialized object of AccessManager (which you will find in budadev/core/AccessManager.class.php), so you inject it. The system knows from the name of the class member ($db, $accessManager, etc) what it has to inject into your controller.

@DefineCommand

Code: Select all

<?php
/**
 * @Instance
 * @DefineCommand(
 *    command     = 'createblob',
 *   accessLevel = 'all',
 *   description = 'creates and saves blob',
 *   help        = 'blob.txt'
 * )
 */
class BlobController

The @DefineCommand annotation registeres your command, defines the default access level, gives a tiny description and points to the help file for that command.

@HandlesCommand($cmd) and @Matches($regexp)

Code: Select all

   /**
    * @HandlesCommand("createblob")
    * @Matches("/^createblob ([a-z]+) (.+)$/i")
    */
   public function blobCommand($message, $channel, $sender, $sendto, $args) {

@HandlesCommand and @Matches are two annotations that don't make sense if they stand alone. @HandlesCommand is used to say that the following @Matches are related to the command $cmd (createblob) and the following function is a handler for the command.
One or more @Matches define the regular expression(s) to match for the command. Note that the value in the @HandlesCommand should match the first part of the @Matches value.
$message is the whole message the $sender wrote, $channel represents the channel the message was written in, $sender represents the character who wrote the message, $sendto is an object for the response (basicly you only use it like $sendto->reply($msg)) and $args represents the argumentlist (preg_match($regexp, $message, $args); )
A command can have multiple command handlers.

@Event($event) and @Description($desc)

Code: Select all

   /**
    * @Event("joinPriv")
    * @Description("blablah")
    */
   public function doNothingOnPrivJoin($eventObj) {}

@Event($type) defines that the following function is an event handler for event type $type. There are different event types: joinPriv, logOn, logOff, etc and a time interval event, there for you pass the the interval time in budatime format, for example @Event("1sec").

@param and @return
They are for documentation generation, I recommend to always add that information, you don't to re-read every line of your code just to change the tiniest bits if you haven't touched the module for some month.

back to BLOB_MODULE
Okay the knowledge about annotations is now more or less in your head. Time to get back to the module.

So first of all we need to let the system know that we want BlobController instanced and we want to register the commands:

Code: Select all

<?php
/**
 * Authors:
 *  - OneWhoIsNotSober
 *
 * @Instance
 *
 * @DefineCommand(
 *   command     = 'createblob',
 *   accessLevel = 'all',
 *   description = 'create a blob',
 *   help        = 'blob.txt'
 * )
 * @DefineCommand(
 *   command     = 'deleteblob',
 *   accessLevel = 'all',
 *   description = 'delete a blob',
 *   help        = 'blob.txt'
 * )
 * @DefineCommand(
 *   command     = 'blob',
 *   accessLevel = 'all',
 *   description = 'work with blobs',
 *   help        = 'blob.txt'
 * )
 */
class BlobController {
   public $moduleName;


Which API function does the module require?
core/DB obviously for database and core/Text will help us to create the blob and chat commands.

Code: Select all

   
   /** @Inject */
   public $db;
   
   /** @Inject */
   public $text;
   


Setup. Do we need to do something? Yeah, we do. We need to load our blob.sql file, the system is handling this nice, it only loads and executes the sql file if it changed anyhow (unless we force the system to reload it)
So we can simply load it on every startup.

Code: Select all

   /**
    * @Setup
    */
   public function setup() {
      $this->db->loadSQLFile($this->moduleName, "blob");
   }


So, you might want to start implementing the commands now, but I say NO! In my opinion its better to have some functions that actually do the work and only those you call in your command handlers (even easier to test, because you could call those in the setup function, or adding new features)

So we need a function to create blobs - store them in DB, blobs have 4 information, the id which is unique for all blobs, the owner, the blob title and the blob content. The id field is auto incremented so we just need 3 parameters.

Code: Select all

   /**
    * Store the blob in database.
    *
    * @param string $owner - owner of the blob
    * @param string $title - blob title
    * @param string $content - blob content
    */
   public function createBlob($owner, $title, $content) {
      $sql = <<<EOD
INSERT INTO
   `blobs`
   (`owner`, `title`, `content`)
VALUES
   (?, ?, ?)
EOD;
      $this->db->exec($sql, strtolower($owner), $title, $content);
   }


Next will be to delete a blob.

Code: Select all

   /**
    * Delete a blob.
    *
    * @param int $bid - the id of the blob
    */
   public function deleteBlob($bid) {
      $sql = <<<EOD
DELETE FROM
   `blobs`
WHERE
   `id` = ?
EOD;
      $this->db->exec($sql, $bid);
   }


We need two functions to get blob data, one to get the blob by id (optional: + owner) and another to get all blobs by owner:

Code: Select all

   /**
    * Get data of a specific blob.
    *
    * @param int $bid - the blob id
    * @param string $owner - the owner, optional, default = null
    * @return DBRow - the resuling row, null if doesnt exist
    */
   public function getBlob($bid, $owner = null) {
      $sql = <<<EOD
SELECT
   `id`,`owner`,`title`,`content`
FROM
   `blobs`
WHERE
   `id` = ?
EOD;
      if($owner) {
         $sql .= " AND `owner` = ?";
      }
      $sql .= " LIMIT 1;";
      if($owner) {
         $result = $this->db->query($sql, $bid, strtolower($owner));
      }
      else {
         $result = $this->db->query($sql, $bid);
      }
      return count($result) ? $result[0] : null;
   }
   
   /**
    * Get blobs by owner.
    *
    * @param string $owner - owner of blobs
    * @return array - DBRow array of blobs related to $owner
    */
   public function getBlobsByOwner($owner) {
      $sql = <<<EOD
SELECT
   `id`,`owner`,`title`,`content`
FROM
   `blobs`
WHERE
   `owner` = ?
EOD;
      return $this->db->query($sql, strtolower($owner));
   }


The last 4 functions are like our module API, if another module we/anyone else writes which needs to interact with the blob stuff, its enough to interact with this functions instead of writing sql queries on their own and have to check every update if any field got renamed or what ever.

Time to implement the command handlers.

Again first one to create the blobs.
The regular expressions should say on there own how to use the command.

Code: Select all

   /**
    * Handler to create new blobs.
    *
    * @HandlesCommand("createblob")
    * @Matches("/^createblob ([a-z]+) (.+)$/i")
    * @Matches("/^createblob '([^']+)' (.+)$/i")
    * @Matches('/^createblob "([^"]+)" (.+)$/i')
    */
   public function createCommand($message, $channel, $sender, $sendto, $args) {
      $this->createBlob($sender, $args[1], $args[2]);
      $sendto->reply("Blob <highlight>{$args[1]}<end> created.");
   }


Now deleting blobs.

Code: Select all

   /**
    * Handler to delete blobs.
    *
    * @HandlesCommand("deleteblob")
    * @Matches("/^deleteblob (\d+)$/i")
    */
   public function deleteCommand($message, $channel, $sender, $sendto, $args) {
      if($blob = $this->getBlob($args[1], $sender)) {
         $this->deleteBlob($blob->id);
         $msg = "Blob <highlight>{$blob->title}<end> deleted.";
      }
      else {
         $msg = "Error! Blob #{$args[1]} either doesn't exist or isn't your blob!";
      }
      $sendto->reply($msg);
   }


The blob command is splitted into more handlers, first of all the !blob command to spam a blob:

Code: Select all

   /**
    * Handler to spam a blob.
    *
    * @HandlesCommand("blob")
    * @Matches("/^blob (\d+)$/i")
    */
   public function spamCommand($message, $channel, $sender, $sendto, $args) {
      if($blob = $this->getBlob($args[1])) {
         $msg = $this->text->make_blob($blob->title, $blob->content);
      }
      else {
         $msg = "Error! Blob #{$args[1]} doesn't exist!";
      }
      $sendto->reply($msg);
   }


Now to list your blobs:

Code: Select all

   /**
    * Handler to spam a blob.
    *
    * @HandlesCommand("blob")
    * @Matches("/^blob$/i")
    */
   public function listCommand($message, $channel, $sender, $sendto, $args) {
      $blobs = $this->getBlobsByOwner($sender);
      if($c = count($blobs)) {
         $msg = Array();
         foreach($blobs as $blob) {
            $msg[] = sprintf("<tab>Blob #%d: %s", $blob->id, $this->text->make_chatcmd($blob->title, "/tell <myname> blob ".$blob->id));
            $msg = $this->text->make_blob("Blobs ($c)", implode("<br>", $msg));
         }
      }
      else {
         $msg = "No blobs listed for you.";
      }
      $sendto->reply($msg);
   }
}


That it was, its done \o/
Here the entire file BlobController.class.php:

Code: Select all

<?php
/**
 * Authors:
 *  - OneWhoIsNotSober
 *
 * @Instance
 *
 * @DefineCommand(
 *   command     = 'createblob',
 *   accessLevel = 'all',
 *   description = 'create a blob',
 *   help        = 'blob.txt'
 * )
 * @DefineCommand(
 *   command     = 'deleteblob',
 *   accessLevel = 'all',
 *   description = 'delete a blob',
 *   help        = 'blob.txt'
 * )
 * @DefineCommand(
 *   command     = 'blob',
 *   accessLevel = 'all',
 *   description = 'work with blobs',
 *   help        = 'blob.txt'
 * )
 */
class BlobController {
   public $moduleName;
   
   /** @Inject */
   public $db;
   
   /** @Inject */
   public $text;
   
   /**
    * @Setup
    */
   public function setup() {
      $this->db->loadSQLFile($this->moduleName, "blob");
   }

   /**
    * Store the blob in database.
    *
    * @param string $owner - owner of the blob
    * @param string $title - blob title
    * @param string $content - blob content
    */
   public function createBlob($owner, $title, $content) {
      $sql = <<<EOD
INSERT INTO
   `blobs`
   (`owner`, `title`, `content`)
VALUES
   (?, ?, ?)
EOD;
      $this->db->exec($sql, strtolower($owner), $title, $content);
   }
   
   /**
    * Delete a blob.
    *
    * @param int $bid - the id of the blob
    */
   public function deleteBlob($bid) {
      $sql = <<<EOD
DELETE FROM
   `blobs`
WHERE
   `id` = ?
EOD;
      $this->db->exec($sql, $bid);
   }
   
   /**
    * Get data of a specific blob.
    *
    * @param int $bid - the blob id
    * @param string $owner - the owner, optional, default = null
    * @return DBRow - the resuling row, null if doesnt exist
    */
   public function getBlob($bid, $owner = null) {
      $sql = <<<EOD
SELECT
   `id`,`owner`,`title`,`content`
FROM
   `blobs`
WHERE
   `id` = ?
EOD;
      if($owner) {
         $sql .= " AND `owner` = ?";
      }
      $sql .= " LIMIT 1;";
      if($owner) {
         $result = $this->db->query($sql, $bid, strtolower($owner));
      }
      else {
         $result = $this->db->query($sql, $bid);
      }
      return count($result) ? $result[0] : null;
   }
   
   /**
    * Get blobs by owner.
    *
    * @param string $owner - owner of blobs
    * @return array - DBRow array of blobs related to $owner
    */
   public function getBlobsByOwner($owner) {
      $sql = <<<EOD
SELECT
   `id`,`owner`,`title`,`content`
FROM
   `blobs`
WHERE
   `owner` = ?
EOD;
      return $this->db->query($sql, strtolower($owner));
   }
   
   /**
    * Handler to create new blobs.
    *
    * @HandlesCommand("createblob")
    * @Matches("/^createblob ([a-z]+) (.+)$/i")
    * @Matches("/^createblob '([^']+)' (.+)$/i")
    * @Matches('/^createblob "([^"]+)" (.+)$/i')
    */
   public function createCommand($message, $channel, $sender, $sendto, $args) {
      $this->createBlob($sender, $args[1], $args[2]);
      $sendto->reply("Blob <highlight>{$args[1]}<end> created.");
   }
   
   /**
    * Handler to delete blobs.
    *
    * @HandlesCommand("deleteblob")
    * @Matches("/^deleteblob (\d+)$/i")
    */
   public function deleteCommand($message, $channel, $sender, $sendto, $args) {
      if($blob = $this->getBlob($args[1], $sender)) {
         $this->deleteBlob($blob->id);
         $msg = "Blob <highlight>{$blob->title}<end> deleted.";
      }
      else {
         $msg = "Error! Blob #{$args[1]} either doesn't exist or isn't your blob!";
      }
      $sendto->reply($msg);
   }
   
   /**
    * Handler to spam a blob.
    *
    * @HandlesCommand("blob")
    * @Matches("/^blob (\d+)$/i")
    */
   public function spamCommand($message, $channel, $sender, $sendto, $args) {
      if($blob = $this->getBlob($args[1])) {
         $msg = $this->text->make_blob($blob->title, $blob->content);
      }
      else {
         $msg = "Error! Blob #{$args[1]} doesn't exist!";
      }
      $sendto->reply($msg);
   }
   
   /**
    * Handler to list blobs.
    *
    * @HandlesCommand("blob")
    * @Matches("/^blob$/i")
    */
   public function listCommand($message, $channel, $sender, $sendto, $args) {
      $blobs = $this->getBlobsByOwner($sender);
      if($c = count($blobs)) {
         $msg = Array();
         foreach($blobs as $blob) {
            $msg[] = sprintf("<tab>Blob #%d: %s", $blob->id, $this->text->make_chatcmd($blob->title, "/tell <myname> blob ".$blob->id));
            $msg = $this->text->make_blob("Blobs ($c)", implode("<br>", $msg));
         }
      }
      else {
         $msg = "No blobs listed for you.";
      }
      $sendto->reply($msg);
   }
}

Return to “User Modules”

Who is online

Users browsing this forum: No registered users and 1 guest