App-RoboBot

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

doc/intro/index.rst
doc/lang/expr/index.rst
doc/lang/index.rst
doc/lang/scopes/index.rst
doc/lang/types/index.rst
doc/upgrade/index.rst
lib/App/RoboBot.pm
lib/App/RoboBot/Channel.pm
lib/App/RoboBot/Config.pm
lib/App/RoboBot/Doc.pm
lib/App/RoboBot/Macro.pm
lib/App/RoboBot/Message.pm
lib/App/RoboBot/Network.pm
lib/App/RoboBot/Network/IRC.pm
lib/App/RoboBot/Network/Mattermost.pm
lib/App/RoboBot/Network/Slack.pm
lib/App/RoboBot/NetworkFactory.pm
lib/App/RoboBot/Nick.pm
lib/App/RoboBot/Parser.pm
lib/App/RoboBot/Plugin.pm
lib/App/RoboBot/Plugin/API/Github.pm

MANIFEST  view on Meta::CPAN

lib/App/RoboBot/Plugin/Bot/IRC.pm
lib/App/RoboBot/Plugin/Bot/Logging.pm
lib/App/RoboBot/Plugin/Bot/Message.pm
lib/App/RoboBot/Plugin/Bot/Output.pm
lib/App/RoboBot/Plugin/Bot/Redirection.pm
lib/App/RoboBot/Plugin/Core.pm
lib/App/RoboBot/Plugin/Core/Compare.pm
lib/App/RoboBot/Plugin/Core/Control.pm
lib/App/RoboBot/Plugin/Core/Help.pm
lib/App/RoboBot/Plugin/Core/Logic.pm
lib/App/RoboBot/Plugin/Core/Macro.pm
lib/App/RoboBot/Plugin/Core/Math.pm
lib/App/RoboBot/Plugin/Core/Regexp.pm
lib/App/RoboBot/Plugin/Core/Variables.pm
lib/App/RoboBot/Plugin/Fun/ExtMarkov.pm
lib/App/RoboBot/Plugin/Fun/Factoid.pm
lib/App/RoboBot/Plugin/Fun/FakeQuote.pm
lib/App/RoboBot/Plugin/Fun/Figlet.pm
lib/App/RoboBot/Plugin/Fun/Filter.pm
lib/App/RoboBot/Plugin/Fun/Fortune.pm
lib/App/RoboBot/Plugin/Fun/Gertz.pm

MANIFEST  view on Meta::CPAN

lib/App/RoboBot/Plugin/Types/Map.pm
lib/App/RoboBot/Plugin/Types/Set.pm
lib/App/RoboBot/Plugin/Types/String.pm
lib/App/RoboBot/Plugin/Types/Vector.pm
lib/App/RoboBot/Response.pm
lib/App/RoboBot/Test/Mock.pm
lib/App/RoboBot/Type.pm
lib/App/RoboBot/Type/Expression.pm
lib/App/RoboBot/Type/Function.pm
lib/App/RoboBot/Type/List.pm
lib/App/RoboBot/Type/Macro.pm
lib/App/RoboBot/Type/Map.pm
lib/App/RoboBot/Type/Number.pm
lib/App/RoboBot/Type/Set.pm
lib/App/RoboBot/Type/String.pm
lib/App/RoboBot/Type/Symbol.pm
lib/App/RoboBot/Type/Vector.pm
lib/App/RoboBot/TypeFactory.pm
share/Vagrantfile
share/migrations/deploy/base.sql
share/migrations/deploy/p-achievements-20161128144040.sql

README.md  view on Meta::CPAN

members, or to hook into message and response parsing phases before and after
expression evaluation.

RoboBot itself handles all the work of parsing input, passing arguments between
functions, enforcing access restrictions, and properly formatting and limiting
its output back to channels or individual recipients. Plugin authors need only
focus on the implementation details of their specific functions.

Refer to the section *Developing Plugins* below for more details.

### Macros

RoboBot provides a basic evaluation-phase macro system, which permits any
authorized users to extend the functionality of RoboBot directly from channels
without having to author a plugin. Macros can invoke functions or other macros.
Macros can even define other macros.

```
(defmacro add-one [n] '(+ n 1))
(add-one 5)
6
```

### Message Variables

