Full Code of jclg/php-slack-bot for AI

master 6631b71d2bb4 cached
15 files
32.8 KB
8.1k tokens
69 symbols
1 requests
Download .txt
Repository: jclg/php-slack-bot
Branch: master
Commit: 6631b71d2bb4
Files: 15
Total size: 32.8 KB

Directory structure:
gitextract_z_txds45/

├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── composer.json
└── src/
    └── PhpSlackBot/
        ├── ActiveMessenger/
        │   └── Push.php
        ├── Base.php
        ├── Bot.php
        ├── Command/
        │   ├── BaseCommand.php
        │   ├── CountCommand.php
        │   ├── DateCommand.php
        │   ├── PingPongCommand.php
        │   └── PokerPlanningCommand.php
        └── Webhook/
            ├── BaseWebhook.php
            └── OutputWebhook.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*~
.idea/
bot.php
composer.phar
composer.lock
vendor

================================================
FILE: .travis.yml
================================================
language: php
php:
  - '7.1'
before_script:
  # PHP_CodeSniffer
  - pear install pear/PHP_CodeSniffer
  - phpenv rehash
script:
  - phpcs --standard=PSR2 src/

================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2016 Jean-Charles Le Goff

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# PHP Slack Bot

A simple bot user written in PHP using the Slack Real Time Messaging API https://api.slack.com/rtm

## Installation
With Composer

    composer require jclg/php-slack-bot

## Usage

Create a php file called `bot.php` with the following content

```php
require 'vendor/autoload.php';
use PhpSlackBot\Bot;

// Custom command
class MyCommand extends \PhpSlackBot\Command\BaseCommand {

    protected function configure() {
        $this->setName('mycommand');
    }

    protected function execute($message, $context) {
        $this->send($this->getCurrentChannel(), null, 'Hello !');
    }

}

$bot = new Bot();
$bot->setToken('TOKEN'); // Get your token here https://my.slack.com/services/new/bot
$bot->loadCommand(new MyCommand());
$bot->loadInternalCommands(); // This loads example commands
$bot->run();
```

Then run `php bot.php` from the command line (terminal).

## Example commands

Example commands are located in `src/PhpSlackBot/Command/` and can be loaded with `$bot->loadInternalCommands();`

##### Ping Pong Command

Type `ping` in a channel and the bot should answer "Pong" to you.

##### Count Command

Type `count` several times in a channel and the bot should answer with 1 then 2...

##### Date Command

Type `date` in a channel and the current date.

##### Planning Poker Command

https://en.wikipedia.org/wiki/Planning_poker

Type `pokerp start` in a public channel with your team in order to start a planning poker session.

Direct message the bot with `pokerp vote number`. The bot will record your vote.

Type `pokerp status` to see the current status of the session (who has voted).

Type `pokerp end` in a public channel and the bot will output each vote.

## Load your own commands

You can load your own commands by implementing the \PhpSlackBot\Command\BaseCommand.

Then call PhpSlackBot\Bot::loadCommand method for each command you have to load.

## "Catch All" command

If you need to execute a command when an event occurs, you can set up a "catch all" command.

This special command will be triggered on all events.

```php
require 'vendor/autoload.php';
use PhpSlackBot\Bot;

// This special command executes on all events
class SuperCommand extends \PhpSlackBot\Command\BaseCommand {

    protected function configure() {
        // We don't have to configure a command name in this case
    }

    protected function execute($data, $context) {
        if ($data['type'] == 'message') {
            $channel = $this->getChannelNameFromChannelId($data['channel']);
            $username = $this->getUserNameFromUserId($data['user']);
            echo $username.' from '.($channel ? $channel : 'DIRECT MESSAGE').' : '.$data['text'].PHP_EOL;
        }
    }

}

$bot = new Bot();
$bot->setToken('TOKEN'); // Get your token here https://my.slack.com/services/new/bot
$bot->loadCatchAllCommand(new SuperCommand());
$bot->run();
```

## Incoming webhooks

The bot can also listen for incoming webhooks.

Commands are triggered from users messages inside Slack and webhooks are triggered from web post requests.

Custom webhooks can be loaded using the PhpSlackBot\Bot::loadWebhook method.

This is useful if you need to control the bot from an external service. For example, with IFTTT https://ifttt.com/maker

To enable webhooks, use the enableWebserver method before the run method.

You can also set a secret token to prevent unauthorized requests.


```php
$bot->loadInternalWebhooks(); // Load the internal "output" webhook
$bot->enableWebserver(8080, 'secret'); // This will listen on port 8080
$bot->run();
```

Then, use the parameter "name" to trigger the corresponding webhook :

```
curl -X POST --data-urlencode 'auth=secret' --data-urlencode 'name=output' --data-urlencode 'payload={"type" : "message", "text": "This is a message", "channel": "#general"}' http://localhost:8080
```

## Active Messaging

