Slackのbotから投稿するmicroblog

先日SlackとWebを連携させるアプリを作ったので、そのときに書いたコードからサンプルを作ってみた。

1. Slack hubotを動かす

Yeomanを使ってhubotの雛形をジェネレートする。
起動スクリプトを書く。

#!/bin/sh

ARG1=$1
if [ "${ARG1}" == "" ]; then
    echo "Usage: bin/hubot (start|stop|restart|status)"
    exit 1;
fi

export HUBOT_SLACK_TOKEN=$HUBOT_SLACK_TOKEN
if [ "${HUBOT_SLACK_TOKEN}" == "" ]; then
    echo "env HUBOT_SLACK_TOKEN is not found"
    exit 1;
fi

set -e

npm install
export PATH="node_modules/.bin:node_modules/hubot/node_modules/.bin:$PATH"

# exec node_modules/.bin/hubot --name "mybot" "$@"

start() {
    forever start -c coffee node_modules/.bin/hubot -a slack
}
stop () {
    forever stop -c coffee node_modules/.bin/hubot
}
status() {
    forever list
}
restart() {
    forever restartall
}

case "$ARG1" in
    "stop" )
        stop
        ;;
    "restart" )
        restart
        ;;
    "start" )
        start
        ;;
    "status" )
        status
        ;;
esac

https://raw.githubusercontent.com/chantera/microblog/master/app/bin/hubot

2. hubotからSQLを叩いてsqliteを操作する

CoffeeScriptを書く。JSはよく書くがCoffeeScriptは全く書かないのでつらい。

module.exports = (robot) ->
  robot.hear /hi/, (res) ->
    res.send res.random [
      'hello',
      'what?'
    ]

  robot.enter (res) ->
    res.send "Hi, #{res.message.user.name}"

  robot.leave (res) ->
    res.send "Goodbye, #{res.message.user.name}"

  robot.respond /post (.+)/i, (res) ->
    message = res.match[1]
    sqlite3 = require 'sqlite3'
    db = new (sqlite3.Database)('../db/app.db')
    db.serialize ->
      stmt = db.prepare('INSERT INTO message (body, user, post_date) VALUES (?, ?, CURRENT_TIMESTAMP)')
      stmt.run message, res.message.user.name
      stmt.finalize()
      res.reply "Post your message successfully"
      return
    db.close()

  robot.respond /select/i, (res) ->
    sqlite3 = require 'sqlite3'
    db = new (sqlite3.Database)('../db/app.db')
    db.serialize ->
      db.each 'SELECT * FROM message WHERE status=1 ORDER BY id DESC', (err, row) ->
        res.send row.id + ': ' + row.user + ', ' + row.body + ', ' + row.post_date
        return
      return
    db.close()

  robot.respond /delete (\d+)/i, (res) ->
    id = res.match[1]
    sqlite3 = require 'sqlite3'
    db = new (sqlite3.Database)('../db/app.db')
    db.serialize ->
      stmt = db.prepare('UPDATE message SET status=2 WHERE id=?')
      stmt.run id
      stmt.finalize()
      res.send "Delete your message successfully"
      return
    db.close()

https://raw.githubusercontent.com/chantera/microblog/master/app/scripts/app.coffee

3. オレオレPHPフレームワークでsqliteから投稿を表示

サンプルで使うだけのミニマルなフレームワークがないので自分で書く。勉強になって良い。

<?php

function bootstrap() {
    $script = $_SERVER['SCRIPT_NAME'];
    define('ENTRY_POINT', '/index.php');
    define('WEBROOT', substr($script, 0, strrpos($script, ENTRY_POINT)));
    session_start();
    return true;
}

class App
{
    protected $_routes = array(
        'GET'    => array(),
        'POST'   => array(),
        'PUT'    => array(),
        'DELETE' => array(),
        'ERROR'  => array(),
    );

    public function __construct()
    {
    }

    public function run()
    {
        $dispatcher = new Dispatcher();
        return $dispatcher->dispatch(new Request(), $this->_routes);
    }

    public function get($url, $callback)
    {
        $this->_addRoute('GET', $url, $callback);
    }

    public function post($url, $callback)
    {
        $this->_addRoute('POST', $url, $callback);
    }

    public function put($url, $callback)
    {
        $this->_addRoute('PUT', $url, $callback);
    }