The Variables plugin provides functions for setting and unsetting variables in

doc/cookbook/index.rst  view on Meta::CPAN

.. include:: ../common.defs

.. _ch-cookbook:

Macro Cookbook
**************

This section provides examples for how to extend |RB| functionality on the fly
through the use of macros. Some of these examples are from real-world usage and
others are contrived examples. But all will hopefully contribute to aiming |RB|
users in the right direction when they choose to add their own functionality.

Thinge Macros
=============

The :ref:`module-fun-thinge` plugin makes it easy for users to store little
snippets of information -- URLs, quotes, jokes, and just about anything else
that will fit into a chat message -- and recall them later. New categories can
be added at any time. But the default behavior requires everyone to do a little
more typing than perhaps they really need to.

A pattern used by one instance of |RB| establishes the use of a macro-defining
macro. A master macro is used which takes the name of a new type of thinge that
users might want to start saving/retrieving, and that macro creates another
macro tailor-made for that new type of thinge.

Make-Thinge Macro
-----------------

This master macro is created which takes a single argument: the name of a type
of thinge for which to create a new convenience macro:

.. code-block:: clojure

    (defmacro make-thinge [ttype]
      '(defmacro ttype [&optional subcmd &rest vargs]
        '(cond

doc/lang/types/index.rst  view on Meta::CPAN


Functions are invoked by using them as the leading atom of a list (thus making
that list an expression), as so::

    (my-cool-function "foo")
    "I just did something with foo!"

Which invokes the ``my-cool-function`` function, providing it with a single
string argument of ``foo``. Some functions take functions as their arguments

Macros
======

Macros bear striking resemblance in their operation to functions, with the most
distinguishing feature being that macros are user-defined by anyone on your
connected chat networks with access to the :ref:`function-core-macro-defmacro`
function. They exist within the :ref:`lang-scope-network`, and may be updated
and invoked at will using the same, fundamental S-expression syntax used
everywhere else. For example::

    (defmacro my-cool-macro [input-string]
        '(format "I just did something with %s!" input-string))
    (my-cool-macro "foo")
    "I just did something with foo!"

lib/App/RoboBot.pm  view on Meta::CPAN

incoming messages, and sending messages.

=item * Multi-network

Bot instances created with App::RoboBot may connect to multiple networks
simultaneously (critical for some plugins like ChannelLink which let you create
your own bridges between disparate networks), even across different protocols.
The only practical limits are memory and bandwidth for the host running your
bot.

=item * Macros

User-defined macros are core to App::RoboBot's operation and allow authorized
users on your chat services to define new functionality for the bot on the fly
using a Lisp-like (emphasis on the "like") language. Macros can invoke
functions, other macros, and even create more macros. Macros use the exact same
S-Expression language as everything else in the bot, and have access to the
full functionality.

=item * Plugins

Nearly all App::RoboBot functionality is provided through the plugin system.
The distribution ships with many plugins already included, from interfaces to
external programs like fortune and filters, all the way through to HTTP clients
and XML parsing and XPath queries. New plugins may be submitted to the core
App::RoboBot project, or distributed separately.

lib/App/RoboBot.pm  view on Meta::CPAN

    # that (it having been done already in the first phase). This is critical
    # for plugins which use things like App::RoboBot::Parser to parse stored
    # expressions.
    foreach my $plugin (@{$self->plugins}) {
        $plugin->post_init($self);
    }

    $logger->debug('Plugin post-initialization hooks finished.');

    # Pre-load all saved macros
    $self->macros({ App::RoboBot::Macro->load_all($self) });
    # TODO: This is an awful hack around the fact that nested macros get parsed incorrectly
    #       the first time around, depending on their load order out of the database. The
    #       Parser module doesn't know about their name yet, so it parses them as a String
    #       instead of a Macro object. That should get fixed in a cleaner way, but for now
    #       we can just load them a second time. All their names will be available for the
    #       Parser and we'll just overwrite their definitions with the correct versions.
    $self->macros({ App::RoboBot::Macro->load_all($self) });

    $logger->debug('Macro initializations finished.');
}

sub run {
    my ($self) = @_;

    my $logger = $self->logger('core.run');

    $logger->info('Bot starting.');

    my $c = AnyEvent->condvar;

lib/App/RoboBot.pm  view on Meta::CPAN

}

sub add_macro {
    my ($self, $network, $nick, $macro_name, $args, $body) = @_;

    my $logger = $self->logger('core.macro');

    $logger->debug(sprintf('Adding macro %s for %s on %s network.', $macro_name, $nick->name, $network->name));

    if (exists $self->macros->{$network->id}{$macro_name}) {
        $logger->debug('Macro already exists. Overwriting definition.');
        $self->macros->{$network->id}{$macro_name}->name($macro_name);
        $self->macros->{$network->id}{$macro_name}->arguments($args);
        $self->macros->{$network->id}{$macro_name}->definition($body);
        $self->macros->{$network->id}{$macro_name}->definer($nick);

        return unless $self->macros->{$network->id}{$macro_name}->save;
    } else {
        $logger->debug('Creating as new macro and saving definition.');
        my $macro = App::RoboBot::Macro->new(
            bot        => $self,
            network    => $network,
            name       => $macro_name,
            arguments  => $args,
            definition => $body,
            definer    => $nick,
        );

        return unless $macro->save;
        $logger->debug('Macro saved successfully. Caching definition for future use.');

        $self->macros->{$network->id} = {} unless exists $self->macros->{$network->id};
        $self->macros->{$network->id}{$macro->name} = $macro;
    }

    return 1;
}

sub remove_macro {
    my ($self, $network, $macro_name) = @_;

    my $logger = $self->logger('core.macro');

    $logger->debug(sprintf('Removing macro %s on %s network.', $macro_name, $network->name));

    return unless exists $self->macros->{$network->id}{$macro_name};

    $self->macros->{$network->id}{$macro_name}->delete;
    delete $self->macros->{$network->id}{$macro_name};

    $logger->debug('Macro successfully removed.');

    return 1;
}

sub network_by_id {
    my ($self, $network_id) = @_;

    return undef unless defined $network_id && $network_id =~ m{^\d+$};
    return (grep { $_->id == $network_id } @{$self->networks})[0] || undef;
}

lib/App/RoboBot/Macro.pm  view on Meta::CPAN

package App::RoboBot::Macro;
$App::RoboBot::Macro::VERSION = '4.004';
use v5.20;

use namespace::autoclean;

use Moose;
use MooseX::ClassAttribute;
use MooseX::SetOnce;

use App::RoboBot::Nick;
use App::RoboBot::Parser;

lib/App/RoboBot/Macro.pm  view on Meta::CPAN


sub BUILD {
    my ($self) = @_;

    $self->log($self->bot->logger('core.macro')) unless $self->has_logger;

    $self->log->debug(sprintf('Creating new macro object for %s on network %s.', $self->name, $self->network->name));

    $self->_generate_expression($self->definition) if defined $self->definition;

    $self->log->debug(sprintf('Macro expression generated from definition for %s.', $self->name));
}

around 'definition' => sub {
    my $orig = shift;
    my $self = shift;

    return $self->$orig() unless @_;

    my $def = shift;

    $self->log->debug(sprintf('Macro definition updating for %s on network %s.', $self->name, $self->network->name));

    $self->_generate_expression($def);
    return $self->$orig($def);
};

sub _generate_expression {
    my ($self, $def) = @_;

    $self->log->debug(sprintf('Generating expression from %s macro definition.', $self->name));

lib/App/RoboBot/Macro.pm  view on Meta::CPAN


        $self->_set_expression([]);
        return;
    }

    $self->log->debug('Creating expression parser.');

    my $parser = App::RoboBot::Parser->new( bot => $self->bot );
    my $expr = $parser->parse($def);

    $self->log->debug('Macro definition body parsed.');

    unless (defined $expr && blessed($expr) =~ m{^App::RoboBot::Type}) {
        $self->log->debug('Parse results yielded invalid expression. Marking macro as invalid.');

        $self->_set_valid(0);
        $self->_set_error("Macro definition body must be a valid expression or list.");
        return;
    }

    $self->log->debug('Valid expression generated. Marking macro valid and setting internal expression.');

    $self->_set_valid(1);
    $self->_set_expression($expr);
};