The example provided in the *usage section* above sets the bot to be reactive. The reactive bot is not capable of sending messages to any user on its own. It must be given an input to get a response from. In other words, the bot can only **react** to inputs given to it. 

*Active Messaging* means that as a developer, you would be able to **send messages to your users without them sending a message to the bot first**. It is useful when you have to notify your users about something e.g. a bot which can check a user's birthday and wish them, or tell them weather outside every 2 hours without them having to type in a command everytime. There can be many other uses.

You can use active messaging this way: 

```php
$bot = new Bot();
$bot->setToken('TOKEN'); // Get your token here https://my.slack.com/services/new/bot
$bot->loadInternalCommands(); // This loads example commands
$bot->loadPushNotifier(function () {
	return [
		'channel' => '#general',
		'username' => '@slacker',
		'message' => "Happy Birthday!! Make sure you have fun today. :-)"
	];
});

$bot->loadPushNotifier(function () {
	return [
		'channel' => '@slacker',
		'username' => null,
		'message' => "Current UNIX timestamp is: " . time()
	];
}, 1800);
$bot->run();

```

In the example above, we have set two active messages. 

**First message** will send the following message in the '#general' channel: 

```
@slacker Happy Birthday!! Make sure you have fun today. :-)
```

It will be triggered only **once**, 10 seconds after launching the script. Slack servers normally establish the connection by then.

**Second message** is a periodic message. It will be sent to the user having the username *slacker* in the team. It won't mention anyone and will be repeated every 30 minutes (1800 seconds). The message should appear as: 

```
Current UNIX timestamp is: 1489145707
Current UNIX timestamp is: 1489147507
```

That should happen within 1 hour of launching. 

*NOTE*: The first message would appear 30 minutes after launching.

The function you add using `loadPushNotifier` must return an array containing the following keys:

- **channel**: Name of the channel or user to whom the message is to be sent. Channel names should have the `#` prefix while usernames must have the `@` prefix. If you do not set a prefix, the name is assumed to be a channel name. Using prefixes is recommended here.
- **username**: Any username to be mentioned ahead of the message. You can specify the name without the `@` prefix here, though you might want to use the prefix to maintain uniformity within the code.
- **message**: The actual message to be sent.



================================================
FILE: composer.json
================================================
{
    "name": "jclg/php-slack-bot",
    "description": "Slack bot user written in PHP",
    "homepage": "https://github.com/jclg/php-slack-bot",
    "license": "MIT",
    "require": {
        "semako/phpws": "1.0.1",
        "react/http": "^0.4",
        "ext-curl": "*"
    },
    "authors": [
        {
            "name": "jclg",
            "email": "jeancharleslegoff@gmail.com"
        }
    ],
    "autoload": {
        "psr-4": {
            "PhpSlackBot\\": "src/PhpSlackBot"
        }
    }
}


================================================
FILE: src/PhpSlackBot/ActiveMessenger/Push.php
================================================
<?php
namespace PhpSlackBot\ActiveMessenger;

use PhpSlackBot\Base;

class Push extends Base
{
    /**
     * @param string $channelOrUsername  Channel name (not Channel ID assigned by Slack) to which message is to be sent.
     *                                   Usernames can also be passed.
     *                                   Channel names should have the '#' prefix. Usernames MUST have the '@' prefix
     * @param string $usernameForMention If a user is to be mentioned in a message.
     * @param string $message            Actual message to be sent
     *
     * @throws \Exception
     */
    public function sendMessage($channelOrUsername, $usernameForMention, $message)
    {
        // if the channelOrUsername is set to null, then do not send the message
        if (!$channelOrUsername) {
            return;
        }

        // See the first character
        if (strpos($channelOrUsername, '@') === 0) {
            // User's name was requested
            $userId = $this->getUserIdFromUserName($channelOrUsername);
            $channelId = $this->getImIdFromUserId($userId);
        } elseif (strpos($channelOrUsername, '#') === 0) {
            // Channel requested
            $channelId = $this->getChannelIdFromChannelName($channelOrUsername);
        } else {
            // Neither user not channel requested
            // NOTE: We assume it to be a channel name
            $channelId = $this->getChannelIdFromChannelName($channelOrUsername);
        }

        $usernameToSend = $this->getUserIdFromUserName($usernameForMention);
        if (!$usernameToSend) {
            $usernameToSend = null;
        }

        if ($channelId) {
            $this->send($channelId, $usernameToSend, $message);
        } else {
            throw new \Exception('Cannot resolve channel ID to to send the message');
        }
    }

    /**
     * This method is defined here only to satisfy the requirements for extending an abstract class
     */
    protected function configure()
    {
    }

    /**
     * This method is defined here only to satisfy the requirements for extending an abstract class
     */
    public function execute($message, $context)
    {
    }
}


================================================
FILE: src/PhpSlackBot/Base.php
================================================
<?php
namespace PhpSlackBot;

