PHP5 Code For Twitter Bot "mesoramen" Using MySQL

main script

<?php
require_once 'twuka/twuka.php';
require_once 'twuka/twukasql.php';
require_once 'twuka/twukahtml.php';

$mesoko = new TwitterPostMesoko();
$mesoko->mesoexec();

/**
 * めそトーク投稿クラス
 * 
 * @package TwitterPostMesoko
 * @author Don
 * @since PHP 5.2.9
 * @version 0.3 2009/03/21
 */
class TwitterPostMesoko {

  const TW_USERNAME   = '****'; // TwitterユーザID
  const TW_PASSWORD   = '****'; // Twitterパスワード
  const DB_SRV        = '****'; // MySQLサーバー名
  const DB_ID         = '****'; // MySQLユーザーID
  const DB_PASSWD     = '****'; // MySQLパスワード
  const DB_DBN        = '****'; // MySQLデータベース名

  const HTML_TITLE    = 'mesoramen post'; // 投稿URLタイトル
  const TALK_INTERVAL = 21600; // (60 * 60 * 24 / 4) = 21600 トーク間隔
                               // アクセスは2分間隔を想定で1日4回程度, 深夜は無し
  private $twitterpost;
  private $mysql;
  private $html;
  private $follow_date;
  private $status_id;
  private $user_screen_name; // これ別に使わないけど…

  /**
   * コンストラクタ
   */
  public function __construct() {
    // twitter投稿用インスタンスを作成
    $this->twitterpost = new TwUka();
    $this->twitterpost->set_param(
      self::TW_USERNAME, self::TW_PASSWORD
    );
    // MySQL通信用インスタンスを作成
    $this->mysql = new TwUkaSQL();
    $this->mysql->set_param(
      self::DB_SRV, self::DB_ID, self::DB_PASSWD, self::DB_DBN
    );
    // HTML表示用インスタンスを作成
    $this->html = new TwUkaHTML();
    $this->html->set_param(self::HTML_TITLE);
  }

  /**
   * めそトークを投稿します。
   */
  public function mesoexec() {
    // セーブデータ取得
    $this->get_param_db();
    // 現在時間取得
    date_default_timezone_set('Asia/Tokyo');
    $today = getdate();

    if ($today['mday'] != $this->follow_date) {
      // 日付が変わったことを記録して自動フォロー
      $this->set_date_db($today['mday']);
      $this->twitterpost->auto_follow();
      $mes = '自動フォローが完了しました';
      $this->html->showhtml($mes);
      exit;
    }

    // reply解析
    $this->twitterpost->get_last_reply();
    // 投稿条件準備
    $max = (self::TALK_INTERVAL / 120) - 1;
    $can_post = (!rand(0, $max)) && ($today['hours'] >= 6);

    if ($this->twitterpost->get_status_id() > $this->status_id) {
      // 新しいreplyがあれば結果を更新して反応する
      $this->set_param_db();
      $mes = $this->get_reply_db();
      $this->twitterpost->posttwitter_with_param(
        $mes, 
        $this->twitterpost->get_user_screen_name(),
        $this->twitterpost->get_status_id()
      );
      $this->html->showhtml('投稿内容: '. $mes);
    } elseif ($can_post) {
      // トークを取得して投稿する
      $mes = $this->get_talk_db();
      $this->twitterpost->posttwitter($mes);
      $this->html->showhtml('投稿内容: '. $mes);
    } else {
      // 投稿しない
      $mes = '抽選漏れで投稿されませんでした';
      $this->html->showhtml($mes);
    }
  }

  /**
   * データベースに問い合わせてパラメータを取得します。
   */
  private function get_param_db() {
    $sql = "
      SELECT `follow_date`, `status_id`, `user_screen_name` 
      FROM `savedata` 
      WHERE `name` LIKE 'mesoramen' 
      LIMIT 1";
    $rs = $this->mysql->send_sql($sql);
    if (!$rs) {
      $mes = 'can not connect db';
      $this->html->showhtml($mes);
      exit;
    }
    $item = mysql_fetch_array($rs); // supplied argument is not a valid MySQL result resource とか言われたので要調査
    $this->follow_date = $item['follow_date'];
    $this->status_id = $item['status_id'];
    $this->user_screen_name = $item['user_screen_name'];
  }