sub load_all {
    my ($class, $bot) = @_;

    my $logger = $bot->logger('core.macro');

    $logger->debug('Macro load_all() request.');

    my $res = $bot->config->db->do(q{
        select m.macro_id, m.network_id, m.name, m.arguments, m.definition,
            n.name as nick, m.defined_at, m.is_locked
        from macros m
            join nicks n on (n.id = m.defined_by)
    });

    return unless $res;

lib/App/RoboBot/Macro.pm  view on Meta::CPAN

}

sub save {
    my ($self) = @_;

    $self->log->debug(sprintf('Save request for macro %s on network %s.', $self->name, $self->network->name));

    my $res;

    if ($self->has_id) {
        $self->log->debug(sprintf('Macro %s already has ID (%d). Updating existing record.', $self->name, $self->id));

        $res = $self->bot->config->db->do(q{
            update macros set ??? where macro_id = ?
        }, {
            name       => $self->name,
            arguments  => encode_json($self->arguments),
            definition => $self->definition,
            is_locked  => $self->is_locked,
        }, $self->id);

        return 1 if $res;
    } else {
        $self->log->debug(sprintf('Macro %s does not have ID. Creating new record.', $self->name));

        unless ($self->has_definer) {
            $self->log->error(sprintf('Attempted to save macro %s on network %s without a definer attribute.', $self->name, $self->network->name));
            return 0;
        }

        $res = $self->bot->config->db->do(q{
            insert into macros ??? returning macro_id
        }, {
            name       => $self->name,

lib/App/RoboBot/Macro.pm  view on Meta::CPAN

        }
    }

    $self->log->error(sprintf('Save for macro %s on network %s failed.', $self->name, $self->network->name));
    return 0;
}

sub delete {
    my ($self) = @_;

    $self->log->debug(sprintf('Macro delete request for %s on network %s.', $self->name, $self->network->name));

    return 0 unless $self->has_id;

    $self->log->debug(sprintf('Removing macro record for ID %d.', $self->id));

    my $res = $self->bot->config->db->do(q{
        delete from macros where macro_id = ?
    }, $self->id);

    return 0 unless $res;

    $self->log->debug('Record deletion successful.');
    return 1;
}

sub lock {
    my ($self) = @_;

    $self->log->debug(sprintf('Macro lock request for %s on network %s.', $self->name, $self->network->name));

    return 0 if $self->is_locked;

    $self->is_locked(1);
    return $self->save;
}

sub unlock {
    my ($self) = @_;

    $self->log->debug(sprintf('Macro unlock request for %s on network %s.', $self->name, $self->network->name));

    return 0 if ! $self->is_locked;

    $self->is_locked(0);
    return $self->save;
}

sub expand {
    my ($self, $message, $rpl, @args) = @_;

    $self->log->debug(sprintf('Macro expansion for %s on network %s (%d arguments).', $self->name, $self->network->name, scalar(@args)));

    my $expr = clone($self->expression);

    $self->log->debug('Macro expression cloned.');

    my $req_count = scalar( grep { $_->{'optional'} != 1 } @{$self->arguments->{'positional'}} ) // 0;
    if ($req_count > 0 && scalar(@args) < $req_count) {
        $self->log->error(sprintf('Macro expansion received incorrect number of arguments (expected %d, got %d).', $req_count, scalar(@args)));

        $message->response->raise('Macro %s expects at least %d arguments, but you provided %d.', $self->name, $req_count, scalar(@args));
        return;
    }

    # TODO: Add a first pass to collect any &key'ed arguments first, before
    #       processing the simple positional ones. Possibly needs to be done
    #       even before the argument count check above is performed.
    foreach my $arg (@{$self->arguments->{'positional'}}) {
        # No need to care whether argument is required or not at this point.
        # We would have already errored out above if there was a mismatch. Just
        # set the optional ones without values to undefined.
        $rpl->{$arg->{'name'}} = @args ? shift(@args) : undef;
    }
    # If anything is left in the arguments list passed to the macro invocation,
    # then it belongs in &rest, should the macro care to make use of them.
    if ($self->arguments->{'rest'} && @args) {
        # TODO: Array support in variables still needs work. For now, join all
        #       remaining values from &rest into a single space-delim string.
        $rpl->{ $self->arguments->{'rest'} } = join(' ', @args);
    }

    $self->log->debug('Macro arguments constructed. Preparing to evaluate.');

    return $expr->evaluate($message, $rpl);
}

sub signature {
    my ($self) = @_;

    $self->log->debug(sprintf('Generating macro signature for %s on network %s.', $self->name, $self->network->name));

    my @arg_list = ();

lib/App/RoboBot/Parser.pm  view on Meta::CPAN


    $self->log->debug(sprintf('Beginning list read with terminator %s.', $terminator));

    my $l = [];

    while (defined (my $c = $self->_read_char)) {
        if ($c eq $terminator) {
            if ($terminator eq ')') {
                if (@{$l} > 0 && ref($l->[0]) && $l->[0]->type eq 'Function') {
                    return $self->tf->build('Expression', $l);
                } elsif (@{$l} > 0 && ref($l->[0]) && $l->[0]->type eq 'Macro') {
                    return $self->tf->build('Expression', $l);
                } else {
                    return $self->tf->build('List', $l);
                }
            } else {
                return $l;
            }
        } elsif ($c =~ m{[\s,]}) {
            next;
        } elsif ($c eq '(') {

lib/App/RoboBot/Parser.pm  view on Meta::CPAN

        if (substr($el, 0, 1) eq ':') {
            $self->log->debug(sprintf('Treating non-list element "%s" as Symbol.', $el));
            return $self->tf->build('Symbol', $el);
        } elsif (looks_like_number($el)) {
            $self->log->debug(sprintf('Treating non-list element "%s" as Number.', $el));
            return $self->tf->build('Number', $el);
        } elsif (exists $self->bot->commands->{lc($el)}) {
            $self->log->debug(sprintf('Treating non-list element "%s" as Function.', $el));
            return $self->tf->build('Function', $el);
        } elsif (exists $self->macros->{lc($el)}) {
            $self->log->debug(sprintf('Treating non-list element "%s" as Macro.', $el));
            return $self->tf->build('Macro', $el);
        } else {
            $self->log->debug(sprintf('Treating non-list element "%s" as String.', $el));
            return $self->tf->build('String', $el);
        }
    } else {
        $self->log->debug('Expected an element, but there turned out to be nothing to read.');
        return undef;
    }
}

lib/App/RoboBot/Plugin.pm  view on Meta::CPAN

        $self->log->debug(sprintf('Preprocessing %d arguments for command %s.', scalar(@args), $command));
        # TODO: There are much better ways of deciding how to pass a symbol
        #       that happens to have the name of a function as a function, or
        #       as a string, than this.
        my $pass_funcs = exists $self->commands->{$command}{'take_funcs'} && $self->commands->{$command}{'take_funcs'} == 1 ? 1 : 0;

        my @new_args;

        foreach my $arg (@args) {
            if (blessed($arg) && $arg->can('evaluate')) {
                if (($arg->type eq 'Function' || $arg->type eq 'Macro') && !$pass_funcs) {
                    push(@new_args, $arg->value);
                } else {
                    push(@new_args, $arg->evaluate($message, $rpl));
                }
            } else {
                push(@new_args, $arg);
            }
        }

        @args = @new_args;

lib/App/RoboBot/Plugin/Core/Control.pm  view on Meta::CPAN

    if (defined $fallback) {
        return $fallback->evaluate($message, $rpl);
    }

    return;
}

sub control_apply {
    my ($self, $message, $command, $rpl, $func, @args) = @_;

    unless (defined $func && blessed($func) =~ m{^App::RoboBot::Type::(Function|Macro)}) {
        $message->response->raise('You must provide a function or macro to apply to your arguments.');
        return;
    }

    unless (@args) {
        $message->response->raise('You cannot apply a function or macro to a non-existent list of arguments.');
        return;
    }

    # it's not an error to have no arguments, but we can at least short-circuit.

lib/App/RoboBot/Plugin/Core/Help.pm  view on Meta::CPAN

    }, $message->network->id);

    my @globals;
    if ($res) {
        while ($res->next) {
            push(@globals, $res->{'var_name'});
        }
    }

    $message->response->push(sprintf('*Functions*: %s', join(', ', @functions))) if @functions > 0;
    $message->response->push(sprintf('*Macros*: %s', join(', ', @macros))) if @macros > 0;
    $message->response->push(sprintf('*Globals*: %s', join(', ', @globals))) if @globals > 0;

    return;
}

