12306 source code

12306@github: http://github.com/sskaje/12306
My blog: https://sskaje.me/
/bot.php

<?php
/**
 * 12306 刷票
 * Special thx 2 @也云
 * 
 * @author sskaje sskaje@gmail.com http://weibo.com/sskaje 
 */

date_default_timezone_set('Asia/Shanghai');

define('SPBOT_12306_DECAPTCHA_MODE_INPUT',     1);
define('SPBOT_12306_DECAPTCHA_MODE_TESSERACT'2);

$config_file __DIR__ '/config.php';
if (isset(
$argv[2]) && ($cfg trim($argv[2]))) {
    if (
file_exists($cfg)) {
        
$config_file $cfg;
    } else if (
file_exists(__DIR__ "/config/{$cfg}.php")) {
        
$config_file __DIR__ "/config/{$cfg}.php";
    }
}

$config = include($config_file);

PHP_OS == "WINNT" or die("Sorry, but Windows only unless you are about to change the default image viewer program.");

$bot = new sp12306bot;
$bot->set_config($config);
$bot->init();

$bot->login();

if (!isset(
$argv[1]) || !in_array($argv[1], array('pay''simple_pay''cookie'))) {
query_ticket:
    
$t $bot->query_ticket();
    if (!isset(
$argv[1]) || !isset($t[$argv[1]])) {
        
MessageBox::msgbox('Check ur ticket now!');
        do {
            
Logger::Out('Available options: ' implode(', 'array_keys($t)) . ' E(x)it (R)etry (S2)=>Sleep 2 seconds');
            
$opt stdin();
        } while(!isset(
$t[$opt]));
        
Logger::Out("Option selected: {$opt}");
    } else {
        
$opt $argv[1];
    }
    
    
$confirm $bot->confirm($t[$opt]);
    
Logger::Out("Option selected: {$opt}");
    
    if (!
$confirm) {
        goto 
query_ticket;
    }
} else if (
$argv[1] == 'simple_pay') {
    
$bot->simple_pay($config['ticket_key'], $config['sequence_no']);
    exit;
} else if (
$argv[1] == 'cookie') {
    
$bot->show_cookie_js();
    exit;
}
Logger::Out('Finished ? ');

MessageBox::msgbox('订票成功!!!正在尝试获取支付页面!!!');

# request for pay link
$s $bot->pay_order();


define('SPBOT_12306_DECAPTCHA_MODE_INPUT',     1);
define('SPBOT_12306_DECAPTCHA_MODE_TESSERACT'2);