abstract class Base
{
    private $name;
    private $client;
    private $user;
    private $context;
    private $mentionOnly = false;
    private $channel;
    private $caseInsensitive = false;
    abstract protected function configure();
    abstract protected function execute($message, $context);

    public function getName()
    {
        $this->configure();
        return $this->name;
    }

    public function getClient()
    {
        return $this->client;
    }

    public function getMentionOnly()
    {
        return $this->mentionOnly;
    }

    public function setMentionOnly($mentionOnly)
    {
        $this->mentionOnly = $mentionOnly;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function setClient($client)
    {
        $this->client = $client;
    }

    public function setChannel($channel)
    {
        $this->channel = $channel;
    }

    public function setUser($user)
    {
        $this->user = $user;
    }

    public function getCurrentUser()
    {
        return $this->user;
    }

    public function setContext($context)
    {
        $this->context = $context;
    }

    public function getCurrentContext()
    {
        return $this->context;
    }

    public function getCurrentChannel()
    {
        return $this->channel;
    }

    public function setCaseInsensitive($caseInsensitive)
    {
        $this->caseInsensitive = $caseInsensitive;
    }

    public function getCaseInsensitive()
    {
        return $this->caseInsensitive;
    }

    protected function send($channel, $username, $message, $parent_thread = null)
    {
        $response = array(
                          'id' => time(),
                          'type' => 'message',
                          'channel' => $channel,
                          'text' => (!is_null($username) ? '<@'.$username.'> ' : '').$message
                          );
        if ($parent_thread) {
            $response['thread_ts'] = $parent_thread;
        }
        $this->client->send(json_encode($response));
    }

    protected function getUserNameFromUserId($userId)
    {
        $username = 'unknown';
        foreach ($this->context['users'] as $user) {
            if ($user['id'] == $userId) {
                $username = $user['name'];
            }
        }
        return $username;
    }

    protected function getUserIdFromUserName($userName)
    {
        $userId = '';
        $userName = str_replace('@', '', $userName);
        foreach ($this->context['users'] as $user) {
            if ($user['name'] == $userName) {
                $userId = $user['id'];
            }
        }
        return $userId;
    }

    protected function getChannelIdFromChannelName($channelName)
    {
        $channelName = str_replace('#', '', $channelName);
        foreach ($this->context['channels'] as $channel) {
            if ($channel['name'] == $channelName) {
                return $channel['id'];
            }
        }
        foreach ($this->context['groups'] as $group) {
            if ($group['name'] == $channelName) {
                return $group['id'];
            }
        }
        return false;
    }

    protected function getChannelNameFromChannelId($channelId)
    {
        foreach ($this->context['channels'] as $channel) {
            if ($channel['id'] == $channelId) {
                return $channel['name'];
            }
        }
        foreach ($this->context['groups'] as $group) {
            if ($group['id'] == $channelId) {
                return $group['name'];
            }
        }
        return false;
    }

    protected function getImIdFromUserId($userId)
    {
        foreach ($this->context['ims'] as $im) {
            if ($im['user'] == $userId) {
                return $im['id'];
            }
        }
        return false;
    }
}


================================================
FILE: src/PhpSlackBot/Bot.php
================================================
<?php
namespace PhpSlackBot;

class Bot
{

    private $params = array();
    private $context = array();
    private $wsUrl;
    private $commands = array();
    private $webhooks = array();
    private $webserverPort = null;
    private $webserverHost = null;
    private $webserverAuthentificationToken = null;
    private $catchAllCommands = array();
    private $pushNotifiers = array();
    private $activeMessenger = null;
    protected $logger = null;

    public function setToken($token)
    {
        $this->params = array('token' => $token);
    }

    public function loadCommand($command)
    {
        if ($command instanceof Command\BaseCommand) {
            $this->commands[$command->getName()] = $command;
        } else {
            throw new \Exception('Command must implement PhpSlackBot\Command\BaseCommand');
        }
    }

    public function loadWebhook($webhook)
    {
        if ($webhook instanceof Webhook\BaseWebhook) {
            $this->webhooks[$webhook->getName()] = $webhook;
        } else {
            throw new \Exception('Webhook must implement PhpSlackBot\Webhook\BaseWebhook');
        }
    }

    public function loadCatchAllCommand($command)
    {
        if ($command instanceof Command\BaseCommand) {
            $this->catchAllCommands[] = $command;
        } else {
            throw new \Exception('Command must implement PhpSlackBot\Command\BaseCommand');
        }
    }

    public function enableWebserver($port, $authentificationToken = null, $host = '127.0.0.1')
    {
        $this->webserverPort = $port;
        $this->webserverAuthentificationToken = $authentificationToken;
        $this->webserverHost = $host;
    }

    public function loadPushNotifier($method, $repeatInterval = null)
    {
        if (is_callable($method)) {
            $this->pushNotifiers[] = ['interval' => (int)$repeatInterval, 'method' => $method];
        } else {
            throw new \Exception('Closure passed as push notifier is not callable.');
        }
    }