    public function delete($url, $callback)
    {
        $this->_addRoute('DELETE', $url, $callback);
    }

    public function error($code, $callback)
    {
        $this->_addRoute('ERROR', (string) $code, $callback);
    }

    protected function _addRoute($method, $url, $callback)
    {
        if ( !is_callable($callback) && !is_string($callback) ) {
            throw new InvalidArgumentException('invalid callback type');
        }
        $this->_routes[$method][$url] = $callback;
    }
}

class Dispatcher
{
    public function dispatch(Request $request, $routes)
    {
        if ( isset($routes[$request->method][$request->path]) ) {
            $callback = $routes[$request->method][$request->path];
        } else {
            $callback = $routes['ERROR']['404'];
        }
        if ( is_callable($callback) ) {
            $func = $callback;
            $args = array($request);
        } else if ( is_string($callback) ) {
            $names = explode(':', $callback);
            if ( count($names) == 2 ) {
                $class = $names[0] . 'Controller';
                $func = array(new $class($request), $names[1]);
                $args = array();
            } else {
                $func = $names[0];
                $args = array($request);
            }
        } else {
            throw new Exception('cannot resolve callback');
        }
        call_user_func_array($func, $args);
        return true;
    }
}

class Request
{
    protected $_values;

    public function __construct()
    {
        $s = $_SERVER;
        $path = array_key_exists('PATH_INFO', $s) ? $s['PATH_INFO'] : '/';
        $this->_values = array(
            'method' => $s['REQUEST_METHOD'],
            'uri'    => $s['REQUEST_URI'],
            'path'   => $path,
            'get'    => $_GET,
            'post'   => $_POST,
        );
    }

    public function __get($name)
    {
        return $this->_values[$name];
    }
}

class Controller
{
    protected $_request;
    protected $_vars = array();

    public function __construct(Request $request)
    {
        $this->_request = $request;
    }

    protected function _set(array $vars)
    {
        $this->_vars = $vars;
    }

    protected function _render($template)
    {
        extract($this->_vars, EXTR_OVERWRITE);
        include_once($template);
    }
}

bootstrap();

https://raw.githubusercontent.com/chantera/microblog/master/www/core.php

ちなみにアプリケーションはこんな感じで書く。

<?php

require __DIR__ . '/core.php';
define('APP_DIR', __DIR__);
define('DB', APP_DIR . '/../db/app.db');

class MyController extends Controller
{
    public function index()
    {
        $this->_set([
            'author' => 'Hiroki Teranishi',
            'message' => 'Hello World!',
        ]);
        $this->_render(APP_DIR . '/templates/index.php');
    }

    public function posts()
    {
        $model = new Post();
        $messages = $model->find();
        $this->_set(compact('messages'));
        $this->_render(APP_DIR . '/templates/posts.php');
    }
}

class Post
{
    public function find()
    {
        $posts = [];
        try {
            $pdo = new PDO('sqlite:' . DB);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            $stmt = $pdo->query("SELECT * FROM message WHERE status=1 ORDER BY id DESC");
            $posts = $stmt->fetchAll();
        } catch (Exception $e) {
            echo $e->getMessage();
        }
        return $posts;
    }
}

function execute() {
    try {
        $app = new App();
        $app->get('/', 'My:index');             // routing '/' to MyController::index action
        $app->get('/posts', 'My:posts');        // routing '/posts' to MyController::posts action
        $app->get('/about', function() {        // routing '/about' to a callback function
            echo '<a href="https://github.com/chantera" target="_blank">See my GitHub page.</a>';
        });
        $app->error(404, 'error404');           // routing 404 error to a function by name
        $app->run();
    } catch (Exception $e) {
        echo $e->getMessage();
        echo $e->getTraceAsString();
    }
}

function error404($request) {
    ?>
    <h1>Not Found</h1>
    <p>The requested URL <?= $request->path ?> was not found on this server.</p>
    <hr>
    <p>This is generated by myblog app.</p>
    <?php
}

function h($string) {
    return htmlspecialchars($string);
}

https://raw.githubusercontent.com/chantera/microblog/master/www/app.php

投稿してみる。

screenshot1

うまく表示された。

screenshot2

ソースコードは以下に置いてます。

https://github.com/chantera/microblog/

数年ぶりに技術者っぽい投稿をしたなあ。

週末にSlackでbotと会話していると心がすさみます。

LINEで送る
Pocket

コメントを残す

*