sub general_help {
    my ($self, $message) = @_;

    my %plugins = (
        map { $_->ns => 1 }

lib/App/RoboBot/Plugin/Core/Macro.pm  view on Meta::CPAN

package App::RoboBot::Plugin::Core::Macro;
$App::RoboBot::Plugin::Core::Macro::VERSION = '4.004';
use v5.20;

use namespace::autoclean;

use Moose;
use MooseX::SetOnce;

use App::RoboBot::Macro;

use Data::Dumper;
use Scalar::Util qw( blessed );

extends 'App::RoboBot::Plugin';

=head1 core.macro

Provides functionality for defining and managing macros. Macros defined by this
plugin are available to all users in all channels on the current network, and
persist across bot restarts.

=cut

has '+name' => (
    default => 'Core::Macro',
);

has '+description' => (
    default => 'Provides functionality for defining and managing macros. Macros defined by this plugin are available to all users in all channels on the current network, and persist across bot restarts.',
);

=head2 defmacro

=head3 Description

Macros are user-defined functions, and any channel members with permission to
call ``(defmacro)`` may define their own macros. Name collisions between macros
and builtin functions are always resolved in favor of the functions.

Macro names may contain any valid identifier characters, including many forms
of punctuation and Unicode glyphs (as long as your chat network supports their
transmission). The primary restrictions on macro names are:

* They cannot begin with a colon ``:`` character (that is reserved for Symbols).

* They cannot contain whitespace.

* They cannot be a valid Numeric (have an optional leading dash, include a decimal separator, and otherwise consist only of numbers).

* They cannot contain a slash ``/`` as that is the function namespace separator.

lib/App/RoboBot/Plugin/Core/Macro.pm  view on Meta::CPAN

=head3 Usage

<macro name>

=cut

has '+commands' => (
    default => sub {{
        'defmacro' => { method          => 'define_macro',
                        preprocess_args => 0,
                        description     => 'Defines a new macro, or replaces an existing macro of the same name. Macros may call, or even create/modify/delete other macros.',
                        usage           => '<name> (<... argument list ...>) \'(<definition body list>)',
                        example         => "plus-one (a) '(+ a 1)" },

        'undefmacro' => { method      => 'undefine_macro',
                          description => 'Undefines an existing macro.',
                          usage       => '<name>' },

        'show-macro' => { method      => 'show_macro',
                          description => 'Displays the definition of a macro.',
                          usage       => '<name>' },

lib/App/RoboBot/Plugin/Core/Macro.pm  view on Meta::CPAN


    return @macros;
}

sub define_macro {
    my ($self, $message, $command, $rpl, $macro_name, $args, $def) = @_;

    my $network = $message->network;

    unless (defined $macro_name && defined $args && defined $def) {
        $message->response->raise('Macro definitions must consist of a name, a list of arguments, and a definition body list.');
        return;
    }

    # Get the actual stringy name.
    if ($macro_name->type eq 'Macro') {
        # We need to special-case this, as redefining an existing macro is going to have the
        # parser identify the name token in the (defmacro ...) call as the name of an
        # existing macro (and therefore type "Macro") instead of just a bare string.
        $macro_name = $macro_name->value;
    } elsif ($macro_name->type eq 'Function') {
        # We can short-circuit and reject macros named the same as a function right away.
        $message->response->raise('Macros cannot have the same name as a function.');
        return;
    } else {
        $macro_name = $macro_name->evaluate($message, $rpl);
    }

    # Enforce a few rules on macro names.
    unless (defined $macro_name && !ref($macro_name) && $macro_name =~ m{^[^\s\{\(\)\[\]\{\}\|,#]+$} && substr($macro_name, 0, 1) ne "'") {
        $message->response->raise('Macro name must be a single string value.');
        return;
    }

    if (exists $self->bot->macros->{$network->id}{lc($macro_name)} && $self->bot->macros->{$network->id}{lc($macro_name)}->is_locked) {
        if ($self->bot->macros->{$network->id}{lc($macro_name)}->definer->id != $message->sender->id) {
            $message->response->raise(
                'The %s macro has been locked by its creator (who happens to not be you) and cannot be redefined by anyone else.',
                $self->bot->macros->{$network->id}{lc($macro_name)}->name
            );
            return;
        }
    }

    unless (blessed($args) && ($args->type eq 'List' || $args->type eq 'Vector')) {
        $message->response->raise('Macro arguments must be specified as a list or vector.');
        return;
    }

    unless (blessed($def) && ($def->type eq 'List' || $def->type eq 'Expression') && $def->quoted) {
        $message->response->raise('Macro body definition must be a quoted expression or list.');
        return;
    }

    $def->quoted(0);

    # Work through the argument list looking for &optional (and maybe in the
    # future we'll do things like &key and friends), building up the arrayref
    # of hashrefs for our macro's arguments.
    my $args_def = {
        has_optional => 0,

lib/App/RoboBot/Plugin/Core/Macro.pm  view on Meta::CPAN

        return;
    }

    my $body;
    unless ($body = $def->flatten($rpl)) {
        $message->response->raise('Could not collapse macro definition.');
        return;
    }

    if ($self->bot->add_macro($message->network, $message->sender, $macro_name, $args_def, $body)) {
        $message->response->push(sprintf('Macro %s defined.', $macro_name));
    } else {
        $message->response->raise('Could not define macro %s.', $macro_name);
    }

    return;
}

sub undefine_macro {
    my ($self, $message, $command, $rpl, $macro_name) = @_;

    # For brevity below.
    my $network = $message->network;

    unless (defined $macro_name && $macro_name =~ m{\w+}o) {
        $message->response->raise('Must provide the name of a macro to undefine.');
        return;
    }

    unless (exists $self->bot->macros->{$network->id}{$macro_name}) {
        $message->response->raise('Macro %s has not been defined.', $macro_name);
        return;
    }

    if ($self->bot->macros->{$network->id}{$macro_name}->is_locked && $self->bot->macros->{$network->id}{$macro_name}->definer != $message->sender->id) {
        $message->response->raise(
            'The %s macro has been locked by its creator (who happens to not be you). You may not undefine it.',
            $self->bot->macros->{$network->id}{$macro_name}->name
        );
    } else {
        if ($self->bot->remove_macro($network, $macro_name)) {
            $message->response->push(sprintf('Macro %s undefined.', $macro_name));
        } else {
            $message->response->push(sprintf('Could not undefine macro %s.', $macro_name));
        }
    }

    return;
}

sub show_macro {
    my ($self, $message, $command, $rpl, $macro_name) = @_;

lib/App/RoboBot/Type.pm  view on Meta::CPAN

}

sub build_from_val {
    my ($class, $bot, $val, $quoted) = @_;

    $quoted //= 0;

    return unless defined $bot && defined $val;

    # If we're building a List or Expression, we need to downgrade to Strings
    # any operands that follow which are marked as Macros, since a macro must
    # always be the operator.
    if (ref($val) eq 'ARRAY') {
        foreach my $el (@{$val}[1..$#$val]) {
            next unless blessed($el) && $el->type eq 'Macro';
            $el = App::RoboBot::Type::String->new(
                bot    => $bot,
                value  => $el->value,
                quoted => $el->quoted,
            );
        }
    }

    return $class->new(
        bot    => $bot,

lib/App/RoboBot/Type/Expression.pm  view on Meta::CPAN


    return unless defined $message && $message->isa('App::RoboBot::Message');
    return unless $self->has_value;

    my @r;

    if (defined $self->value->[0] && $self->value->[0]->type eq 'Function') {
        my ($fn, @args) = @{$self->value};

        return $fn->evaluate($message, $rpl, @args);
    } elsif (defined $self->value->[0] && $self->value->[0]->type eq 'Macro') {
        my ($macro, @args) = @{$self->value};

        return $macro->evaluate($message, $rpl, @args);
    } else {
        return map {
            blessed($_) && $_->can('evaluate')
            ? $_->evaluate($message, $rpl)
            : $_
        } @{$self->value};
    }

lib/App/RoboBot/Type/Macro.pm  view on Meta::CPAN

package App::RoboBot::Type::Macro;
$App::RoboBot::Type::Macro::VERSION = '4.004';
use v5.20;

use namespace::autoclean;

use Moose;

use Scalar::Util qw( blessed );

extends 'App::RoboBot::Type';

has '+type' => (
    default => 'Macro',
);

has '+value' => (
    is        => 'rw',
    isa       => 'Str',
    required  => 1,
);

sub evaluate {
    my ($self, $message, $rpl, @args) = @_;

lib/App/RoboBot/TypeFactory.pm  view on Meta::CPAN

use namespace::autoclean;

use Moose;
use MooseX::ClassAttribute;

use Module::Loaded;

use App::RoboBot::Type::Expression;
use App::RoboBot::Type::Function;
use App::RoboBot::Type::List;
use App::RoboBot::Type::Macro;
use App::RoboBot::Type::Map;
use App::RoboBot::Type::Number;
use App::RoboBot::Type::Set;
use App::RoboBot::Type::String;
use App::RoboBot::Type::Symbol;
use App::RoboBot::Type::Vector;

has 'bot' => (
    is       => 'ro',
    isa      => 'App::RoboBot',

share/migrations/sqitch.plan  view on Meta::CPAN

p-auth-20161128164909 [base] 2016-11-28T16:49:10Z Jon Sime <jonsime@gmail.com> # Plugin: auth/permissions base tables
p-autoreply-20161128171050 [base] 2016-11-28T17:10:50Z Jon Sime <jonsime@gmail.com> # Plugin: autoreply base table
p-channellink-20161128173743 [base] 2016-11-28T17:37:43Z Jon Sime <jonsime@gmail.com> # Plugin: ChannelLink base table
p-factoids-20161128182038 [base] 2016-11-28T18:20:38Z Jon Sime <jonsime@gmail.com> # Plugin: factoids base table
p-fakequotes-20161128183237 [base] 2016-11-28T18:32:37Z Jon Sime <jonsime@gmail.com> # Plugin: FakeQuote base tables
p-github-20161128190436 [base] 2016-11-28T19:04:36Z Jon Sime <jonsime@gmail.com> # Plugin: Github watchers base tables
p-variables-20161128195027 [base] 2016-11-28T19:50:27Z Jon Sime <jonsime@gmail.com> # Plugin: Variables base table
p-karma-20161128200656 [base] 2016-11-28T20:06:57Z Jon Sime <jonsime@gmail.com> # Plugin: Karma base table
p-location-20161128204426 [base] 2016-11-28T20:44:26Z Jon Sime <jonsime@gmail.com> # Plugin: Location base table
p-logger-20161128205533 [base] 2016-11-28T20:55:33Z Jon Sime <jonsime@gmail.com> # Plugin: Logging base table
p-macros-20161128210159 [base] 2016-11-28T21:01:59Z Jon Sime <jonsime@gmail.com> # Plugin: Macros base table
p-madlibs-20161128211213 [base] 2016-11-28T21:12:14Z Jon Sime <jonsime@gmail.com> # Plugin: MadLibs base table
p-markov-20161128212720 [base] 2016-11-28T21:27:20Z Jon Sime <jonsime@gmail.com> # Plugin: Markov chain generator base tables
p-memos-20161128214110 [base] 2016-11-28T21:41:11Z Jon Sime <jonsime@gmail.com> # Plugin: Memos base table
p-net-http-20161128214753 [base] 2016-11-28T21:47:54Z Jon Sime <jonsime@gmail.com> # Plugin: net/http logging table
p-skills-20161128215019 [base] 2016-11-28T21:50:19Z Jon Sime <jonsime@gmail.com> # Plugin: Skills base tables
p-thinge-20161128223332 [base] 2016-11-28T22:33:33Z Jon Sime <jonsime@gmail.com> # Plugin: Thinge base tables
p-net-urls-20161128224203 [base] 2016-11-28T22:42:03Z Jon Sime <jonsime@gmail.com> # Plugin: net/urls title logging base table
p-voting-20161128224521 [base] 2016-11-28T22:45:21Z Jon Sime <jonsime@gmail.com> # Plugin: Voting base tables
p-skills-20161128231536 [p-skills-20161128215019] 2016-11-28T23:15:36Z Jon Sime <jonsime@gmail.com> # default selection of skill levels for Skills plugin



( run in 0.867 second using v1.01-cache-2.11-cpan-49f99fa48dc )