    public function run()
    {
        if (!isset($this->params['token'])) {
            throw new \Exception('A token must be set. Please see https://my.slack.com/services/new/bot');
        }

        $this->init();
        $logger = $this->logger;

        $loop = \React\EventLoop\Factory::create();
        $client = new \Devristo\Phpws\Client\WebSocket($this->wsUrl, $loop, $logger);

        $client->on("request", function ($headers) use ($logger) {
                $logger->notice("Request object created!");
        });

        $client->on("handshake", function () use ($logger) {
                $logger->notice("Handshake received!");
        });

        $client->on("connect", function () use ($logger, $client) {
                $logger->notice("Connected!");
        });

        $client->on("message", function ($message) use ($client, $logger) {
            $data = $message->getData();
            $logger->notice("Got message: ".$data);
            $data = json_decode($data, true);

            if (count($this->catchAllCommands)) {
                foreach ($this->catchAllCommands as $command) {
                    $command->setClient($client);
                    $command->setContext($this->context);
                    $command->executeCommand($data, $this->context);
                }
            }
            $command = $this->getCommand($data);
            if ($command instanceof Command\BaseCommand) {
                $command->setClient($client);
                if (isset($data['channel'])) {
                    $command->setChannel($data['channel']);
                }
                if (isset($data['user'])) {
                    $command->setUser($data['user']);
                }
                $command->setContext($this->context);
                $command->executeCommand($data, $this->context);
            }
        });

        /* Webserver */
        if (null !== $this->webserverPort) {
            $logger->notice("Listening on port ".$this->webserverPort);
            $socket = new \React\Socket\Server($loop);
            $http = new \React\Http\Server($socket);
            $http->on('request', function ($request, $response) use ($client) {
                $request->on('data', function ($data) use ($client, $request, $response) {
                    parse_str($data, $post);
                    if ($this->webserverAuthentificationToken === null ||
                        ($this->webserverAuthentificationToken !== null &&
                         isset($post['auth']) &&
                         $post['auth'] === $this->webserverAuthentificationToken)) {
                        if (isset($post['name']) && is_string($post['name']) && isset($this->webhooks[$post['name']])) {
                            $hook = $this->webhooks[$post['name']];
                            $hook->setClient($client);
                            $hook->setContext($this->context);
                            $hook->executeWebhook(json_decode($post['payload'], true), $this->context);
                            $response->writeHead(200, array('Content-Type' => 'text/plain'));
                            $response->end("Ok\n");
                        } else {
                            $response->writeHead(404, array('Content-Type' => 'text/plain'));
                            $response->end("No webhook found\n");
                        }
                    } else {
                        $response->writeHead(403, array('Content-Type' => 'text/plain'));
                        $response->end("");
                    }
                });
            });
            $socket->listen($this->webserverPort, $this->webserverHost);
        }

        /* Notifiers */

        if (!$this->activeMessenger) {
            $this->activeMessenger = new ActiveMessenger\Push();
            $this->activeMessenger->setContext($this->context);
            $this->activeMessenger->setClient($client);
        }
        foreach ($this->pushNotifiers as $notifierArray) {
            if ($notifierArray['interval'] != 0) {
                $loop->addPeriodicTimer($notifierArray['interval'], function () use ($notifierArray) {
                    if ($this->activeMessenger instanceof ActiveMessenger\Push) {
                        $resultArray = call_user_func($notifierArray['method']);
                        $this->activeMessenger->sendMessage(
                            $resultArray['channel'],
                            $resultArray['username'],
                            $resultArray['message']
                        );
                    }
                });
            } else {
                $loop->addTimer(10, function () use ($notifierArray) {
                    if ($this->activeMessenger instanceof ActiveMessenger\Push) {
                        $resultArray = call_user_func($notifierArray['method']);
                        $this->activeMessenger->sendMessage(
                            $resultArray['channel'],
                            $resultArray['username'],
                            $resultArray['message']
                        );
                    }
                });
            }
        }

        $client->open();

        $loop->run();
    }

    public function initLogger(\Zend\Log\LoggerInterface $logger = null)
    {
        if (! is_null($logger)) {
            $this->logger = $logger;
        } else {
            $this->logger = new \Zend\Log\Logger();
            $writer = new \Zend\Log\Writer\Stream("php://output");
            $this->logger->addWriter($writer);
        }
    }

    private function init()
    {
        $url = 'https://slack.com/api/rtm.start';
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($this->params));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $body = curl_exec($ch);
        if ($body === false) {
            throw new \Exception('Error when requesting '.$url.' '.curl_error($ch));
        }
        curl_close($ch);
        $response = json_decode($body, true);
        if (is_null($response)) {
            throw new \Exception('Error when decoding body ('.$body.').');
        }
        $this->context = $response;
        if (isset($response['error'])) {
            throw new \Exception($response['error']);
        }
        $this->wsUrl = $response['url'];

        if (is_null($this->logger)) {
            $this->initLogger();
        }
    }