  /**
   * データベースに問い合わせて日付を更新します。
   *
   * @param integer $mday 日付
   */
  private function set_date_db($mday) {
    $sql = "
      UPDATE `". self::DB_DBN. "`.`savedata` 
      SET `follow_date` = ". $mday. " 
      WHERE `savedata`.`name` LIKE 'mesoramen' 
      LIMIT 1";
    $rs = $this->mysql->send_sql($sql);
    if (!$rs) {
      $mes = 'can not connect db';
      $this->html->showhtml($mes);
      exit;
    }
  }

  /**
   * データベースに問い合わせてパラメータを更新します。
   */
  private function set_param_db() {
    $sql = "
      UPDATE `". self::DB_DBN. "`.`savedata` 
      SET `status_id` = ". $this->twitterpost->get_status_id(). " 
        , `user_screen_name` =  '". $this->twitterpost->get_user_screen_name(). "' 
      WHERE `savedata`.`name` LIKE 'mesoramen' 
      LIMIT 1";
    $rs = $this->mysql->send_sql($sql);
    if (!$rs) {
      $mes = 'can not connect db';
      $this->html->showhtml($mes);
      exit;
    }
  }

  /**
   * データベースに問い合わせてリプライトークを生成します。
   *
   * @return string
   */
  private function get_reply_db() {
    $sql = 'SELECT meso_reply FROM `meso_reply` ORDER BY RAND( ) LIMIT 1';
    $rs = $this->mysql->send_sql($sql);
    if (!$rs) {
      $mes = 'can not connect db';
      $this->html->showhtml($mes);
      exit;
    }
    $item = mysql_fetch_array($rs);
    $mes = $item['meso_reply'];
    $mes = '@'. $this->twitterpost->get_user_screen_name(). ' '. $mes;

    return $mes;
  }

  /**
   * データベースに問い合わせてトークを生成します。
   *
   * @return string トーク
   */
  private function get_talk_db() {
    $sql = 'SELECT tsubuyaki FROM `mesoramen` ORDER BY RAND( ) LIMIT 1';
    $rs = $this->mysql->send_sql($sql);
    if (!$rs) {
      $mes = 'can not connect db';
      $this->html->showhtml($mes);
      exit;
    }
    $item = mysql_fetch_array($rs);
    $mes = $item['tsubuyaki'];

    return $mes;
  }

}
?>

twuka.php

<?php
/**
 * Twitter Bot 用の機能色々
 * 
 * PHP versions 4 and 5
 * 
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 * 
 * @package TwUka
 * @author  Don
 * @license http://www.php.net/license/3_0.txt  PHP License 3.0
 * @since   PHP 5.2.9
 * @version 0.3 2009/03/21
 */
class TwUka {

  const TWITTER_POST_URL      = 'http://twitter.com/statuses/update.xml'; 
  const TWITTER_REPLY_URL     = 'http://twitter.com/statuses/replies.xml';
  const TWITTER_FOLLOWERS_URL = 'http://twitter.com/statuses/followers.xml';
  const TWITTER_FOLLOWING_URL = 'http://twitter.com/statuses/friends.xml';
  const TWITTER_FOLLOW_URL    = 'http://twitter.com/friendships/create/';
  const TWITTER_REMOVE_URL    = 'http://twitter.com/friendships/destroy/';

  private $username;
  private $password;

  private $status_id;
  private $user_screen_name;

  /**
   * コンストラクタ
   */
  public function __construct() {
    $this->username = '';
    $this->password = '';
  }

  /**
   * メンバ変数に値をセットします。
   *
   * @param string $username TwitterユーザID
   * @param string $password Twitterパスワード
   */
  public function set_param($username, $password) {
    $this->username = $username;
    $this->password = $password;
  }

  /**
   * 最終replyIDを取得します。
   *
   * @return integer 最終replyID
   */
  public function get_status_id() {
    return $this->status_id;
  }

  /**
   * 最終reply元の名前を取得します。
   *
   * @return string 最終reply送信先
   */
  public function get_user_screen_name() {
    return $this->user_screen_name;
  }

  /**
   * 最終受信reply情報を取得します。
   */
  public function get_last_reply() {
    $url    = self::TWITTER_REPLY_URL;
    $xmlstr = file_get_contents($url, false, stream_context_create(array(
      'http' => array(
        'method' => 'GET',
        'header' => 'Authorization: Basic '. base64_encode(
          $this->username. ':'. $this->password
        )
      )
    )));
    $xml = new SimpleXMLElement($xmlstr);
    $this->status_id        = $xml->status[0]->id;
    $this->user_screen_name = $xml->status[0]->user->screen_name;
  }

