289 lines
8.7 KiB
PHP
289 lines
8.7 KiB
PHP
|
<?php
|
||
|
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
|
||
|
|
||
|
/**
|
||
|
* TMXInfoSearcher - Search TMX info for TMO/TMS/TMN/TMU(F)/TMNF tracks
|
||
|
* Created by Xymph <tm@gamers.org>
|
||
|
* Based on TMXInfoFetcher & http://united.tm-exchange.com/main.aspx?action=threadshow&id=619302
|
||
|
*
|
||
|
* v1.7: Added Countable interface to searcher class
|
||
|
* v1.6: Fixed an error checking bug
|
||
|
* v1.5: Improved error reporting via $error
|
||
|
* v1.4: Added User-Agent to the GET request
|
||
|
* v1.3: Renamed $worldrec into $custimg to correct its meaning; fixed
|
||
|
* $replayid check
|
||
|
* v1.2: Fixed TMXInfo processing false $track
|
||
|
* v1.1: Optimized get_file URL parsing
|
||
|
* v1.0: Initial release
|
||
|
*/
|
||
|
class TMXInfoSearcher implements Iterator,Countable {
|
||
|
|
||
|
public $error;
|
||
|
protected $tracks = array();
|
||
|
private $section;
|
||
|
private $prefix;
|
||
|
|
||
|
/**
|
||
|
* Searches TMX for tracks matching name, author and/or environment;
|
||
|
* or search TMX for the 10 most recent tracks
|
||
|
*
|
||
|
* @param String $game
|
||
|
* TMX section for 'TMO', 'TMS', 'TMN', 'TMU', 'TMNF'
|
||
|
* @param String $name
|
||
|
* The track name to search for (partial, case-insensitive match)
|
||
|
* @param String $author
|
||
|
* The track author to search for (partial, case-insensitive match)
|
||
|
* @param String $env
|
||
|
* The environment to search for (exact case-insensitive match
|
||
|
* from: Desert, Snow, Rally, Bay, Coast, Island, Stadium);
|
||
|
* ignored when searching TMN or TMNF
|
||
|
* @param Boolean $recent
|
||
|
* If true, ignore search parameters and just return 10 newest tracks
|
||
|
* (max. one per author)
|
||
|
* @return TMXInfoSearcher
|
||
|
* If ->valid() is false, no matching track was found;
|
||
|
* otherwise, an iterator of TMXInfo objects for a 'foreach' loop.
|
||
|
* Returns at most 500 tracks ($maxpage * 20) for TMNF/TMU(F),
|
||
|
* and at most 20 tracks for the other TMX sections.
|
||
|
*/
|
||
|
public function __construct($game, $name, $author, $env, $recent) {
|
||
|
|
||
|
$this->section = $game;
|
||
|
switch ($game) {
|
||
|
case 'TMO':
|
||
|
$this->prefix = 'original';
|
||
|
break;
|
||
|
case 'TMS':
|
||
|
$this->prefix = 'sunrise';
|
||
|
break;
|
||
|
case 'TMN':
|
||
|
$this->prefix = 'nations';
|
||
|
$env = ''; // ignore possible environment
|
||
|
break;
|
||
|
case 'TMU':
|
||
|
$this->prefix = 'united';
|
||
|
break;
|
||
|
case 'TMNF':
|
||
|
$this->prefix = 'tmnforever';
|
||
|
$env = ''; // ignore possible environment
|
||
|
break;
|
||
|
default:
|
||
|
$this->prefix = '';
|
||
|
$this->error = 'Unknown TMX section: ' . $game;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$this->error = '';
|
||
|
if ($recent) {
|
||
|
$this->tracks = $this->getRecent();
|
||
|
} else {
|
||
|
$this->tracks = $this->getList($name, $author, $env);
|
||
|
}
|
||
|
} // __construct
|
||
|
|
||
|
// define standard Iterator functions
|
||
|
public function rewind() {
|
||
|
reset($this->tracks);
|
||
|
}
|
||
|
public function current() {
|
||
|
return new TMXInfo($this->section, $this->prefix, current($this->tracks));
|
||
|
}
|
||
|
public function next() {
|
||
|
return new TMXInfo($this->section, $this->prefix, next($this->tracks));
|
||
|
}
|
||
|
public function key() {
|
||
|
return key($this->tracks);
|
||
|
}
|
||
|
public function valid() {
|
||
|
return (current($this->tracks) !== false);
|
||
|
}
|
||
|
// define standard Countable function
|
||
|
public function count() {
|
||
|
return count($this->tracks);
|
||
|
}
|
||
|
|
||
|
private function getRecent() {
|
||
|
|
||
|
// get 10 most recent tracks
|
||
|
$url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apirecent';
|
||
|
$file = $this->get_file($url);
|
||
|
if ($file === false) {
|
||
|
$this->error = 'Connection or response error on ' . $url;
|
||
|
return array();
|
||
|
} else if ($file === -1) {
|
||
|
$this->error = 'Timed out while reading data from ' . $url;
|
||
|
return array();
|
||
|
} else if ($file == '') {
|
||
|
$this->error = 'No data returned from ' . $url;
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
// check for API error message
|
||
|
if (strpos($file, chr(27)) !== false) {
|
||
|
$this->error = 'Cannot decode recent track info from ' . $url;
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
// return list of tracks as array of strings
|
||
|
return explode("\r\n", $file);
|
||
|
} // getRecent
|
||
|
|
||
|
private function getList($name, $author, $env) {
|
||
|
|
||
|
// older TMX sections don't support multi-page results :(
|
||
|
$maxpage = 1;
|
||
|
if ($this->prefix == 'tmnforever' || $this->prefix == 'united')
|
||
|
$maxpage = 25; // max. 500 tracks
|
||
|
|
||
|
// compile search URL
|
||
|
$url = 'http://' . $this->prefix . '.tm-exchange.com/apiget.aspx?action=apisearch';
|
||
|
if ($name != '')
|
||
|
$url .= '&track=' . $name;
|
||
|
if ($author != '')
|
||
|
$url .= '&author=' . $author;
|
||
|
if ($env != '')
|
||
|
$url .= '&env=' . $env;
|
||
|
$url .= '&page=';
|
||
|
|
||
|
$tracks = '';
|
||
|
$page = 0;
|
||
|
$done = false;
|
||
|
|
||
|
// get results 20 tracks at a time
|
||
|
while ($page < $maxpage && !$done) {
|
||
|
$file = $this->get_file($url . $page);
|
||
|
if ($file === false) {
|
||
|
$this->error = 'Connection or response error on ' . $url;
|
||
|
return array();
|
||
|
} else if ($file === -1) {
|
||
|
$this->error = 'Timed out while reading data from ' . $url;
|
||
|
return array();
|
||
|
} else if ($file == '') {
|
||
|
if ($tracks == '') {
|
||
|
$this->error = 'No data returned from ' . $url;
|
||
|
return array();
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check for API error message
|
||
|
if (strpos($file, chr(27)) !== false) {
|
||
|
$this->error = 'Cannot decode searched track info from ' . $url;
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
// check for results
|
||
|
if ($file != '') {
|
||
|
// no line break before first page
|
||
|
$tracks .= ($page++ > 0 ? "\r\n" : '') . $file;
|
||
|
} else {
|
||
|
$done = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return list of tracks as array of strings
|
||
|
if ($tracks != '')
|
||
|
return explode("\r\n", $tracks);
|
||
|
else
|
||
|
return array();
|
||
|
} // getList
|
||
|
|
||
|
// Simple HTTP Get function with timeout
|
||
|
// ok: return string || error: return false || timeout: return -1
|
||
|
private function get_file($url) {
|
||
|
|
||
|
$url = parse_url($url);
|
||
|
$port = isset($url['port']) ? $url['port'] : 80;
|
||
|
$query = isset($url['query']) ? "?" . $url['query'] : "";
|
||
|
|
||
|
$fp = @fsockopen($url['host'], $port, $errno, $errstr, 4);
|
||
|
if (!$fp)
|
||
|
return false;
|
||
|
|
||
|
fwrite($fp, 'GET ' . $url['path'] . $query . " HTTP/1.0\r\n" .
|
||
|
'Host: ' . $url['host'] . "\r\n" .
|
||
|
'User-Agent: TMXInfoSearcher (' . PHP_OS . ")\r\n\r\n");
|
||
|
stream_set_timeout($fp, 2);
|
||
|
$res = '';
|
||
|
$info['timed_out'] = false;
|
||
|
while (!feof($fp) && !$info['timed_out']) {
|
||
|
$res .= fread($fp, 512);
|
||
|
$info = stream_get_meta_data($fp);
|
||
|
}
|
||
|
fclose($fp);
|
||
|
|
||
|
if ($info['timed_out']) {
|
||
|
return -1;
|
||
|
} else {
|
||
|
if (substr($res, 9, 3) != '200')
|
||
|
return false;
|
||
|
$page = explode("\r\n\r\n", $res, 2);
|
||
|
return trim($page[1]);
|
||
|
}
|
||
|
} // get_file
|
||
|
} // class TMXInfoSearcher
|
||
|
|
||
|
|
||
|
class TMXInfo {
|
||
|
|
||
|
public $section, $prefix, $id, $name, $userid, $author,
|
||
|
$type, $envir, $mood, $style, $routes, $length, $diffic,
|
||
|
$lbrating, $awards, $comments, $custimg, $game, $uploaded, $updated,
|
||
|
$pageurl, $replayid, $replayurl, $imageurl, $thumburl, $dloadurl;
|
||
|
|
||
|
/**
|
||
|
* Returns track object with a hell of a lot of data from TMX track string
|
||
|
*
|
||
|
* @param String $section
|
||
|
* TMX section
|
||
|
* @param String $prefix
|
||
|
* TMX URL prefix
|
||
|
* @param String $track
|
||
|
* The TMX track string from TMXInfoSearcher
|
||
|
* @return TMXInfo
|
||
|
*/
|
||
|
public function TMXInfo($section, $prefix, $track) {
|
||
|
|
||
|
$this->section = $section;
|
||
|
$this->prefix = $prefix;
|
||
|
if ($track) {
|
||
|
// separate columns on Tabs
|
||
|
$fields = explode(chr(9), $track);
|
||
|
|
||
|
$this->id = $fields[0];
|
||
|
$this->name = $fields[1];
|
||
|
$this->userid = $fields[2];
|
||
|
$this->author = $fields[3];
|
||
|
$this->type = $fields[4];
|
||
|
$this->envir = $fields[5];
|
||
|
$this->mood = $fields[6];
|
||
|
$this->style = $fields[7];
|
||
|
$this->routes = $fields[8];
|
||
|
$this->length = $fields[9];
|
||
|
$this->diffic = $fields[10];
|
||
|
$this->lbrating = ($fields[11] > 0 ? $fields[11] : 'Classic!');
|
||
|
$this->awards = $fields[12];
|
||
|
$this->comments = $fields[13];
|
||
|
$this->custimg = (strtolower($fields[14]) == 'true');
|
||
|
$this->game = $fields[15];
|
||
|
$this->replayid = $fields[16];
|
||
|
// unknown = $fields[17-21];
|
||
|
$this->uploaded = $fields[22];
|
||
|
$this->updated = $fields[23];
|
||
|
|
||
|
$this->pageurl = 'http://' . $prefix . '.tm-exchange.com/main.aspx?action=trackshow&id=' . $this->id;
|
||
|
$this->imageurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreen&id=' . $this->id;
|
||
|
$this->thumburl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackscreensmall&id=' . $this->id;
|
||
|
$this->dloadurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=trackgbx&id=' . $this->id;
|
||
|
|
||
|
if ($this->replayid > 0) {
|
||
|
$this->replayurl = 'http://' . $prefix . '.tm-exchange.com/get.aspx?action=recordgbx&id=' . $this->replayid;
|
||
|
} else {
|
||
|
$this->replayurl = '';
|
||
|
}
|
||
|
}
|
||
|
} // TMXInfo
|
||
|
} // class TMXInfo
|
||
|
?>
|