    public function loadInternalCommands()
    {
        $commands = array(
                          new \PhpSlackBot\Command\PingPongCommand,
                          new \PhpSlackBot\Command\CountCommand,
                          new \PhpSlackBot\Command\DateCommand,
                          new \PhpSlackBot\Command\PokerPlanningCommand,
                          );
        foreach ($commands as $command) {
            if (!isset($this->commands[$command->getName()])) {
                $this->commands[$command->getName()] = $command;
            }
        }
    }

    public function loadInternalWebhooks()
    {
        $webhooks = array(
                          new \PhpSlackBot\Webhook\OutputWebhook,
                          );
        foreach ($webhooks as $webhook) {
            if (!isset($this->webhooks[$webhook->getName()])) {
                $this->webhooks[$webhook->getName()] = $webhook;
            }
        }
    }

    private function getCommand($data)
    {
        if (empty($data['text'])) {
            return null;
        }

        // Check if bot is mentioned
        $botMention = false;
        if (strpos($data['text'], '<@'.$this->context['self']['id'].'>') !== false) {
            $botMention = true;
        }

        $find = '/^'.preg_quote('<@'.$this->context['self']['id'].'>', '/').'[ ]*/';
        $text = preg_replace($find, '', $data['text']);

        if (empty($text)) {
            return null;
        }

        foreach ($this->commands as $commandName => $availableCommand) {
            $find = '/^'.preg_quote($commandName).'/';
            if ($availableCommand->getCaseInsensitive()) {
                $find .= 'i';
            }

            if (preg_match($find, $text) &&
                (!$availableCommand->getMentionOnly() || $botMention)) {
                return $availableCommand;
            }
        }

        return null;
    }
}


================================================
FILE: src/PhpSlackBot/Command/BaseCommand.php
================================================
<?php
namespace PhpSlackBot\Command;

abstract class BaseCommand extends \PhpSlackBot\Base
{

    public function executeCommand($message, $context)
    {
        return $this->execute($message, $context);
    }
}


================================================
FILE: src/PhpSlackBot/Command/CountCommand.php
================================================
<?php
namespace PhpSlackBot\Command;

class CountCommand extends BaseCommand
{

    private $count = 0;

    protected function configure()
    {
        $this->setName('count');
    }

    protected function execute($message, $context)
    {
        $this->send($this->getCurrentChannel(), null, $this->count);
        $this->count++;
    }
}


================================================
FILE: src/PhpSlackBot/Command/DateCommand.php
================================================
<?php
namespace PhpSlackBot\Command;

class DateCommand extends BaseCommand
{

    protected function configure()
    {
        $this->setName('date');
    }

    protected function execute($message, $context)
    {
        $this->send($this->getCurrentChannel(), null, date("D M j G:i:s T Y"));
    }
}


================================================
FILE: src/PhpSlackBot/Command/PingPongCommand.php
================================================
<?php
namespace PhpSlackBot\Command;

class PingPongCommand extends BaseCommand
{

    protected function configure()
    {
        $this->setName('ping');
    }

    protected function execute($message, $context)
    {
        $this->send($this->getCurrentChannel(), $this->getCurrentUser(), 'Pong');
    }
}


================================================
FILE: src/PhpSlackBot/Command/PokerPlanningCommand.php
================================================
<?php
namespace PhpSlackBot\Command;

class PokerPlanningCommand extends BaseCommand
{

    private $count = 0;
    private $initiator;
    private $scores = array();
    private $status = 'free';

    protected function configure()
    {
        $this->setName('pokerp');
    }

    protected function execute($message, $context)
    {
        $args = $this->getArgs($message);
        $command = isset($args[1]) ? $args[1] : '';

        switch ($command) {
            case 'start':
                $this->start($args);
                break;
            case 'status':
                $this->status();
                break;
            case 'vote':
                $this->vote($args);
                break;
            case 'end':
                $this->end();
                break;
            default:
                $this->send(
                    $this->getCurrentChannel(),
                    $this->getCurrentUser(),
                    'No comprendo. Use "'.$this->getName().' start" or "'.$this->getName().' status"'
                );
        }
    }

    private function start($args)
    {
        if ($this->status == 'free') {
            $this->subject = isset($args[2]) ? $args[2] : null;
            if (!is_null($this->subject)) {
                $this->subject = str_replace(array('<', '>'), '', $this->subject);
            }
            $this->status = 'running';
            $this->initiator = $this->getCurrentUser();
            $this->scores = array();
            $this->send(
                $this->getCurrentChannel(),
                null,
                'Poker planning sessions start by '.$this->getUserNameFromUserId($this->initiator)."\n".
                'Please vote'.(!is_null($this->subject) ? ' for '.$this->subject : '')
            );
            $this->send(
                $this->getCurrentChannel(),
                $this->getCurrentUser(),
                'Use "'.$this->getName().' end" to end the session'
            );
        } else {
            $this->send(
                $this->getCurrentChannel(),
                $this->getCurrentUser(),
                'A poker session is still active'
            );
        }
    }