  /**
   * Twitterにメッセージを投稿します
   *
   * @param string $mes 投稿メッセージ
   */
  public function posttwitter($mes) {
    $url    = self::TWITTER_POST_URL;
    $params = '?status='. rawurlencode($mes);
    $result = file_get_contents($url. $params , false, stream_context_create(array(
      'http' => array(
        'method' => 'POST',
        'header' => 'Authorization: Basic '. base64_encode(
          $this->username. ':'. $this->password
        )
      )
    )));
  }

  /**
   * Twitterにメッセージをパラメータ付きで投稿します。
   *
   * @param string $mes 投稿メッセージ
   * @param string $in_reply_to reply 送信先ユーザID
   * @param integer $in_reply_to_status_id reply 送信先ステータスID
   */
  public function posttwitter_with_param($mes, $in_reply_to, $in_reply_to_status_id) {
    $url    = self::TWITTER_POST_URL;
    $params = '?status='. rawurlencode($mes). 
              '&in_reply_to='. rawurlencode($in_reply_to).
              '&in_reply_to_status_id='. rawurlencode($in_reply_to_status_id);
    $result = file_get_contents($url. $params , false, stream_context_create(array(
      'http' => array(
        'method' => 'POST',
        'header' => 'Authorization: Basic '. base64_encode(
          $this->username. ':'. $this->password
        )
      )
    )));
  }

  /**
   * follow,removeメソッドを使ってfollowingをfollowersと一致させます。
   * 100人以上の場合もうひと工夫必要です。
   */
  public function auto_follow() {
    // followersの取得
    $url    = self::TWITTER_FOLLOWERS_URL;
    $xmlstr = file_get_contents($url, false, stream_context_create(array(
      'http' => array(
        'method' => 'GET',
        'header' => 'Authorization: Basic '. base64_encode(
          $this->username. ':'. $this->password
        )
      )
    )));
    $xml = new SimpleXMLElement($xmlstr);
    foreach ($xml->user as $user) {
      $followers[] = $user->screen_name;
    }
    // followingの取得
    $url    = self::TWITTER_FOLLOWING_URL;
    $xmlstr = file_get_contents($url, false, stream_context_create(array(
      'http' => array(
        'method' => 'GET',
        'header' => 'Authorization: Basic '. base64_encode(
          $this->username. ':'. $this->password
        )
      )
    )));
    $xml = new SimpleXMLElement($xmlstr);
    foreach ($xml->user as $user) {
      $followings[] = $user->screen_name;
    }
    // followersとfollowingの差分
    $not_following = array_diff($followers, $followings);
    $not_followed  = array_diff($followings, $followers);
    // follow
    if (count($not_following) > 0) {
      foreach ($not_following as $user) {
        $url    = self::TWITTER_FOLLOW_URL. $user. ".xml";
        // protected な相手だと HTTP/1.1 403 Forbidden を食らう
        $xmlstr = @file_get_contents($url, false, stream_context_create(array(
          'http' => array(
            'method' => 'POST',
            'header' => 'Authorization: Basic '. base64_encode(
              $this->username. ':'. $this->password
            )
          )
        )));
      }
    }
    // remove
    if (count($not_followed) > 0) {
      foreach ($not_followed as $user) {
        $url    = self::TWITTER_REMOVE_URL. $user. ".xml";
        $xmlstr = file_get_contents($url, false, stream_context_create(array(
          'http' => array(
            'method' => 'POST',
            'header' => 'Authorization: Basic '. base64_encode(
              $this->username. ':'. $this->password
            )
          )
        )));
      }
    }
  }

}
?>

twukasql.php

<?php
/**
 * MySQLサーバにSQL文を投げるクラス
 */
class TwUkaSQL {

  private $srv;
  private $id;
  private $passwd;
  private $dbn;

  /**
   * メンバ変数に値をセットします。
   *
   * @param stirng $srv    MySQLサーバー名
   * @param string $id     MySQLユーザーID
   * @param string $passwd MySQLパスワード
   * @param string $dbn    MySQLデータベース名
   */
  public function set_param($srv, $id, $passwd, $dbn) {
    $this->srv    = $srv;
    $this->id     = $id;
    $this->passwd = $passwd;
    $this->dbn    = $dbn;
  }

