docker-tmserver/xaseco/includes/tmxinfosearcher.inc.php

289 lines
8.7 KiB
PHP
Raw Normal View History

2022-06-26 17:43:44 +02:00
<?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
?>