    private function status()
    {
        $message = 'Current status : '.$this->status;
        if ($this->status == 'running') {
            $message .= "\n".'Initiator : '.$this->getUserNameFromUserId($this->initiator);
        }
        $this->send($this->getCurrentChannel(), null, $message);
        if ($this->status == 'running') {
            if (empty($this->scores)) {
                $this->send($this->getCurrentChannel(), null, 'No one has voted yet');
            } else {
                $message = '';
                foreach ($this->scores as $user => $score) {
                    $message .= $this->getUserNameFromUserId($user).' has voted'."\n";
                }
                $this->send($this->getCurrentChannel(), null, $message);
            }
        }
    }

    private function vote($args)
    {
        if ($this->status == 'running') {
            $score = isset($args[2]) ? $args[2] : -1;
            $sequence = $this->getSequence();
            if (!in_array($score, $sequence)) {
                $this->send(
                    $this->getCurrentChannel(),
                    $this->getCurrentUser(),
                    'Use "'.$this->getName().' vote [number]". Choose [number] between '.implode(', ', $sequence)
                );
            } else {
                $this->scores[$this->getCurrentUser()] = (int) $score;
                $this->send(
                    $this->getCurrentChannel(),
                    $this->getCurrentUser(),
                    'Thank you! Your vote ('.$score.
                    ') has been recorded You can still change your vote until the end of the session'
                );
            }
        } else {
            $this->send(
                $this->getCurrentChannel(),
                $this->getCurrentUser(),
                'There is no poker session. You can start one with "'.$this->getName().' start"'
            );
        }
    }

    private function end()
    {
        if ($this->status == 'running') {
            if ($this->getCurrentUser() == $this->initiator) {
                $message = 'Ending session'.
                    (!is_null($this->subject) ? ' for '.$this->subject : '')."\n".'Results : '."\n";
                if (empty($this->scores)) {
                    $message .= 'No vote !';
                } else {
                    foreach ($this->scores as $user => $score) {
                        $message .= $this->getUserNameFromUserId($user).' => '.$score."\n";
                    }
                    $message .= '------------------'."\n";
                    $message .= 'Average score : '.$this->getAverageScore()."\n";
                    $message .= 'Median score : '.$this->getMedianScore()."\n";
                    $message .= 'Max score : '.$this->getMaxScore()."\n";
                    $message .= 'Min score : '.$this->getMinScore();
                }
                $this->send($this->getCurrentChannel(), null, $message);
                $this->status = 'free';
            } else {
                $this->send(
                    $this->getCurrentChannel(),
                    $this->getCurrentUser(),
                    'Only '.$this->getUserNameFromUserId($this->initiator).' can end the session'
                );
            }
        } else {
            $this->send(
                $this->getCurrentChannel(),
                $this->getCurrentUser(),
                'There is no poker session. You can start one with "'.$this->getName().' start"'
            );
        }
    }

    private function getArgs($message)
    {
        $args = array();
        if (isset($message['text'])) {
            $args = array_values(array_filter(explode(' ', $message['text'])));
        }
        $commandName = $this->getName();
        // Remove args which are before the command name
        $finalArgs = array();
        $remove = true;
        foreach ($args as $arg) {
            if ($commandName == $arg) {
                $remove = false;
            }
            if (!$remove) {
                $finalArgs[] = $arg;
            }
        }
        return $finalArgs;
    }

    private function getAverageScore()
    {
        return array_sum($this->scores) / count($this->scores);
    }

    private function getMedianScore()
    {
        $arr = $this->scores;
        sort($arr);
        $count = count($arr);
        $middleval = floor(($count-1)/2);
        if ($count % 2) {
            $median = $arr[$middleval];
        } else {
            $low = $arr[$middleval];
            $high = $arr[$middleval+1];
            $median = (($low+$high)/2);
        }
        return $median;
    }

    private function getSequence()
    {
        return array(0, 1, 2, 3, 5, 8, 13, 20, 40, 100);
    }

    private function getMaxScore()
    {
        return max($this->scores);
    }

    private function getMinScore()
    {
        return min($this->scores);
    }
}


================================================
FILE: src/PhpSlackBot/Webhook/BaseWebhook.php
================================================
<?php
namespace PhpSlackBot\Webhook;

abstract class BaseWebhook extends \PhpSlackBot\Base
{

    public function executeWebhook($payload, $context)
    {
        return $this->execute($payload, $context);
    }
}


================================================
FILE: src/PhpSlackBot/Webhook/OutputWebhook.php
================================================
<?php
namespace PhpSlackBot\Webhook;

class OutputWebhook extends BaseWebhook
{

    protected function configure()
    {
        $this->setName('output');
    }

    protected function execute($payload, $context)
    {
        $payload['channel'] = $this->getChannelIdFromChannelName($payload['channel']);
        $this->getClient()->send(json_encode($payload));
    }
}
Download .txt
gitextract_z_txds45/