class 
sp12306bot
{
    protected 
$convert_path        '"C:/Program Files/ImageMagick-6.7.3-Q16/convert.exe"';
    protected 
$tesseract_path    'd:/Apps/Tesseract/tesseract.exe -l hcp -psm 7';
    protected 
$browser_path     '"C:/Program Files (x86)/Internet Explorer/iexplore.exe"';
    
    protected 
$login_user;
    protected 
$login_pass;
    protected 
$login_realname;
    
    protected 
$train_date;
    protected 
$train_from;
    protected 
$train_to;
    protected 
$train_time;
    
    protected 
$train_no;
    protected 
$train_passtype;
    protected 
$train_class;
    
    protected 
$return_train_date;
    protected 
$return_train_time;
    
    protected 
$return_train_passtype;
    protected 
$return_train_class;
    
    protected 
$single_round_type 1;# 1 单程 2 返程
    
    
protected $tickets_info = array();
    
    protected 
$seats;
    
    protected 
$seat_detail = array(
         array(
             
'seat'        =>    4,    # @see $seatnames
             
'ticket'    =>    1,
             
'name'        =>    '测试',
             
'cardtype'    =>    2,    # 1二代身份证,2一代身份证,C港澳通行证,G台湾通行证,B护照
             
'cardno'    =>    '110110800101123',
             
'mobileno'    =>    '13011112222',
         ),
    );
    
    protected 
$seat_order = array();
    
    
/**
     * 座位编号名称
     *
     * @var array
     */
    
protected $seat_names = array(
        
'S' =>  '一等包座',
        
'M' =>  '一等座',
        
'O' =>  '二等座',
        
'P' =>  '特等座',
        
'Q' =>  '观光座',
        
1   =>  '硬座',
        
2   =>  '软座',
        
3   =>  '硬卧',
        
4   =>  '软卧',
        
6   =>  '高级软卧',
        
9   =>  '商务座',
    );
    
/**
     * 查票的查询字段
     *
     * @var array
     */
    
protected $query_fields = array(
        array(
'序号'null),
        array(
'车次'null),
        array(
'始发'null),
        array(
'到达'null),
        array(
'历时'null),
        array(
'商务座'9),
        array(
'特等座''P'),
        array(
'一等座''M'),
        array(
'二等座''O'),
        array(
'高级软卧'6),
        array(
'软卧'4),
        array(
'硬卧'3),
        array(
'软座'2),
        array(
'硬座'1),
        array(
'无座'null),
        array(
'其他'null),
        array(
'购票'null),
    );
    
    protected 
$seat_field_map = array();
    
    public function 
__construct()
    {
    }
    
    public function 
init()
    {
        
$this->init_seat_field_map();
        
$this->init_curl();
        
# request for station names
        
$this->request_station_names();
    }
    
    protected function 
init_seat_field_map() 
    {
        foreach (
$this->query_fields as $k=>$v) {
            if (
$v[1] !== null) {
                
$this->seat_field_map[$v[1]] = $k;
            }
        }
    }
    
    protected 
$curl;
    
    protected function 
init_curl() 
    {
        
$header = array(
            
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            
'Accept-Language: zh-cn,zh;q=0.5',
            
'Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7',
            
'Connection: keep-alive',
            
'Cache-Control: max-age=0',
        );
    
        
$this->curl curl_init();
        
        
$cookiefile $this->get_cookie_file();
        
curl_setopt($this->curlCURLOPT_COOKIEJAR,  $cookiefile);
        
curl_setopt($this->curlCURLOPT_COOKIEFILE$cookiefile);
        
curl_setopt($this->curlCURLOPT_VERBOSE1);
        
curl_setopt($this->curlCURLOPT_USERAGENT'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0');
        
curl_setopt($this->curlCURLOPT_SSL_VERIFYPEER0);
        
curl_setopt($this->curlCURLOPT_SSL_VERIFYHOST0);
        
curl_setopt($this->curlCURLOPT_RETURNTRANSFER1);
        
curl_setopt($this->curlCURLOPT_HEADER0);
        
curl_setopt($this->curlCURLOPT_HTTPHEADER$header);
        
curl_setopt($this->curlCURLOPT_COOKIESESSION1);
        
curl_setopt($this->curlCURLOPT_TIMEOUT60);
        
curl_setopt($this->curlCURLOPT_ENCODING'gzip');
    
        return 
$this->curl;
    }
    
    protected function 
get_cookie_file()
    {
        return 
$this->login_user $this->cookiejar;
    }
    
    protected 
$cookiejar '_cookie.txt';
    
    public function 
__destruct()
    {
        
$this->flush_curl();
    }
    
    protected function 
exec_curl()
    {
        
$ret curl_exec($this->curl);
        if (
false === $ret) {
            
Logger::Out('Curl Exec failed, retrying...');
            return 
$this->exec_curl();
        } else {
            return 
$ret;
        }
    }
    
    protected function 
is_403()
    {
        
$info curl_getinfo($this->curlCURLINFO_HTTP_CODE);
        return 
$info == '403';
    }
    
    protected function 
test_403()
    {
        if (
$this->is_403()) {
            
Logger::Out('403 Forbidden');
            return 
true;
        } else {
            return 
false;
        }
    }
    protected function 
is_200()
    {
        
$info curl_getinfo($this->curlCURLINFO_HTTP_CODE);
        return 
$info == '200';
    }
    
    protected function 
flush_curl()
    {
        
curl_close($this->curl);
        if (!
file_exists($this->get_cookie_file())) {
            
Logger::Out('Cookie jar file not found');
        }
        
        
$time time() + 3600 24;
        
# update expiration
        
file_put_contents(
            
$this->get_cookie_file(),
            
str_replace(
                
"\t0\t",
                
"\t{$time}\t",
                
file_get_contents($this->get_cookie_file()) 
            )
        );
        
Logger::Out('Cookie jar updated. (Expiration only)');
    }
    
    public function 
show_cookie_js()
    {
        
$f $this->get_cookie_file();
        if (!
is_file($f)) {
            
Logger::Out("Cookie File '{$f}' Not Found.");
            return 
false;
        }
        
        
$s file($f);
        if (isset(
$s[4])) {
            for (
$i=4; isset($s[$i]); $i++) {
                
$tmp explode("\t"trim($s[$i]));
                
Logger::Out("INLINE SCRIPT: javascript:alert(document.cookie=\"{$tmp[5]}={$tmp[6]}; path=/\")");
            }
        }
    }
    
/**
     * 加载配置
     * 
     * @param array $config
     */
    
public function set_config($config)
    {
        
$this->login_user        $config['login_user'];
        
$this->login_pass        $config['login_pass'];
        
$this->login_realname    $config['login_realname'];
        
        
$this->train_date        $config['train_date'] ? : date('Y-m-d'time()+86400*11);
        
$this->train_from        $config['train_from'];
        
$this->train_to            $config['train_to'];
        
$this->train_time        $config['train_time'];
        
$this->train_no            $config['train_no'];
        
$this->train_passtype    $config['train_passtype'];
        
$this->train_class        $config['train_class'];
        
$this->seat_detail        $config['seat_detail'];
        
        
$this->single_round_type$config['single_round_type'];
        
        
$this->return_train_date        $config['return_train_date'];
        
$this->return_train_time        $config['return_train_time'];
        
        
$this->return_train_passtype    $config['return_train_passtype'];
        
$this->return_train_class        $config['return_train_class'];
        
        
$this->tickets_info        $config['tickets_info'];
        
        return 
true;
    }
    
    
/**
     * 验证码识别
     * 
     * @param string $url
     * @param int $hit
     */
    
protected function decaptcha($url$is_login=true$hit=0
    {
        
$flag_input =
            
$is_login ?
                
defined('SPBOT_12306_DECAPTCHA_LOGIN') && SPBOT_12306_DECAPTCHA_LOGIN == SPBOT_12306_DECAPTCHA_MODE_INPUT
                
:
                
defined('SPBOT_12306_DECAPTCHA_CONFIRM') && SPBOT_12306_DECAPTCHA_CONFIRM == SPBOT_12306_DECAPTCHA_MODE_INPUT
            
;
            
        
#$flag_input = 1;
        
        
curl_setopt($this->curlCURLOPT_URL$url . (strpos($url'?') === false '?' '&') . mt_rand());
        
curl_setopt($this->curlCURLOPT_TIMEOUT$flag_input 20 15);
        
curl_setopt($this->curlCURLOPT_HTTPGET1);
        
curl_setopt($this->curlCURLOPT_POST,    0);
        
        do {
            
$o $this->exec_curl();
        } while (
stripos($o'<html') !== false && false !== sleep(1));
        
        
Logger::File('out.jpg'$o);
    
        if (
$flag_input) {
            
Logger::Out("Trying to open image...");
#            exec("C:/windows/system32/mspaint.exe out.jpg");
            
exec("out.jpg");
            
Logger::Out("Input captcha you read:");
            
$decaptcha stdin();
        } else {
#            exec("{$this->convert_path} out.jpg out.tiff 2>&1 >nul");
#            exec("{$this->tesseract_path} out.tiff out 2>&1 >nul");

#            passthru("{$this->convert_path} -compress none -depth 8 -alpha off -colorspace Gray out.jpg out.tif 2>&1 >nul");
#            passthru("{$this->convert_path} out.tif -scale 110% out.tif 2>&1 >nul");
#            passthru("{$this->tesseract_path} out.tif out 2>&1");

            
exec("{$this->tesseract_path} out.jpg out 2>&1 >nul");
        
            
$decaptcha str_replace(' '''trim(file_get_contents('out.txt')));
            
Logger::Out('Decaptcha: hit ' $hit);
        }
        
        if (!
$this->decaptcha_valid($decaptcha)) {
            return 
$this->decaptcha($url$is_login, ++$hit);
        } else {
            
#rename('out.jpg', 'decaptcha_' . $decaptcha . '.jpg');
            
return $decaptcha;
        }
    }
    
/**
     * 判断验证码结果是否合法
     * 
     * @param string $decaptcha
     * @return boolean
     */
    
protected function decaptcha_valid($decaptcha
    {
        return 
preg_match('#^[A-Z0-9]{4}$#'$decaptcha);
    }
    
    protected function 
get_message($o)
    {
        
$m = array();
        if (
preg_match('#var message = "(.+)";#'$o$m)) {
            return 
$m[1];
        } else {
            return 
null;
        }
    }
    protected function 
get_formtoken($o)
    {
        
$m = array();
        if (
preg_match('#<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="(.+)">#'$o$m)) {
            return 
$m[1];
        } else {
            return 
null;
        }
    }
    protected function 
get_sequence_no($o)
    {
        
$m = array();
        if (
preg_match('#<input type="hidden" name="sequence_no" value="(.+)">#'$o$m)) {
            return 
$m[1];
        } else {
            return 
null;
        }
    }
    
    
    protected function 
is_logged_in($html '')
    {
        if (!empty(
$html)) {
            
# 比较串
            
if (strpos($html'isLogin= true')) {
                
Logger::Out('Login: Already logged in as ' $this->login_user);
                return 
true;
            } else {
                
Logger::Out('Login required. ');
                return 
false;
            }
        } else {

            
$test_login_url 'https://dynamic.12306.cn/otsweb/passengerAction.do?method=initUsualPassenger';
            
$test_login_url 'https://dynamic.12306.cn/otsweb/loginAction.do?method=initForMy12306';
            
curl_setopt($this->curlCURLOPT_URL,     $test_login_url);
            
curl_setopt($this->curlCURLOPT_REFERER$test_login_url);
            
$count_403 0;
retry_test_login:
            
$o $this->exec_curl();
            
Logger::File('test_login.txt'$o);
            if (
$this->test_403()) {
                
Logger::Out("Retry test login: " . ++$count_403);
                if (
$count_403 10) {
                    if (
$count_403 == 0) {
                        
sleep(1);
                    }
                    goto 
retry_test_login;
                }
            }

            if (!
$o || !$this->is_200()) {
                return 
false;
            } else {
                
$m = array();
                if (
null !== ($m $this->get_message($o))) {
                    
Logger::Out('Login required. ' $m);
                    return 
false;
                } else {
                    
Logger::Out('Login: Already logged in as ' $this->login_user);
                    return 
true;
                }
        }
        }
    }
    
/**
     * 登录尝试
     */
    
public function login()
    {
        if (!
$this->is_logged_in()) {
            
$login false;
            
$login_attempts 0;
            
$refresh_captcha true;
            do {
                
Logger::Out("Login Attempts: {$login_attempts}");
                if (
$refresh_captcha) {
                    
$decaptcha $this->decaptcha('https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=lrand'true);
                    
Logger::Out("Login Captcha: {$decaptcha}");
                } else {
                    
Logger::Out("Reuse Login Captcha: {$decaptcha}");
                }
                
                
$o '';
                
$login $this->do_login($decaptcha$o$refresh_captcha);
                ++ 
$login_attempts;
                
                if (
$login_attempts 10 == 0) {
                    
Logger::Out("Login Sleep: 2");
                    
sleep(2);
                }
            } while (!
$login || !$this->is_logged_in($o));
        }
        
        
# 成功后先关闭curl再重开
        
$this->flush_curl();
        
$this->init_curl();
        
        
Logger::Out('Login success.');
        return 
true;
    }
    
/**
     * 执行登录
     * 
     * @param string $decaptcha
     */
    
protected function do_login($decaptcha, &$o=null, &$refresh_captcha=true
    {
        if (empty(
$this->login_user) || empty($this->login_pass)) {
            
_err('登录用户名密码未指定');
            exit;
        }
        
Logger::Out("Login in as {$this->login_user}");
        
$postfields '';
        
$postfields .= 'loginUser.user_name='.urlencode($this->login_user);
        
$postfields .= '&nameErrorFocus=&user.password=' urlencode($this->login_pass);
        
$postfields .= '&passwordErrorFocus=&randCode='.urlencode($decaptcha);
        
$postfields .= '&randErrorFocus=';
        
        
curl_setopt($this->curlCURLOPT_REFERER'https://dynamic.12306.cn/otsweb/loginAction.do?method=init');
        
curl_setopt($this->curlCURLOPT_URL'https://dynamic.12306.cn/otsweb/loginAction.do?method=login');
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
curl_setopt($this->curlCURLOPT_TIMEOUT30);
        
$o $this->exec_curl();
        
        
Logger::File('login.html'$o);
        
        if (
$this->test_403()) {
            
sleep(1);
            return 
false;
        }
        
        if (
strpos($o'var isLogin= false')) {
            if (
strpos($o'请输入正确的验证码') !== false) {
                
$refresh_captcha true;
                
Logger::Out('Login failed: bad captcha');
            } else {
                
$refresh_captcha false;
            }
            
            if (
null !== ($m $this->get_message($o))) {
                
Logger::Out("Login failed: {$m}");
                if (
strpos($m'密码错误') !== false) {
                    exit;
                }
            }
            return 
false;
        } else {
            return 
true;
        }
    }
    
/**
     * 查询余票
     */
    
public function query_ticket()
    {
        
$ret = array();
        foreach (
$this->tickets_info as $k=>$row) {
            
$tmp explode('#'$row);
            
Logger::Out("Query ticket[preset]: [{$k}{$tmp[0]}({$row})");
            
$ret[$k] = $tmp;
        }
        return 
$ret;
    }
    
    protected 
$station_names;
    
    public function 
request_station_names() 
    {
        if (!(
$data cache_get('station_name'))) {    
            
$url 'https://dynamic.12306.cn/otsweb/js/common/station_name.js?rand='.mt_rand();
            
curl_setopt($this->curlCURLOPT_URL$url);
            
curl_setopt($this->curlCURLOPT_HTTPGET1);
            
curl_setopt($this->curlCURLOPT_POST0);
            
curl_setopt($this->curlCURLOPT_TIMEOUT60);
            
$o $this->exec_curl();
            
            
$m = array();
            if (!
preg_match('#\'(.+)\'#'$o$m)) {
                return 
false;
            }
            
$s explode('@'$m[1]);
            
$data = array();
            foreach (
$s as $_s) {
                if (!
$_s) {
                    continue;
                }
    
                
$t explode('|'$_s);
                
$data[$t[2]] = $t;
            }
    
            
cache_set('station_name'$data86400 10);
        }
        return 
$this->station_names $data;
    }
    
    protected function 
parse_submit($o, &$refresh_captcha=false)
    {
        
$refresh_captcha false;
        if (
null !== ($m $this->get_message($o))) {
            
Logger::Out('Error: ' $m);
            if (
false !== strpos($m'验证码')) {
                
$refresh_captcha true;
            } else if (
                
false === strpos($m'提交订单用户过多')
                && 
false === strpos($m'重复提交')
            ) {
                exit;
            }
        }

        return 
$this->get_formtoken($o);
    }
    
    public function 
confirm($opt)
    {
        
# request for order
        
$confirm false;
        
$confirm_attempts 0;
        
$refresh_captcha true;
        
$last_token '';
        
$token '';
        
        do {
            
Logger::Out("Confirm Attempts: {$confirm_attempts}");
            if (
$refresh_captcha) {
                
$decaptcha $this->decaptcha('https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=randp'false);
                
Logger::Out("Confirm Captcha: {$decaptcha}");
            } else {
                
Logger::Out("Reuse Confirm Captcha: {$decaptcha}");
            }
            if (
$last_token == $token || empty($token)) {
                
$token '';
                do {
                    
$r $this->fetch_incompleted();
                    
#$r = $this->fetch_incompleted('https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=init&showMessage=Y');
                    
$token $r['token'];
                     
                    if (isset(
$r['ticket_key'])) {
                        
Logger::Out("Ticket key found on incompleted order.");
                        return 
true;
                    }
                } while (!
$token);
                
Logger::Out("Read token from incompleted-orders");
            }
            
            
$last_token $token;
            
Logger::Out("Using token: {$token}");
            
$confirm $this->do_confirm_order($opt$token$decaptcha$refresh_captcha);
            
            ++ 
$confirm_attempts;
        } while (!
$confirm);
        
        return 
$confirm;
    }
    
    protected function 
do_confirm_order($req, & $token$decaptcha, &$refresh_captcha)
    {
        
# 单程票
        
$url 'https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=confirmPassengerInfoSingle';
        
        
# Z65#10:15#20:00#2400000Z6505#BXP#JJG#06:15#北京西#九江#302910002440458000026084200001
        
$post[] = 'org.apache.struts.taglib.html.TOKEN=' urlencode($token);
        
$post[] = 'orderRequest.reserve_flag=A';
        
$post[] = 'orderRequest.train_date=' urlencode($this->train_date);
        
$post[] = 'orderRequest.train_no=' urlencode($req[3]); # 240000Z13304';
        
$post[] = 'orderRequest.station_train_code=' urlencode($req[0]); #Z133';
        
$post[] = 'orderRequest.from_station_telecode=' urlencode($req[4]); #BXP';
        
$post[] = 'orderRequest.to_station_telecode=' urlencode($req[5]); #JJG';
        
$post[] = 'orderRequest.seat_type_code=';
        
$post[] = 'orderRequest.ticket_type_order_num=';
        
$post[] = 'orderRequest.bed_level_order_num=000000000000000000000000000000';
        
$post[] = 'orderRequest.start_time=' urlencode($req[2]); #19%3A45';
        
$post[] = 'orderRequest.end_time=' urlencode($req[6]); #06%3A09';
        
$post[] = 'orderRequest.from_station_name=' urlencode($req[7]); #%E5%8C%97%E4%BA%AC%E8%A5%BF';
        
$post[] = 'orderRequest.to_station_name=' urlencode($req[8]); #%E4%B9%9D%E6%B1%9F';
        
$post[] = 'orderRequest.cancel_flag=1';
        
$post[] = 'orderRequest.id_mode=Y';
        
$post[] = 'randCode=' $decaptcha;
        
        
$post[] = 'checkbox0=0';
        
$post[] = 'textfield=%E4%B8%AD%E6%96%87%E6%88%96%E6%8B%BC%E9%9F%B3%E9%A6%96%E5%AD%97%E6%AF%8D';
        
$i 0;
        foreach (
$this->seat_detail as $s) {
            ++
$i;
            
$post[] = 'passengerTickets='.urlencode("{$s['seat']},{$s['ticket']},{$s['name']},{$s['cardtype']},{$s['cardno']},{$s['mobileno']},Y");
            
$post[] = 'oldPassengers=' urlencode("{$s['name']},{$s['cardtype']},{$s['cardno']}");
            
$post[] = "passenger_{$i}_seat={$s['seat']}";
            
$post[] = "passenger_{$i}_ticket={$s['ticket']}";
            
$post[] = "passenger_{$i}_name=" urlencode($s['name']);
            
$post[] = "passenger_{$i}_cardtype={$s['cardtype']}";
            
$post[] = "passenger_{$i}_cardno={$s['cardno']}";
            
$post[] = "passenger_{$i}_mobileno={$s['mobileno']}";
        }

        for (
$c=5-$i$c>0$c--) {
            
$post[] = 'oldPassengers=&checkbox9=Y';
        }
        
$postfields implode('&'$post);
        
        
Logger::Out("post fields: {$postfields}");
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
curl_setopt($this->curlCURLOPT_REFERER'https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=init');
        
curl_setopt($this->curlCURLOPT_TIMEOUT180);
        
        
$o $this->exec_curl();
        
$msg $this->get_message($o);
        
$refresh_captcha false;
        if (
$msg) {
            
Logger::Out("Message: {$msg}");
            if (
strpos($msg'验证码') !== false) {
                
$refresh_captcha true;
            }
        }
        
# 验证码策略改了
        
$refresh_captcha true;
        
        if (
strpos($o'席位已成功锁定')) {
            
Logger::File('cfm_success.txt'$o);
            
Logger::Out('Done: 支付去吧');
            return 
$o;
        } else if (
strpos($msg'未处理的订单') !== false) {
            
MessageBox::msgbox('有未支付订单');
            return 
$o;
        } else {
            
Logger::File('cfm_fail.txt'$o);
            
$token $this->get_formtoken($o);
            return 
false;
        }
    }
    
    protected function 
fetch_incompleted($url null)
    {
        
$referer 'https://dynamic.12306.cn/otsweb/loginAction.do?method=initForMy12306';

        if (
$url === null) {
            
$url 'https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=queryMyOrderNotComplete';
        }
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_REFERER$referer);
        
curl_setopt($this->curlCURLOPT_POST0);
        
curl_setopt($this->curlCURLOPT_HTTPGET1);
        
curl_setopt($this->curlCURLOPT_TIMEOUT60);
        
        
$o $this->exec_curl();
        
Logger::File('incompleted.html'$o);

        
$m = array();
        if (
preg_match('#cancelOrder\(\'(.+)\'\)#'$o$m)) {
            
$ret = array();
            
$ret['seq'] = $m[1];
            
preg_match('#value="('.$ret['seq'].'.+)"#'$o$m);
            
$ret['ticket_key'] = $m[1];
            
$ret['token'] = $this->get_formtoken($o);
            return 
$ret;
        } else if (
null === ($m $this->get_message($o))) {
            return array(
                
'token'    =>    $this->get_formtoken($o),
            );
        } else {
            
Logger::Out('Read incompleted order failed');
            
usleep(300000);
            return 
$this->fetch_incompleted();
        }
    }
    
    public function 
pay_order()
    {
        
$ret $this->fetch_incompleted();
        if (!isset(
$ret['ticket_key']) || empty($ret['ticket_key'])) {
            
Logger::Out('Error: 没有待支付订单');
            exit;
        }
        
$url 'https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=laterEpay&orderSequence_no=' urlencode($ret['seq']) . '&con_pay_type=epay';
        
        
$postfields 'queryOrderDTO.from_order_date=&queryOrderDTO.to_order_date=';
        
$postfields .= '&org.apache.struts.taglib.html.TOKEN='.urlencode($ret['token']);
        
$postfields .= '&ticket_key='.urlencode($ret['ticket_key']);
    
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
curl_setopt($this->curlCURLOPT_TIMEOUT60);
        
        
$o $this->exec_curl();
        
Logger::File('pay_order.txt'$o);
        
        if (!
strpos($o'席位已成功锁定')) {
            
$m $this->get_message($o);
            
Logger::Out('Pay order: 支付失败,重试中...' $m);
            
usleep(500000);
            return 
$this->pay_order();
        }
        
$m = array();
        if (
preg_match('#<form id="epayForm".+</form>#sU'$o$m)) {
            
$pay_script = <<<HTML
<script type="text/javascript">
function pay() {
    var form =document.getElementById("epayForm");
        form.submit();
    }
pay();
</script>
HTML;
            
Logger::File('payorder.html'$m[0] . $pay_script);
            
            
Logger::Out('Trying to popup browser');
             
            
$cmd $this->browser_path.' '.getcwd().'/payorder.html';
            
system($cmd);
            exit;
        } else {
            
Logger::Out('Pay error: 无法找到支付表单');
            return 
$this->pay_order();
        }
        
        
$token $this->get_formtoken($o);
        
$seq   $this->get_sequence_no($o);
        
$batch_no '1#';
    
        
$postfields '';
        
$postfields .= 'org.apache.struts.taglib.html.TOKEN='.urlencode($token);
        
$postfields .= '&sequence_no='.urlencode($seq);
        
$postfields .= '&batch_no='.urlencode($batch_no);
        
        
$url 'https://dynamic.12306.cn/otsweb/order/payConfirmOnlineSingleAction.do?method=payConfirmOnlineSingleEPay';
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
        
$o $this->exec_curl();
        
Logger::File('pay_order_2.html'$o);
    
        if (!
strpos($o'interfaceName')) {
            
Logger::Out('Pay order: 支付请求提交失败,重试中...' );
            return 
$this->pay_order();
        }
        
Logger::Out('Trying to popup browser');
        
        
$cmd $this->browser_path.' '.getcwd().'/pay_order_2.html';
        
system($cmd);
        exit;
    }
    
    public function 
simple_pay($ticket_key$sequence_no$batch_no='1#')
    {
        
$token '';
        do {
            
#$r = $this->fetch_incompleted();
            
$r $this->fetch_incompleted('https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=init&showMessage=Y');
            
$token $r['token']; 
        } while (!
$token);
        
        
$postfields '';
        
$postfields .= 'org.apache.struts.taglib.html.TOKEN='.urlencode($token);
        
$postfields .= '&sequence_no='.urlencode($sequence_no);
        
$postfields .= '&batch_no='.urlencode($batch_no);
         
        
$url 'https://dynamic.12306.cn/otsweb/order/payConfirmOnlineSingleAction.do?method=payConfirmOnlineSingleEPay';
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
        
        
$o $this->exec_curl();
        
Logger::File('pay_order_2.html'$o);
        
        if (!
strpos($o'interfaceName')) {
            
Logger::Out('Pay order: 支付请求提交失败,重试中...' );
            return 
$this->simple_pay($ticket_key$sequence_no$batch_no);
        }
        
Logger::Out('Trying to popup browser');
         
        
$cmd $this->browser_path.' '.getcwd().'/pay_order_2.html';
        
system($cmd);
        exit;
    }
    
    public function 
cancel_order()
    {
        
$ret $this->fetch_incompleted();
        if (!isset(
$ret['ticket_key']) || empty($ret['ticket_key'])) {
            
Logger::Out('Error: 没有待支付订单');
            exit;
        }
        
$url 'https://dynamic.12306.cn/otsweb/order/orderAction.do?method=cancelMyOrderNotComplete';
        
$postfields '';
        
$postfields .= 'org.apache.struts.taglib.html.TOKEN='.urlencode($ret['token']);
        
$postfields .= '&sequence_no='.urlencode($ret['seq']);
        
        
curl_setopt($this->curlCURLOPT_URL$url);
        
curl_setopt($this->curlCURLOPT_POST1);
        
curl_setopt($this->curlCURLOPT_POSTFIELDS$postfields);
        
curl_setopt($this->curlCURLOPT_TIMEOUT60);
        
        
$o $this->exec_curl();
        
Logger::File('cancel.txt'$o);
        
        if (
null === ($m $this->get_message($o)) || false === strpos($m'成功')) {
            
Logger::Out('Cancel Order Failed: ' $m);
            exit;
        } else {
            
Logger::Out('Cancel Order: ' $m);
        }
        
usleep(500000);
    }
}


function 
stdin()
{
    
$opt fgets(STDIN);
    
$opt strtoupper(trim($opt));
    if (
$opt === 'X' || $opt === ':X') {
        exit;
    } else if (
$opt === ':MOFF') {
        
MessageBox::moff();
    } else if (
$opt === ':MON') {
        
MessageBox::mon();
    }
    return 
$opt;
}

class 
Logger
{
    static public function 
File($file$content)
    {
        return 
file_put_contents($file$content);
    }
    
    static public function 
Out($msg)
    {
        
$msg date('[H:i:s] ') . trim($msg) . "\r\n";
        
file_put_contents('_log_out.txt'$msgFILE_APPEND);
        return 
fwrite(
            
STDOUT
            
iconv('UTF-8''GBK'$msg)
        );
    }
    
    static public function 
Err($msg)
    {
        
$msg date('[H:i:s] ') . trim($msg) . "\r\n";
        
file_put_contents('_log_err.txt'$msgFILE_APPEND);
        return 
fwrite(
            
STDERR
            
iconv('UTF-8''GBK'$msg)
        );
    }
}

class 
MessageBox
{
    static public function 
msgbox($message)
    {
        if (
PHP_OS == 'WINNT' && self::$on) {
            
system(__DIR__ '/messagebox.vbs "' iconv('utf-8''gbk'$message) . '"');
        }
    }

    static protected 
$on true;

    static public function 
mon()
    {
        
self::$on true;
    }

    static public function 
moff()
    {
        
self::$on false;
    }
}

function 
cache_set($key$val$expire=864000)
{
    
$f cache_file($key);
    return 
file_put_contents(
        
$f,
        
serialize(array(
            
'expire'    =>  time() + $expire,
            
'data'      =>  $val,
        ))
    );
}

function 
cache_get($key)
{
    
$f cache_file($key);
    if (!
is_file($f)) {
        return 
false;
    }
    
$d unserialize(file_get_contents($f));

    if (
$d['expire'] < time()) {
        return 
false;
    } else {
        return 
$d['data'];
    }
}

function 
cache_file($key)
{
    if (!
is_dir(__DIR__ '/caches')) {
        
mkdir(__DIR__ '/caches');
    }

    return 
realpath(__DIR__ '/caches/c_' strval($key));
}

# EOF