  /**
   * データベースに問い合わせてデータを返します。
   *
   * @param unknown_type $sql SQL文
   * @return unknown mysql_queryの返り値resource, 失敗時はFALSE
   */
  public function send_sql($sql) {
    //DBへ接続開始
    $dbHandle = mysql_connect($this->srv, $this->id, $this->passwd);
    //サーバの接続に失敗した場合はFALSEを返す
    if (!$dbHandle) {
      return FALSE;
    }
    //文字コードをutf-8にする
    $sql_charset = 'SET NAMES utf8';
    $utf = mysql_query($sql_charset);
    //DBの接続に失敗した場合はFALSEを返す
    if (!mysql_select_db($this->dbn)) {
      mysql_close($dbHandle);
      return FALSE;
    }
    $rs = mysql_query($sql);
    mysql_close($dbHandle);

    return $rs;
  }
  
}
?>

twukahtml.php

<?php
/**
 * HTMLを出力するクラス
 */
class TwUkaHTML {

  private $title;

  /**
   * メンバ変数に値をセットします。
   *
   * @param string $title 投稿ページのタイトル
   */
  public function set_param($title) {
    $this->title    = $title;
  }

  /**
   * ブラウザにメッセージを表示します。
   *
   * @param string $mes 表示メッセージ
   */
  public function showhtml($mes) {
    echo '<?xml version="1.0" encoding="UTF-8"?>'. "\n";
    echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'. "\n";
    echo '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja" dir="ltr">'. "\n";
    echo '<head>'. "\n";
    echo '<meta http-equiv="content-type" content="text/html;charset=UTF-8" />'. "\n";
    echo '<title>'. $this->title. '</title>'. "\n";
    echo '</head>'. "\n";
    echo '<body>'. "\n";
    echo '<h1>'. $mes. '</h1>'. "\n";
    echo '</body>'. "\n";
    echo '</html>'. "\n";
  }

}
?>

Perl5 Code For Twitter Bot "beefriends" Posting From SSP

main script

#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;
use CGI qw/:cgi/;
use Net::Twitter;

package main;

my $tw_username = '****';
my $tw_password = '****';
my $ssp_postkey = '****';
my $html_title  = 'beefriends post';

my $bee = TwUkaTrans->new;
$bee->set_param($tw_username, $tw_password, $ssp_postkey, $html_title);
$bee->trans;

{
  package TwUkaTrans;

  sub new {
    my $pkg = shift;
    bless {
      'tw_username' => undef,
      'tw_password' => undef,
      'ssp_postkey' => undef,
      'html_title'  => undef,
    },$pkg;
  }

  sub set_param {
    my $self = shift;
    ($self->{tw_username}, $self->{tw_password}, $self->{ssp_postkey}, $self->{html_title}) = @_;
  }

  sub trans {
    my $self = shift;
    my $cgi = new CGI;
    $cgi->charset('utf-8');
    my $mes = $cgi->param($self->{ssp_postkey});
    if ($mes) {
      $mes = $cgi->escapeHTML($mes);
      my $twit = Net::Twitter->new(
          username=>$self->{tw_username}
        , password=>$self->{tw_password}
      );
      my $result = $twit->update($mes);
    } else {
      $mes = Encode::encode_utf8('投稿内容が不正です');
    }
    my $html = TwUkaHTML->new;
    $html->set_param(Encode::encode_utf8($self->{html_title}));
    print $html->showhtml($mes);
  }
}

{
  package TwUkaHTML;

  sub new {
    my $pkg = shift;
    bless {
      title => undef,
    },$pkg;
  }

  sub set_param {
    my $self = shift;
    my $title = shift;
    $self->{title} = $title;
  }

  sub showhtml {
    my $self = shift;
    my $mes = shift;
    my $ret = '';
    $ret .= 'Content-type: text/html;'. "\n";
    $ret .= '<?xml version="1.0" encoding="UTF-8"?>'. "\n";
    $ret .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'. "\n";
    $ret .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">'. "\n";
    $ret .= '<head>'. "\n";
    $ret .= '<meta http-equiv="content-type" content="text/html;charset=UTF-8" />'. "\n";
    $ret .= '<title>'. $self->{title}. '</title>'. "\n";
    $ret .= '</head>'. "\n";
    $ret .= '<body>'. "\n";
    $ret .= '<h1>'. $mes. '</h1>'. "\n";
    $ret .= '</body>'. "\n";
    $ret .= '</html>'. "\n";
    return $ret;
  }
}