├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── composer.json
└── src/
    └── PhpSlackBot/
        ├── ActiveMessenger/
        │   └── Push.php
        ├── Base.php
        ├── Bot.php
        ├── Command/
        │   ├── BaseCommand.php
        │   ├── CountCommand.php
        │   ├── DateCommand.php
        │   ├── PingPongCommand.php
        │   └── PokerPlanningCommand.php
        └── Webhook/
            ├── BaseWebhook.php
            └── OutputWebhook.php
Download .txt
SYMBOL INDEX (69 symbols across 10 files)

FILE: src/PhpSlackBot/ActiveMessenger/Push.php
  class Push (line 6) | class Push extends Base
    method sendMessage (line 17) | public function sendMessage($channelOrUsername, $usernameForMention, $...
    method configure (line 53) | protected function configure()
    method execute (line 60) | public function execute($message, $context)

FILE: src/PhpSlackBot/Base.php
  class Base (line 4) | abstract class Base
    method configure (line 13) | abstract protected function configure();
    method execute (line 14) | abstract protected function execute($message, $context);
    method getName (line 16) | public function getName()
    method getClient (line 22) | public function getClient()
    method getMentionOnly (line 27) | public function getMentionOnly()
    method setMentionOnly (line 32) | public function setMentionOnly($mentionOnly)
    method setName (line 37) | public function setName($name)
    method setClient (line 42) | public function setClient($client)
    method setChannel (line 47) | public function setChannel($channel)
    method setUser (line 52) | public function setUser($user)
    method getCurrentUser (line 57) | public function getCurrentUser()
    method setContext (line 62) | public function setContext($context)
    method getCurrentContext (line 67) | public function getCurrentContext()
    method getCurrentChannel (line 72) | public function getCurrentChannel()
    method setCaseInsensitive (line 77) | public function setCaseInsensitive($caseInsensitive)
    method getCaseInsensitive (line 82) | public function getCaseInsensitive()
    method send (line 87) | protected function send($channel, $username, $message, $parent_thread ...
    method getUserNameFromUserId (line 101) | protected function getUserNameFromUserId($userId)
    method getUserIdFromUserName (line 112) | protected function getUserIdFromUserName($userName)
    method getChannelIdFromChannelName (line 124) | protected function getChannelIdFromChannelName($channelName)
    method getChannelNameFromChannelId (line 140) | protected function getChannelNameFromChannelId($channelId)
    method getImIdFromUserId (line 155) | protected function getImIdFromUserId($userId)

FILE: src/PhpSlackBot/Bot.php
  class Bot (line 4) | class Bot
    method setToken (line 20) | public function setToken($token)
    method loadCommand (line 25) | public function loadCommand($command)
    method loadWebhook (line 34) | public function loadWebhook($webhook)
    method loadCatchAllCommand (line 43) | public function loadCatchAllCommand($command)
    method enableWebserver (line 52) | public function enableWebserver($port, $authentificationToken = null, ...
    method loadPushNotifier (line 59) | public function loadPushNotifier($method, $repeatInterval = null)
    method run (line 68) | public function run()
    method initLogger (line 188) | public function initLogger(\Zend\Log\LoggerInterface $logger = null)
    method init (line 199) | private function init()
    method loadInternalCommands (line 225) | public function loadInternalCommands()
    method loadInternalWebhooks (line 240) | public function loadInternalWebhooks()
    method getCommand (line 252) | private function getCommand($data)

FILE: src/PhpSlackBot/Command/BaseCommand.php
  class BaseCommand (line 4) | abstract class BaseCommand extends \PhpSlackBot\Base
    method executeCommand (line 7) | public function executeCommand($message, $context)

FILE: src/PhpSlackBot/Command/CountCommand.php
  class CountCommand (line 4) | class CountCommand extends BaseCommand
    method configure (line 9) | protected function configure()
    method execute (line 14) | protected function execute($message, $context)

FILE: src/PhpSlackBot/Command/DateCommand.php
  class DateCommand (line 4) | class DateCommand extends BaseCommand
    method configure (line 7) | protected function configure()
    method execute (line 12) | protected function execute($message, $context)

FILE: src/PhpSlackBot/Command/PingPongCommand.php
  class PingPongCommand (line 4) | class PingPongCommand extends BaseCommand
    method configure (line 7) | protected function configure()
    method execute (line 12) | protected function execute($message, $context)

FILE: src/PhpSlackBot/Command/PokerPlanningCommand.php
  class PokerPlanningCommand (line 4) | class PokerPlanningCommand extends BaseCommand
    method configure (line 12) | protected function configure()
    method execute (line 17) | protected function execute($message, $context)
    method start (line 44) | private function start($args)
    method status (line 74) | private function status()
    method vote (line 94) | private function vote($args)
    method end (line 123) | private function end()
    method getArgs (line 159) | private function getArgs($message)
    method getAverageScore (line 180) | private function getAverageScore()
    method getMedianScore (line 185) | private function getMedianScore()
    method getSequence (line 201) | private function getSequence()
    method getMaxScore (line 206) | private function getMaxScore()
    method getMinScore (line 211) | private function getMinScore()

FILE: src/PhpSlackBot/Webhook/BaseWebhook.php
  class BaseWebhook (line 4) | abstract class BaseWebhook extends \PhpSlackBot\Base
    method executeWebhook (line 7) | public function executeWebhook($payload, $context)

FILE: src/PhpSlackBot/Webhook/OutputWebhook.php
  class OutputWebhook (line 4) | class OutputWebhook extends BaseWebhook
    method configure (line 7) | protected function configure()
    method execute (line 12) | protected function execute($payload, $context)
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (36K chars).
[
  {
    "path": ".gitignore",
    "chars": 52,
    "preview": "*~\n.idea/\nbot.php\ncomposer.phar\ncomposer.lock\nvendor"
  },
  {
    "path": ".travis.yml",
    "chars": 158,
    "preview": "language: php\nphp:\n  - '7.1'\nbefore_script:\n  # PHP_CodeSniffer\n  - pear install pear/PHP_CodeSniffer\n  - phpenv rehash\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1077,
    "preview": "MIT License\n\nCopyright (c) 2016 Jean-Charles Le Goff\n\nPermission is hereby granted, free of charge, to any person obtain"
  },
  {
    "path": "README.md",
    "chars": 6487,
    "preview": "# PHP Slack Bot\n\nA simple bot user written in PHP using the Slack Real Time Messaging API https://api.slack.com/rtm\n\n## "
  },
  {
    "path": "composer.json",
    "chars": 503,
    "preview": "{\n    \"name\": \"jclg/php-slack-bot\",\n    \"description\": \"Slack bot user written in PHP\",\n    \"homepage\": \"https://github."
  },
  {
    "path": "src/PhpSlackBot/ActiveMessenger/Push.php",
    "chars": 2197,
    "preview": "<?php\nnamespace PhpSlackBot\\ActiveMessenger;\n\nuse PhpSlackBot\\Base;\n\nclass Push extends Base\n{\n    /**\n     * @param str"
  },
  {
    "path": "src/PhpSlackBot/Base.php",
    "chars": 3888,
    "preview": "<?php\nnamespace PhpSlackBot;\n\nabstract class Base\n{\n    private $name;\n    private $client;\n    private $user;\n    priva"
  },
  {
    "path": "src/PhpSlackBot/Bot.php",
    "chars": 10389,
    "preview": "<?php\nnamespace PhpSlackBot;\n\nclass Bot\n{\n\n    private $params = array();\n    private $context = array();\n    private $w"
  },
  {
    "path": "src/PhpSlackBot/Command/BaseCommand.php",
    "chars": 214,
    "preview": "<?php\nnamespace PhpSlackBot\\Command;\n\nabstract class BaseCommand extends \\PhpSlackBot\\Base\n{\n\n    public function execut"
  },
  {
    "path": "src/PhpSlackBot/Command/CountCommand.php",
    "chars": 344,
    "preview": "<?php\nnamespace PhpSlackBot\\Command;\n\nclass CountCommand extends BaseCommand\n{\n\n    private $count = 0;\n\n    protected f"
  },
  {
    "path": "src/PhpSlackBot/Command/DateCommand.php",
    "chars": 304,
    "preview": "<?php\nnamespace PhpSlackBot\\Command;\n\nclass DateCommand extends BaseCommand\n{\n\n    protected function configure()\n    {\n"
  },
  {
    "path": "src/PhpSlackBot/Command/PingPongCommand.php",
    "chars": 310,
    "preview": "<?php\nnamespace PhpSlackBot\\Command;\n\nclass PingPongCommand extends BaseCommand\n{\n\n    protected function configure()\n  "
  },
  {
    "path": "src/PhpSlackBot/Command/PokerPlanningCommand.php",
    "chars": 7111,
    "preview": "<?php\nnamespace PhpSlackBot\\Command;\n\nclass PokerPlanningCommand extends BaseCommand\n{\n\n    private $count = 0;\n    priv"
  },
  {
    "path": "src/PhpSlackBot/Webhook/BaseWebhook.php",
    "chars": 214,
    "preview": "<?php\nnamespace PhpSlackBot\\Webhook;\n\nabstract class BaseWebhook extends \\PhpSlackBot\\Base\n{\n\n    public function execut"
  },
  {
    "path": "src/PhpSlackBot/Webhook/OutputWebhook.php",
    "chars": 372,
    "preview": "<?php\nnamespace PhpSlackBot\\Webhook;\n\nclass OutputWebhook extends BaseWebhook\n{\n\n    protected function configure()\n    "
  }
]

About this extraction

This page contains the full source code of the jclg/php-slack-bot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (32.8 KB), approximately 8.1k tokens, and a symbol index with 69 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!