mirror of
https://codeberg.org/demostf/api.git
synced 2026-06-03 18:04:08 +02:00
upload and tests
This commit is contained in:
parent
05f48fd0a0
commit
03d3acebf5
21 changed files with 1026 additions and 152 deletions
|
|
@ -1,89 +1,22 @@
|
||||||
<?php namespace Demostf\API\Controllers;
|
<?php namespace Demostf\API\Controllers;
|
||||||
|
|
||||||
use Demostf\API\Demo\DemoStore;
|
use Demostf\API\Providers\UploadProvider;
|
||||||
use Demostf\API\Demo\Parser;
|
|
||||||
use Demostf\API\Providers\DemoProvider;
|
|
||||||
use Demostf\API\Providers\UserProvider;
|
|
||||||
|
|
||||||
class UploadController extends BaseController {
|
class UploadController extends BaseController {
|
||||||
|
private $uploadProvider;
|
||||||
|
|
||||||
/**
|
public function __construct(UploadProvider $uploadProvider) {
|
||||||
* @var \Providers\DemoProvider
|
$this->uploadProvider = $uploadProvider;
|
||||||
*/
|
|
||||||
private $demoProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var UserProvider
|
|
||||||
*/
|
|
||||||
private $userProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Parser
|
|
||||||
*/
|
|
||||||
private $parser;
|
|
||||||
|
|
||||||
/** @var DemoStore */
|
|
||||||
private $store;
|
|
||||||
|
|
||||||
public function __construct(DemoProvider $demoProvider, UserProvider $userProvider, Parser $parser, DemoStore $store) {
|
|
||||||
$this->demoProvider = $demoProvider;
|
|
||||||
$this->userProvider = $userProvider;
|
|
||||||
$this->parser = $parser;
|
|
||||||
$this->store = $store;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function upload() {
|
public function upload() {
|
||||||
$key = $this->post('key');
|
$key = $this->post('key', '');
|
||||||
$red = $this->post('red', 'RED');
|
$red = $this->post('red', 'RED');
|
||||||
$blu = $this->post('blu', 'BLU');
|
$blu = $this->post('blu', 'BLU');
|
||||||
$name = $this->post('name', 'Unnamed');
|
$name = $this->post('name', 'Unnamed');
|
||||||
$demo = $this->file('demo');
|
$demo = $this->file('demo');
|
||||||
$user = $this->userProvider->byKey($key);
|
$demoFile = $demo['tmp_name'];
|
||||||
if (!$user) {
|
|
||||||
return 'Invalid key';
|
|
||||||
}
|
|
||||||
if (!$red) {
|
|
||||||
$red = 'RED';
|
|
||||||
}
|
|
||||||
if (!$blu) {
|
|
||||||
$blu = 'BLU';
|
|
||||||
}
|
|
||||||
|
|
||||||
$size = $demo['size'];
|
return $this->uploadProvider->upload($key, $red, $blu, $name, $demoFile);
|
||||||
if ($size < 1024) {
|
|
||||||
return 'Demos needs to be at least 1KB is size';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($size > 100 * 1024 * 1024) {
|
|
||||||
return 'Demos cant be more than 100MB in size';
|
|
||||||
}
|
|
||||||
$tmpPath = $demo['tmp_name'];
|
|
||||||
try {
|
|
||||||
$info = $this->parser->parseHeader($tmpPath);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return 'Not a valid demo';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($info->getDuration() < (5 * 60)) {
|
|
||||||
return 'Demos need to be at least 5m long';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($info->getDuration() > (60 * 60)) {
|
|
||||||
return 'Demos cant be longer than one hour';
|
|
||||||
}
|
|
||||||
|
|
||||||
$hash = hash_file('md5', $tmpPath);
|
|
||||||
|
|
||||||
$existingDemo = $this->demoProvider->demoIdByHash($hash);
|
|
||||||
if ($existingDemo) {
|
|
||||||
if ($key) {
|
|
||||||
return 'STV available at: https://demos.tf/' . $existingDemo;
|
|
||||||
} else {
|
|
||||||
\Flight::redirect('https://demos.tf/' . $existingDemo);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = $this->store->store($tmpPath, $hash . '_' . $name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
64
src/Data/ParsedDemo.php
Normal file
64
src/Data/ParsedDemo.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Data;
|
||||||
|
|
||||||
|
use Demostf\API\Demo\ChatMessage;
|
||||||
|
|
||||||
|
class ParsedDemo {
|
||||||
|
/** @var int */
|
||||||
|
private $redScore;
|
||||||
|
/** @var int */
|
||||||
|
private $blueScore;
|
||||||
|
/** @var ChatMessage[] */
|
||||||
|
private $chat;
|
||||||
|
/** @var ParsedPlayer[] */
|
||||||
|
private $players;
|
||||||
|
/** @var ParsedKill[] */
|
||||||
|
private $kills;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ParsedDemo constructor.
|
||||||
|
*
|
||||||
|
* @param int $redScore
|
||||||
|
* @param int $blueScore
|
||||||
|
* @param ChatMessage[] $chat
|
||||||
|
* @param ParsedPlayer[] $players
|
||||||
|
* @param ParsedKill[] $kills
|
||||||
|
*/
|
||||||
|
public function __construct(int $redScore, int $blueScore, array $chat, array $players, array $kills) {
|
||||||
|
$this->redScore = $redScore;
|
||||||
|
$this->blueScore = $blueScore;
|
||||||
|
$this->chat = $chat;
|
||||||
|
$this->players = $players;
|
||||||
|
$this->kills = $kills;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRedScore(): int {
|
||||||
|
return $this->redScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlueScore(): int {
|
||||||
|
return $this->blueScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ChatMessage[]
|
||||||
|
*/
|
||||||
|
public function getChat(): array {
|
||||||
|
return $this->chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ParsedPlayer[]
|
||||||
|
*/
|
||||||
|
public function getPlayers(): array {
|
||||||
|
return $this->players;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ParsedKill[]
|
||||||
|
*/
|
||||||
|
public function getKills(): array {
|
||||||
|
return $this->kills;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/Data/ParsedKill.php
Normal file
36
src/Data/ParsedKill.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Data;
|
||||||
|
|
||||||
|
class ParsedKill {
|
||||||
|
private $attackerDemoId;
|
||||||
|
|
||||||
|
private $assisterDemoId;
|
||||||
|
|
||||||
|
private $victimDemoId;
|
||||||
|
|
||||||
|
private $weapon;
|
||||||
|
|
||||||
|
public function __construct(int $attackerDemoId, int $assisterDemoId, int $victimDemoId, string $weapon) {
|
||||||
|
$this->attackerDemoId = $attackerDemoId;
|
||||||
|
$this->assisterDemoId = $assisterDemoId;
|
||||||
|
$this->victimDemoId = $victimDemoId;
|
||||||
|
$this->weapon = $weapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAttackerDemoId(): int {
|
||||||
|
return $this->attackerDemoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAssisterDemoId(): int {
|
||||||
|
return $this->assisterDemoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVictimDemoId(): int {
|
||||||
|
return $this->victimDemoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeapon(): string {
|
||||||
|
return $this->weapon;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/Data/ParsedPlayer.php
Normal file
44
src/Data/ParsedPlayer.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Data;
|
||||||
|
|
||||||
|
class ParsedPlayer {
|
||||||
|
/** @var string */
|
||||||
|
private $name;
|
||||||
|
/** @var int */
|
||||||
|
private $demoUserId;
|
||||||
|
/** @var string */
|
||||||
|
private $steamId;
|
||||||
|
/** @var string */
|
||||||
|
private $team;
|
||||||
|
/** @var string` */
|
||||||
|
private $class;
|
||||||
|
|
||||||
|
public function __construct(string $name, int $demoUserId, string $steamId, string $team, string $class) {
|
||||||
|
$this->name = $name;
|
||||||
|
$this->demoUserId = $demoUserId;
|
||||||
|
$this->steamId = $steamId;
|
||||||
|
$this->team = $team;
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDemoUserId(): int {
|
||||||
|
return $this->demoUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSteamId(): string {
|
||||||
|
return $this->steamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTeam(): string {
|
||||||
|
return $this->team;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClass(): string {
|
||||||
|
return $this->class;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/Data/StoredDemo.php
Normal file
30
src/Data/StoredDemo.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Data;
|
||||||
|
|
||||||
|
class StoredDemo {
|
||||||
|
/** @var string */
|
||||||
|
private $url;
|
||||||
|
/** @var string */
|
||||||
|
private $backend;
|
||||||
|
/** @var string */
|
||||||
|
private $path;
|
||||||
|
|
||||||
|
public function __construct(string $url, string $backend, string $path) {
|
||||||
|
$this->url = $url;
|
||||||
|
$this->backend = $backend;
|
||||||
|
$this->path = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrl(): string {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBackend(): string {
|
||||||
|
return $this->backend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string {
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/Data/Upload.php
Normal file
44
src/Data/Upload.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Data;
|
||||||
|
|
||||||
|
class Upload {
|
||||||
|
/** @var string */
|
||||||
|
private $name;
|
||||||
|
/** @var string */
|
||||||
|
private $red;
|
||||||
|
/** @var string */
|
||||||
|
private $blue;
|
||||||
|
/** @var int */
|
||||||
|
private $uploaderId;
|
||||||
|
/** @var string */
|
||||||
|
private $hash;
|
||||||
|
|
||||||
|
public function __construct(string $name, string $red, string $blue, int $uploaderId, string $hash) {
|
||||||
|
$this->name = $name;
|
||||||
|
$this->red = $red;
|
||||||
|
$this->blue = $blue;
|
||||||
|
$this->uploaderId = $uploaderId;
|
||||||
|
$this->hash = $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRed(): string {
|
||||||
|
return $this->red;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlue(): string {
|
||||||
|
return $this->blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUploaderId(): int {
|
||||||
|
return $this->uploaderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHash(): string {
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ class Demo implements \JsonSerializable {
|
||||||
private $name;
|
private $name;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $server;
|
private $server;
|
||||||
/** @var int */
|
/** @var float */
|
||||||
private $duration;
|
private $duration;
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $nick;
|
private $nick;
|
||||||
|
|
@ -44,7 +44,7 @@ class Demo implements \JsonSerializable {
|
||||||
string $url,
|
string $url,
|
||||||
string $name,
|
string $name,
|
||||||
string $server,
|
string $server,
|
||||||
int $duration,
|
float $duration,
|
||||||
string $nick,
|
string $nick,
|
||||||
string $map,
|
string $map,
|
||||||
\DateTime $time,
|
\DateTime $time,
|
||||||
|
|
@ -89,7 +89,7 @@ class Demo implements \JsonSerializable {
|
||||||
return $this->server;
|
return $this->server;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDuration(): int {
|
public function getDuration(): float {
|
||||||
return $this->duration;
|
return $this->duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +157,9 @@ class Demo implements \JsonSerializable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return DemoPlayer[]
|
||||||
|
*/
|
||||||
public function getPlayers(): array {
|
public function getPlayers(): array {
|
||||||
return $this->players;
|
return $this->players;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
src/Demo/DemoSaver.php
Normal file
90
src/Demo/DemoSaver.php
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Demo;
|
||||||
|
|
||||||
|
use Demostf\API\Data\Kill;
|
||||||
|
use Demostf\API\Data\ParsedDemo;
|
||||||
|
use Demostf\API\Data\Player;
|
||||||
|
use Demostf\API\Data\StoredDemo;
|
||||||
|
use Demostf\API\Data\Upload;
|
||||||
|
use Demostf\API\Providers\ChatProvider;
|
||||||
|
use Demostf\API\Providers\DemoProvider;
|
||||||
|
use Demostf\API\Providers\KillProvider;
|
||||||
|
use Demostf\API\Providers\PlayerProvider;
|
||||||
|
use Demostf\API\Providers\UserProvider;
|
||||||
|
|
||||||
|
class DemoSaver {
|
||||||
|
/** @var KillProvider */
|
||||||
|
private $killProvider;
|
||||||
|
/** @var PlayerProvider */
|
||||||
|
private $playerProvider;
|
||||||
|
/** @var ChatProvider */
|
||||||
|
private $chatProvider;
|
||||||
|
/** @var UserProvider */
|
||||||
|
private $userProvider;
|
||||||
|
/** @var DemoProvider */
|
||||||
|
private $demoProvider;
|
||||||
|
|
||||||
|
public function __construct(KillProvider $killProvider, PlayerProvider $playerProvider, ChatProvider $chatProvider, UserProvider $userProvider, DemoProvider $demoProvider) {
|
||||||
|
$this->killProvider = $killProvider;
|
||||||
|
$this->playerProvider = $playerProvider;
|
||||||
|
$this->chatProvider = $chatProvider;
|
||||||
|
$this->userProvider = $userProvider;
|
||||||
|
$this->demoProvider = $demoProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveDemo(ParsedDemo $demo, Header $header, StoredDemo $storedDemo, Upload $upload): int {
|
||||||
|
/** @var int[] $userMap [$demoUserId => $dbUserId] */
|
||||||
|
$userMap = [0 => 0];
|
||||||
|
|
||||||
|
$demoId = $this->demoProvider->storeDemo(new Demo(
|
||||||
|
0,
|
||||||
|
$storedDemo->getUrl(),
|
||||||
|
$upload->getName(),
|
||||||
|
$header->getServer(),
|
||||||
|
$header->getDuration(),
|
||||||
|
$header->getNick(),
|
||||||
|
$header->getMap(),
|
||||||
|
new \DateTime(),
|
||||||
|
$upload->getRed(),
|
||||||
|
$upload->getBlue(),
|
||||||
|
$demo->getRedScore(),
|
||||||
|
$demo->getBlueScore(),
|
||||||
|
count($demo->getPlayers()),
|
||||||
|
$upload->getUploaderId(),
|
||||||
|
$upload->getHash()
|
||||||
|
), $storedDemo->getBackend(), $storedDemo->getPath());
|
||||||
|
|
||||||
|
foreach ($demo->getPlayers() as $player) {
|
||||||
|
$userId = $this->userProvider->getUserId($player->getSteamId());
|
||||||
|
$userMap[$player->getDemoUserId()] = $userId;
|
||||||
|
|
||||||
|
$this->playerProvider->store(new Player(
|
||||||
|
0,
|
||||||
|
$demoId,
|
||||||
|
$player->getDemoUserId(),
|
||||||
|
$userId,
|
||||||
|
$player->getName(),
|
||||||
|
$player->getTeam(),
|
||||||
|
$player->getClass()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($demo->getKills() as $kill) {
|
||||||
|
$this->killProvider->store(new Kill(
|
||||||
|
0,
|
||||||
|
$demoId,
|
||||||
|
$userMap[$kill->getAttackerDemoId()],
|
||||||
|
$userMap[$kill->getAssisterDemoId()],
|
||||||
|
$userMap[$kill->getVictimDemoId()],
|
||||||
|
$kill->getWeapon()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($demo->getChat() as $chat) {
|
||||||
|
$this->chatProvider->storeChatMessage($demoId, $chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $demoId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<?php namespace Demostf\API\Demo;
|
<?php namespace Demostf\API\Demo;
|
||||||
|
|
||||||
|
use Demostf\API\Data\StoredDemo;
|
||||||
|
|
||||||
class DemoStore {
|
class DemoStore {
|
||||||
/** @var string */
|
/** @var string */
|
||||||
private $root;
|
private $root;
|
||||||
|
|
@ -11,13 +13,13 @@ class DemoStore {
|
||||||
$this->webroot = $webroot;
|
$this->webroot = $webroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(string $sourcePath, string $name): string {
|
public function store(string $sourcePath, string $name): StoredDemo {
|
||||||
$target = $this->generatePath($name);
|
$target = $this->generatePath($name);
|
||||||
if (!is_dir(dirname($target))) {
|
if (!is_dir(dirname($target))) {
|
||||||
mkdir(dirname($target), 0777, true);
|
mkdir(dirname($target), 0777, true);
|
||||||
}
|
}
|
||||||
rename($sourcePath, $target);
|
rename($sourcePath, $target);
|
||||||
return $this->getUrl($name);
|
return new StoredDemo($this->getUrl($name), 'static', $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generatePath(string $name): string {
|
private function generatePath(string $name): string {
|
||||||
|
|
|
||||||
|
|
@ -61,21 +61,30 @@ class Header {
|
||||||
*/
|
*/
|
||||||
protected $sigon;
|
protected $sigon;
|
||||||
|
|
||||||
/**
|
public function __construct(
|
||||||
* @param array $info
|
string $type,
|
||||||
*/
|
int $version,
|
||||||
public function __construct($info) {
|
int $protocol,
|
||||||
$this->type = $info['type'];
|
string $server,
|
||||||
$this->version = $info['version'];
|
string $nick,
|
||||||
$this->protocol = $info['protocol'];
|
string $map,
|
||||||
$this->server = $info['server'];
|
string $game,
|
||||||
$this->nick = $info['nick'];
|
float $duration,
|
||||||
$this->map = $info['map'];
|
int $ticks,
|
||||||
$this->game = $info['game'];
|
int $frames,
|
||||||
$this->duration = $info['duration'];
|
int $sigon
|
||||||
$this->ticks = $info['ticks'];
|
) {
|
||||||
$this->frames = $info['frames'];
|
$this->type = $type;
|
||||||
$this->sigon = $info['sigon'];
|
$this->version = $version;
|
||||||
|
$this->protocol = $protocol;
|
||||||
|
$this->server = $server;
|
||||||
|
$this->nick = $nick;
|
||||||
|
$this->map = $map;
|
||||||
|
$this->game = $game;
|
||||||
|
$this->duration = $duration;
|
||||||
|
$this->ticks = $ticks;
|
||||||
|
$this->frames = $frames;
|
||||||
|
$this->sigon = $sigon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDuration(): float {
|
public function getDuration(): float {
|
||||||
|
|
@ -122,4 +131,19 @@ class Header {
|
||||||
return $this->version;
|
return $this->version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function fromArray(array $info) {
|
||||||
|
return new Header(
|
||||||
|
$info['type'],
|
||||||
|
$info['version'],
|
||||||
|
$info['protocol'],
|
||||||
|
$info['server'],
|
||||||
|
$info['nick'],
|
||||||
|
$info['map'],
|
||||||
|
$info['game'],
|
||||||
|
$info['duration'],
|
||||||
|
$info['ticks'],
|
||||||
|
$info['frames'],
|
||||||
|
$info['sigon']
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class HeaderParser {
|
||||||
if (!isset($info['type']) || $info['type'] !== 'HL2DEMO') {
|
if (!isset($info['type']) || $info['type'] !== 'HL2DEMO') {
|
||||||
throw new \InvalidArgumentException('Not an HL2 demo');
|
throw new \InvalidArgumentException('Not an HL2 demo');
|
||||||
}
|
}
|
||||||
return new Header($info);
|
return Header::fromArray($info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
namespace Demostf\API\Demo;
|
namespace Demostf\API\Demo;
|
||||||
|
|
||||||
|
use Demostf\API\Data\ParsedDemo;
|
||||||
|
use Demostf\API\Data\ParsedKill;
|
||||||
|
use Demostf\API\Data\ParsedPlayer;
|
||||||
|
use Demostf\API\Data\Player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Higher level parser
|
* Higher level parser
|
||||||
*
|
*
|
||||||
|
|
@ -27,7 +32,7 @@ class Parser {
|
||||||
$this->rawParser = $rawParser;
|
$this->rawParser = $rawParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function analyse(string $path): array {
|
public function analyse(string $path): ParsedDemo {
|
||||||
$data = $this->rawParser->parse($path);
|
$data = $this->rawParser->parse($path);
|
||||||
if (!is_array($data)) {
|
if (!is_array($data)) {
|
||||||
throw new \InvalidArgumentException('Error parsing demo');
|
throw new \InvalidArgumentException('Error parsing demo');
|
||||||
|
|
@ -35,11 +40,13 @@ class Parser {
|
||||||
return $this->handleData($data);
|
return $this->handleData($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleData(array $data) {
|
private function handleData(array $data): ParsedDemo {
|
||||||
$intervalPerTick = $data['intervalPerTick'];
|
$intervalPerTick = $data['intervalPerTick'];
|
||||||
$red = 0;
|
$red = 0;
|
||||||
$blue = 0;
|
$blue = 0;
|
||||||
|
/** @var ChatMessage[] $chat */
|
||||||
$chat = [];
|
$chat = [];
|
||||||
|
/** @var ParsedPlayer[] $players */
|
||||||
$players = [];
|
$players = [];
|
||||||
foreach ($data['rounds'] as $round) {
|
foreach ($data['rounds'] as $round) {
|
||||||
if ($round['winner'] === 'red') {
|
if ($round['winner'] === 'red') {
|
||||||
|
|
@ -51,11 +58,7 @@ class Parser {
|
||||||
|
|
||||||
foreach ($data['chat'] as $message) {
|
foreach ($data['chat'] as $message) {
|
||||||
if (isset($message['from'])) {
|
if (isset($message['from'])) {
|
||||||
$chat[] = [
|
$chat[] = new ChatMessage($message['from'], (int)floor(($message['tick'] - $data['startTick']) * $intervalPerTick), $message['text']);
|
||||||
'time' => floor(($message['tick'] - $data['startTick']) * $intervalPerTick),
|
|
||||||
'from' => $message['from'],
|
|
||||||
'text' => $message['text']
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,28 +72,59 @@ class Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($class && $player['steamId']) {//skip spectators
|
if ($class && $player['steamId']) {//skip spectators
|
||||||
$players[] = [
|
$players[] = new ParsedPlayer(
|
||||||
'name' => $player['name'],
|
$player['name'],
|
||||||
'demo_user_id' => $player['userId'],
|
$player['userId'],
|
||||||
'steam_id' => $player['steamId'],
|
$this->convertSteamIdToCommunityId($player['steamId']),
|
||||||
'team' => $player['team'],
|
$player['team'],
|
||||||
'class' => $this->getClassName($class)
|
$this->getClassName($class)
|
||||||
];
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
$kills = array_map(function (array $death) {
|
||||||
'score' => [
|
return new ParsedKill($death['killer'] ?? 0, $death['assister'] ?? 0, $death['victim'] ?? 0, $death['weapon']);
|
||||||
'red' => $red,
|
}, $data['deaths']);
|
||||||
'blue' => $blue
|
|
||||||
],
|
return new ParsedDemo(
|
||||||
'chat' => $chat,
|
$red,
|
||||||
'players' => $players,
|
$blue,
|
||||||
'kills' => $data['deaths']
|
$chat,
|
||||||
];
|
$players,
|
||||||
|
$kills
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getClassName(int $classId): string {
|
private function getClassName(int $classId): string {
|
||||||
return self::CLASSES[$classId] ?? 'Unknown';
|
return self::CLASSES[$classId] ?? 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credit to https://github.com/koraktor/steam-condenser-php
|
||||||
|
*
|
||||||
|
* Converts a SteamID as reported by game servers to a 64bit numeric
|
||||||
|
* SteamID as used by the Steam Community
|
||||||
|
*
|
||||||
|
* @param string $steamId The SteamID string as used on servers, like
|
||||||
|
* <var>STEAM_0:0:12345</var>
|
||||||
|
* @return string The converted 64bit numeric SteamID
|
||||||
|
* @throws \InvalidArgumentException if the SteamID doesn't have the correct
|
||||||
|
* format
|
||||||
|
*/
|
||||||
|
public function convertSteamIdToCommunityId($steamId) {
|
||||||
|
if ($steamId === 'STEAM_ID_LAN' || $steamId === 'BOT') {
|
||||||
|
throw new \InvalidArgumentException("Cannot convert SteamID \"$steamId\" to a community ID.");
|
||||||
|
}
|
||||||
|
if (preg_match('/^STEAM_[0-1]:[0-1]:[0-9]+$/', $steamId)) {
|
||||||
|
$steamParts = explode(':', substr($steamId, 8));
|
||||||
|
$steamId = $steamParts[0] + $steamParts[1] * 2 + 1197960265728;
|
||||||
|
return '7656' . $steamId;
|
||||||
|
} else if (preg_match('/^\[U:[0-1]:[0-9]+\]$/', $steamId)) {
|
||||||
|
$steamParts = explode(':', substr($steamId, 3, -1));
|
||||||
|
$steamId = $steamParts[0] + $steamParts[1] + 1197960265727;
|
||||||
|
return '7656' . $steamId;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException("SteamID \"$steamId\" doesn't have the correct format.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?php declare(strict_types = 1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Demostf\API\Providers;
|
namespace Demostf\API\Providers;
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ class DemoProvider extends BaseProvider {
|
||||||
'red' => $query->createNamedParameter($demo->getRed()),
|
'red' => $query->createNamedParameter($demo->getRed()),
|
||||||
'blu' => $query->createNamedParameter($demo->getBlue()),
|
'blu' => $query->createNamedParameter($demo->getBlue()),
|
||||||
'uploader' => $query->createNamedParameter($demo->getUploader(), \PDO::PARAM_INT),
|
'uploader' => $query->createNamedParameter($demo->getUploader(), \PDO::PARAM_INT),
|
||||||
'duration' => $query->createNamedParameter($demo->getDuration(), \PDO::PARAM_INT),
|
'duration' => $query->createNamedParameter((int)$demo->getDuration(), \PDO::PARAM_INT),
|
||||||
'created_at' => $query->createNamedParameter($demo->getTime()->format(\DATE_ATOM)),
|
'created_at' => $query->createNamedParameter($demo->getTime()->format(\DATE_ATOM)),
|
||||||
'updated_at' => 'now()',
|
'updated_at' => 'now()',
|
||||||
'backend' => $query->createNamedParameter($backend),
|
'backend' => $query->createNamedParameter($backend),
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,109 @@
|
||||||
<?php namespace Demostf\API\Providers;
|
<?php namespace Demostf\API\Providers;
|
||||||
|
|
||||||
|
use Demostf\API\Data\DemoPlayer;
|
||||||
|
use Demostf\API\Data\ParsedDemo;
|
||||||
|
use Demostf\API\Data\Upload;
|
||||||
|
use Demostf\API\Data\User;
|
||||||
|
use Demostf\API\Demo\DemoSaver;
|
||||||
|
use Demostf\API\Demo\DemoStore;
|
||||||
|
use Demostf\API\Demo\Header;
|
||||||
|
use Demostf\API\Demo\HeaderParser;
|
||||||
|
use Demostf\API\Demo\Parser;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use RandomLib\Generator;
|
use RandomLib\Generator;
|
||||||
|
|
||||||
class UploadProvider extends BaseProvider {
|
class UploadProvider extends BaseProvider {
|
||||||
/**
|
/** @var Generator */
|
||||||
* @var Generator
|
|
||||||
*/
|
|
||||||
private $generator;
|
private $generator;
|
||||||
|
/** @var HeaderParser */
|
||||||
|
private $headerParser;
|
||||||
|
/** @var Parser */
|
||||||
|
private $parser;
|
||||||
|
/** @var DemoStore */
|
||||||
|
private $store;
|
||||||
|
/** @var UserProvider */
|
||||||
|
private $userProvider;
|
||||||
|
/** @var DemoProvider */
|
||||||
|
private $demoProvider;
|
||||||
|
/** @var DemoSaver */
|
||||||
|
private $demoSaver;
|
||||||
|
private $baseUrl;
|
||||||
|
|
||||||
public function __construct(Connection $db, Generator $generator) {
|
public function __construct(Connection $db,
|
||||||
|
string $baseUrl,
|
||||||
|
HeaderParser $headerParser,
|
||||||
|
Parser $parser,
|
||||||
|
DemoStore $store,
|
||||||
|
UserProvider $userProvider,
|
||||||
|
DemoProvider $demoProvider,
|
||||||
|
DemoSaver $demoSaver
|
||||||
|
) {
|
||||||
parent::__construct($db);
|
parent::__construct($db);
|
||||||
$this->generator = $generator;
|
$this->baseUrl = $baseUrl;
|
||||||
|
$this->headerParser = $headerParser;
|
||||||
|
$this->parser = $parser;
|
||||||
|
$this->store = $store;
|
||||||
|
$this->userProvider = $userProvider;
|
||||||
|
$this->demoProvider = $demoProvider;
|
||||||
|
$this->demoSaver = $demoSaver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function upload(string $key, string $red, string $blu, string $name, string $demoFile): string {
|
||||||
|
$user = $this->userProvider->byKey($key);
|
||||||
|
if (!$user) {
|
||||||
|
return 'Invalid key';
|
||||||
|
}
|
||||||
|
|
||||||
|
$hash = hash_file('md5', $demoFile);
|
||||||
|
|
||||||
|
$existingDemo = $this->demoProvider->demoIdByHash($hash);
|
||||||
|
if ($existingDemo) {
|
||||||
|
return 'STV available at: ' . $this->baseUrl . '/' . $existingDemo;
|
||||||
|
}
|
||||||
|
|
||||||
|
$header = $this->headerParser->parseHeader($demoFile);
|
||||||
|
$error = $this->validateHeader(filesize($demoFile), $header);
|
||||||
|
if ($error) {
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $this->parser->analyse($demoFile);
|
||||||
|
|
||||||
|
$error = $this->validateParsed($header, $parsed);
|
||||||
|
if ($error) {
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$storedDemo = $this->store->store($demoFile, $hash . '_' . $name);
|
||||||
|
$upload = new Upload($name, $red, $blu, $user->getId(), $hash);
|
||||||
|
|
||||||
|
$id = $this->demoSaver->saveDemo($parsed, $header, $storedDemo, $upload);
|
||||||
|
|
||||||
|
return 'STV available at: ' . $this->baseUrl . '/' . $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateHeader(int $size, Header $header) {
|
||||||
|
if ($size < 1024) {
|
||||||
|
return 'Demos needs to be at least 1KB is size';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size > 100 * 1024 * 1024) {
|
||||||
|
return 'Demos cant be more than 100MB in size';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($header->getDuration() > (60 * 60)) {
|
||||||
|
return 'Demos cant be longer than one hour';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateParsed(Header $header, ParsedDemo $parsedDemo) {
|
||||||
|
$rounds = $parsedDemo->getRedScore() + $parsedDemo->getBlueScore();
|
||||||
|
if ($rounds === 0 && $header->getDuration() < (5 * 60)) {
|
||||||
|
return 'Demos must be at least 5 minutes long';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,4 +92,15 @@ class UserProvider extends BaseProvider {
|
||||||
$row = $query->execute()->fetch();
|
$row = $query->execute()->fetch();
|
||||||
return $row ? User::fromRow($row) : null;
|
return $row ? User::fromRow($row) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUserId(string $steamId) {
|
||||||
|
$existing = $this->get($steamId);
|
||||||
|
if ($existing) {
|
||||||
|
return $existing->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->store(new \SteamId($steamId));
|
||||||
|
|
||||||
|
return $this->get($steamId)->getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
112
tests/Demo/DemoSaverTest.php
Normal file
112
tests/Demo/DemoSaverTest.php
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Test\Demo;
|
||||||
|
|
||||||
|
use Demostf\API\Data\ParsedDemo;
|
||||||
|
use Demostf\API\Data\ParsedKill;
|
||||||
|
use Demostf\API\Data\ParsedPlayer;
|
||||||
|
use Demostf\API\Data\StoredDemo;
|
||||||
|
use Demostf\API\Data\Upload;
|
||||||
|
use Demostf\API\Demo\ChatMessage;
|
||||||
|
use Demostf\API\Demo\DemoSaver;
|
||||||
|
use Demostf\API\Demo\Header;
|
||||||
|
use Demostf\API\Providers\ChatProvider;
|
||||||
|
use Demostf\API\Providers\DemoProvider;
|
||||||
|
use Demostf\API\Providers\KillProvider;
|
||||||
|
use Demostf\API\Providers\PlayerProvider;
|
||||||
|
use Demostf\API\Providers\UserProvider;
|
||||||
|
use Demostf\API\Test\TestCase;
|
||||||
|
|
||||||
|
class DemoSaverTest extends TestCase {
|
||||||
|
public function testSave() {
|
||||||
|
|
||||||
|
$steamId1 = $this->getSteamId('1234567', 'user1');
|
||||||
|
$steamId2 = $this->getSteamId('2345678', 'user2');
|
||||||
|
|
||||||
|
$userProvider = new UserProvider($this->getDatabaseConnection(), $this->getRandomGenerator());
|
||||||
|
$demoProvider = new DemoProvider($this->getDatabaseConnection());
|
||||||
|
$chatProvider = new ChatProvider($this->getDatabaseConnection());
|
||||||
|
|
||||||
|
$userProvider->store($steamId1);
|
||||||
|
$userProvider->store($steamId2);
|
||||||
|
|
||||||
|
$upload = new Upload(
|
||||||
|
'foodemo',
|
||||||
|
'DER',
|
||||||
|
'ULB',
|
||||||
|
$userProvider->getUserId('2345678'),
|
||||||
|
'securehash'
|
||||||
|
);
|
||||||
|
|
||||||
|
$header = new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
'My Server',
|
||||||
|
'STV',
|
||||||
|
'pl_badwater',
|
||||||
|
'tf',
|
||||||
|
60,
|
||||||
|
60 * 60,
|
||||||
|
2,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
$parsed = new ParsedDemo(2, 3,
|
||||||
|
[
|
||||||
|
new ChatMessage('user1', 12, 'msg1'),
|
||||||
|
new ChatMessage('user2', 13, 'msg2')
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ParsedPlayer('user1', 1, '1234567', 'red', 'scout'),
|
||||||
|
new ParsedPlayer('user2', 2, '2345678', 'blue', 'soldier'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
new ParsedKill(1, 0, 2, 'pan'),
|
||||||
|
new ParsedKill(1, 2, 2, 'pan'),
|
||||||
|
new ParsedKill(2, 0, 1, 'pan'),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$saver = new DemoSaver(
|
||||||
|
new KillProvider($this->getDatabaseConnection()),
|
||||||
|
new PlayerProvider($this->getDatabaseConnection()),
|
||||||
|
$chatProvider,
|
||||||
|
$userProvider,
|
||||||
|
$demoProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
$storedDemo = new StoredDemo('http://example.com/foo', 'foo', 'example');
|
||||||
|
|
||||||
|
$demoId = $saver->saveDemo($parsed, $header, $storedDemo, $upload);
|
||||||
|
|
||||||
|
$retrievedDemo = $demoProvider->get($demoId, true);
|
||||||
|
|
||||||
|
$this->assertEquals(2, $retrievedDemo->getPlayerCount());
|
||||||
|
$this->assertEquals(2, $retrievedDemo->getRedScore());
|
||||||
|
$this->assertEquals(3, $retrievedDemo->getBlueScore());
|
||||||
|
$this->assertEquals('DER', $retrievedDemo->getRed());
|
||||||
|
$this->assertEquals('ULB', $retrievedDemo->getBlue());
|
||||||
|
|
||||||
|
$this->assertEquals('user2', $retrievedDemo->getUploaderUser()->getName());
|
||||||
|
|
||||||
|
$this->assertEquals('user2', $retrievedDemo->getPlayers()[0]->getName());
|
||||||
|
$this->assertEquals(1, $retrievedDemo->getPlayers()[0]->getKills());
|
||||||
|
$this->assertEquals(1, $retrievedDemo->getPlayers()[0]->getAssists());
|
||||||
|
$this->assertEquals(2, $retrievedDemo->getPlayers()[0]->getDeaths());
|
||||||
|
$this->assertEquals('blue', $retrievedDemo->getPlayers()[0]->getTeam());
|
||||||
|
$this->assertEquals('soldier', $retrievedDemo->getPlayers()[0]->getClass());
|
||||||
|
|
||||||
|
$this->assertEquals('user1', $retrievedDemo->getPlayers()[1]->getName());
|
||||||
|
$this->assertEquals(2, $retrievedDemo->getPlayers()[1]->getKills());
|
||||||
|
$this->assertEquals(0, $retrievedDemo->getPlayers()[1]->getAssists());
|
||||||
|
$this->assertEquals(1, $retrievedDemo->getPlayers()[1]->getDeaths());
|
||||||
|
$this->assertEquals('red', $retrievedDemo->getPlayers()[1]->getTeam());
|
||||||
|
$this->assertEquals('scout', $retrievedDemo->getPlayers()[1]->getClass());
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
new ChatMessage('user1', 12, 'msg1'),
|
||||||
|
new ChatMessage('user2', 13, 'msg2')
|
||||||
|
], $chatProvider->getChat($demoId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Demostf\API\Test\Data;
|
namespace Demostf\API\Test\Demo;
|
||||||
|
|
||||||
use Demostf\API\Demo\DemoStore;
|
use Demostf\API\Demo\DemoStore;
|
||||||
use Demostf\API\Test\TestCase;
|
use Demostf\API\Test\TestCase;
|
||||||
|
|
@ -16,17 +16,16 @@ class DemoStoreTest extends TestCase {
|
||||||
$file = tempnam(sys_get_temp_dir(), 'dummy_');
|
$file = tempnam(sys_get_temp_dir(), 'dummy_');
|
||||||
file_put_contents($file, 'foobar');
|
file_put_contents($file, 'foobar');
|
||||||
|
|
||||||
$url = $demoStore->store($file, 'foodemo.dem');
|
$storedDemo = $demoStore->store($file, 'foodemo.dem');
|
||||||
|
|
||||||
$this->assertStringEndsWith('/foodemo.dem', $url);
|
$this->assertStringEndsWith('/foodemo.dem', $storedDemo->getUrl());
|
||||||
$this->assertStringStartsWith('https://static.example.com/', $url);
|
$this->assertStringStartsWith('https://static.example.com/', $storedDemo->getUrl());
|
||||||
|
$this->assertEquals('static', $storedDemo->getBackend());
|
||||||
|
|
||||||
$subPath = str_replace('https://static.example.com/', '', $url);
|
$this->assertStringEqualsFile($storedDemo->getPath(), 'foobar');
|
||||||
|
unlink($storedDemo->getPath());
|
||||||
$this->assertStringEqualsFile($targetDir . '/' . $subPath, 'foobar');
|
rmdir(dirname($storedDemo->getPath()));
|
||||||
unlink($targetDir . '/' . $subPath);
|
rmdir(dirname($storedDemo->getPath(), 2));
|
||||||
rmdir(dirname($targetDir . '/' . $subPath));
|
|
||||||
rmdir(dirname($targetDir . '/' . $subPath, 2));
|
|
||||||
rmdir($targetDir);
|
rmdir($targetDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Demostf\API\Test\Providers;
|
namespace Demostf\API\Test\Demo;
|
||||||
|
|
||||||
use Demostf\API\Demo\Header;
|
use Demostf\API\Demo\Header;
|
||||||
use Demostf\API\Demo\HeaderParser;
|
use Demostf\API\Demo\HeaderParser;
|
||||||
|
|
@ -10,19 +10,19 @@ class HeaderParserTest extends TestCase {
|
||||||
public function testParseFile() {
|
public function testParseFile() {
|
||||||
$parser = new HeaderParser();
|
$parser = new HeaderParser();
|
||||||
|
|
||||||
$expected = new Header([
|
$expected = new Header(
|
||||||
'type' => 'HL2DEMO',
|
'HL2DEMO',
|
||||||
'version' => 3,
|
3,
|
||||||
'protocol' => 24,
|
24,
|
||||||
'server' => 'UGC Highlander Match',
|
'UGC Highlander Match',
|
||||||
'nick' => 'SourceTV Demo',
|
'SourceTV Demo',
|
||||||
'map' => 'koth_product_rc8',
|
'koth_product_rc8',
|
||||||
'game' => 'tf',
|
'tf',
|
||||||
'duration' => 778.4849853515625,
|
778.4849853515625,
|
||||||
'ticks' => 51899,
|
51899,
|
||||||
'frames' => 25703,
|
25703,
|
||||||
'sigon' => 818263
|
818263
|
||||||
]);
|
);
|
||||||
$parsed = $parser->parseHeader(__DIR__ . '/../data/product.dem');
|
$parsed = $parser->parseHeader(__DIR__ . '/../data/product.dem');
|
||||||
|
|
||||||
$this->assertEquals($expected->getServer(), $parsed->getServer());
|
$this->assertEquals($expected->getServer(), $parsed->getServer());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Demostf\API\Test\Providers;
|
namespace Demostf\API\Test\Demo;
|
||||||
|
|
||||||
use Demostf\API\Demo\Parser;
|
use Demostf\API\Demo\Parser;
|
||||||
use Demostf\API\Demo\RawParser;
|
use Demostf\API\Demo\RawParser;
|
||||||
|
|
@ -30,9 +30,31 @@ class ParserTest extends TestCase {
|
||||||
|
|
||||||
$result = $parser->analyse(__DIR__ . '/../data/product.dem');
|
$result = $parser->analyse(__DIR__ . '/../data/product.dem');
|
||||||
|
|
||||||
$expected = json_decode(file_get_contents(__DIR__ . '/../data/product-analyse.json'), true);
|
$expectedRaw = json_decode(file_get_contents(__DIR__ . '/../data/product-analyse.json'), true);
|
||||||
|
|
||||||
$this->assertEquals($expected, $result);
|
$expectedChat = $expectedRaw['chat'];
|
||||||
|
$this->assertCount(count($expectedChat), $result->getChat());
|
||||||
|
$this->assertEquals($expectedChat[0]['text'], $result->getChat()[0]->getMessage());
|
||||||
|
$this->assertEquals($expectedChat[0]['time'], $result->getChat()[0]->getTime());
|
||||||
|
$this->assertEquals($expectedChat[0]['from'], $result->getChat()[0]->getUser());
|
||||||
|
|
||||||
|
$this->assertEquals($expectedRaw['score']['red'], $result->getRedScore());
|
||||||
|
$this->assertEquals($expectedRaw['score']['blue'], $result->getBlueScore());
|
||||||
|
|
||||||
|
$expectedPlayers = $expectedRaw['players'];
|
||||||
|
$this->assertCount(count($expectedPlayers), $result->getPlayers());
|
||||||
|
$this->assertEquals($expectedPlayers[0]['name'], $result->getPlayers()[0]->getName());
|
||||||
|
$this->assertEquals($expectedPlayers[0]['demo_user_id'], $result->getPlayers()[0]->getDemoUserId());
|
||||||
|
$this->assertEquals($expectedPlayers[0]['team'], $result->getPlayers()[0]->getTeam());
|
||||||
|
$this->assertEquals($expectedPlayers[0]['class'], $result->getPlayers()[0]->getClass());
|
||||||
|
$this->assertEquals($parser->convertSteamIdToCommunityId($expectedPlayers[0]['steam_id']), $result->getPlayers()[0]->getSteamId());
|
||||||
|
|
||||||
|
$expectedKills = $expectedRaw['kills'];
|
||||||
|
$this->assertCount(count($expectedKills), $result->getKills());
|
||||||
|
$this->assertEquals((int)$expectedKills[0]['killer'], $result->getKills()[0]->getAttackerDemoId());
|
||||||
|
$this->assertEquals((int)$expectedKills[0]['assister'], $result->getKills()[0]->getAssisterDemoId());
|
||||||
|
$this->assertEquals((int)$expectedKills[0]['victim'], $result->getKills()[0]->getVictimDemoId());
|
||||||
|
$this->assertEquals($expectedKills[0]['weapon'], $result->getKills()[0]->getWeapon());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,4 +74,20 @@ class ParserTest extends TestCase {
|
||||||
|
|
||||||
$parser->analyse('foo');
|
$parser->analyse('foo');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testConvertSteamIdToCommunityId() {
|
||||||
|
$parser = new Parser($this->rawParser);
|
||||||
|
|
||||||
|
$steamId64 = $parser->convertSteamIdToCommunityId('STEAM_0:0:12345');
|
||||||
|
$this->assertEquals('76561197960290418', $steamId64);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConvertUIdToCommunityId() {
|
||||||
|
$parser = new Parser($this->rawParser);
|
||||||
|
|
||||||
|
$steamId64 = $parser->convertSteamIdToCommunityId('[U:1:12345]');
|
||||||
|
$this->assertEquals('76561197960278073', $steamId64);
|
||||||
|
$steamId64 = $parser->convertSteamIdToCommunityId('[U:1:39743963]');
|
||||||
|
$this->assertEquals('76561198000009691', $steamId64);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
300
tests/Providers/UploadProviderTest.php
Normal file
300
tests/Providers/UploadProviderTest.php
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Demostf\API\Test\Providers;
|
||||||
|
|
||||||
|
use Demostf\API\Data\ParsedDemo;
|
||||||
|
use Demostf\API\Demo\Demo;
|
||||||
|
use Demostf\API\Demo\DemoSaver;
|
||||||
|
use Demostf\API\Demo\DemoStore;
|
||||||
|
use Demostf\API\Demo\Header;
|
||||||
|
use Demostf\API\Demo\HeaderParser;
|
||||||
|
use Demostf\API\Demo\Parser;
|
||||||
|
use Demostf\API\Demo\RawParser;
|
||||||
|
use Demostf\API\Providers\ChatProvider;
|
||||||
|
use Demostf\API\Providers\DemoProvider;
|
||||||
|
use Demostf\API\Providers\KillProvider;
|
||||||
|
use Demostf\API\Providers\PlayerProvider;
|
||||||
|
use Demostf\API\Providers\UploadProvider;
|
||||||
|
use Demostf\API\Providers\UserProvider;
|
||||||
|
use Demostf\API\Test\TestCase;
|
||||||
|
|
||||||
|
class UploadProviderTest extends TestCase {
|
||||||
|
/** @var RawParser */
|
||||||
|
private $rawParser;
|
||||||
|
/** @var HeaderParser */
|
||||||
|
private $headerParser;
|
||||||
|
/** @var Parser */
|
||||||
|
private $parser;
|
||||||
|
/** @var DemoStore */
|
||||||
|
private $demoStore;
|
||||||
|
/** @var UserProvider */
|
||||||
|
private $userProvider;
|
||||||
|
/** @var DemoProvider */
|
||||||
|
private $demoProvider;
|
||||||
|
/** @var DemoSaver */
|
||||||
|
private $demoSaver;
|
||||||
|
/** @var UploadProvider */
|
||||||
|
private $uploadProvider;
|
||||||
|
/** @var string */
|
||||||
|
private $tmpDir;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->rawParser = $this->getMockBuilder(RawParser::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$this->rawParser->expects($this->any())
|
||||||
|
->method('parse')
|
||||||
|
->will($this->returnCallback(function ($path) {
|
||||||
|
$jsonPath = str_replace('.dem', '-raw.json', $path);
|
||||||
|
return json_decode(file_get_contents($jsonPath), true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
$targetDir = tempnam(sys_get_temp_dir(), 'dummy_target_');
|
||||||
|
unlink($targetDir);
|
||||||
|
mkdir($targetDir);
|
||||||
|
$this->tmpDir = $targetDir;
|
||||||
|
|
||||||
|
$this->headerParser = new HeaderParser();
|
||||||
|
$this->parser = new Parser($this->rawParser);
|
||||||
|
$this->demoStore = new DemoStore($targetDir, 'example.com');
|
||||||
|
$this->userProvider = new UserProvider($this->getDatabaseConnection(), $this->getRandomGenerator());
|
||||||
|
$this->demoProvider = new DemoProvider($this->getDatabaseConnection());
|
||||||
|
$this->demoSaver = new DemoSaver(
|
||||||
|
new KillProvider($this->getDatabaseConnection()),
|
||||||
|
new PlayerProvider($this->getDatabaseConnection()),
|
||||||
|
new ChatProvider($this->getDatabaseConnection()),
|
||||||
|
$this->userProvider,
|
||||||
|
$this->demoProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->uploadProvider = new UploadProvider(
|
||||||
|
$this->getDatabaseConnection(),
|
||||||
|
'http://example.com',
|
||||||
|
$this->headerParser,
|
||||||
|
$this->parser,
|
||||||
|
$this->demoStore,
|
||||||
|
$this->userProvider,
|
||||||
|
$this->demoProvider,
|
||||||
|
$this->demoSaver
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rmdirr($dir) {
|
||||||
|
$it = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||||
|
$files = new \RecursiveIteratorIterator($it,
|
||||||
|
\RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($file->isDir()) {
|
||||||
|
rmdir($file->getRealPath());
|
||||||
|
} else {
|
||||||
|
unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rmdir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown() {
|
||||||
|
$this->rmdirr($this->tmpDir);
|
||||||
|
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateHeaderToSmall() {
|
||||||
|
$this->assertEquals('Demos needs to be at least 1KB is size', $this->uploadProvider->validateHeader(
|
||||||
|
12,
|
||||||
|
new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
'Server',
|
||||||
|
'Nick',
|
||||||
|
'cp_gullywash',
|
||||||
|
'tf',
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateHeaderToBig() {
|
||||||
|
$this->assertEquals('Demos cant be more than 100MB in size', $this->uploadProvider->validateHeader(
|
||||||
|
99999999999,
|
||||||
|
new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
'Server',
|
||||||
|
'Nick',
|
||||||
|
'cp_gullywash',
|
||||||
|
'tf',
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateHeaderToLong() {
|
||||||
|
$this->assertEquals('Demos cant be longer than one hour', $this->uploadProvider->validateHeader(
|
||||||
|
9999,
|
||||||
|
new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
'Server',
|
||||||
|
'Nick',
|
||||||
|
'cp_gullywash',
|
||||||
|
'tf',
|
||||||
|
999999,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateParsedToShortNoRounds() {
|
||||||
|
$this->assertEquals('Demos must be at least 5 minutes long', $this->uploadProvider->validateParsed(
|
||||||
|
new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
'Server',
|
||||||
|
'Nick',
|
||||||
|
'cp_gullywash',
|
||||||
|
'tf',
|
||||||
|
60,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12
|
||||||
|
),
|
||||||
|
new ParsedDemo(0, 0, [], [], [])
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidateParsedToShortRounds() {
|
||||||
|
$this->assertNull($this->uploadProvider->validateParsed(
|
||||||
|
new Header(
|
||||||
|
'HL2DEMO',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
'Server',
|
||||||
|
'Nick',
|
||||||
|
'cp_gullywash',
|
||||||
|
'tf',
|
||||||
|
60,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
12
|
||||||
|
),
|
||||||
|
new ParsedDemo(1, 0, [], [], [])
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUploadInvalidKey() {
|
||||||
|
$this->assertEquals('Invalid key',
|
||||||
|
$this->uploadProvider->upload('asdasd', 'RED', 'BLU', 'asdasd', 'asdas')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Not an HL2 demo
|
||||||
|
*/
|
||||||
|
public function testUploadNonDemo() {
|
||||||
|
file_put_contents($this->tmpDir . '/foo.dem', 'asd');
|
||||||
|
|
||||||
|
$steamId = $this->getSteamId('123', 'a');
|
||||||
|
$token = $this->userProvider->store($steamId);
|
||||||
|
|
||||||
|
$this->uploadProvider->upload($token, 'RED', 'BLU', 'asdasd', $this->tmpDir . '/foo.dem');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUploadExisting() {
|
||||||
|
$source = fopen(__DIR__ . '/../data/product.dem', 'r');
|
||||||
|
file_put_contents($this->tmpDir . '/foo.dem', fread($source, 2048));
|
||||||
|
fclose($source);
|
||||||
|
$hash = md5_file($this->tmpDir . '/foo.dem');
|
||||||
|
|
||||||
|
$id = $this->demoProvider->storeDemo(
|
||||||
|
new Demo(
|
||||||
|
0,
|
||||||
|
'a',
|
||||||
|
'b',
|
||||||
|
'c',
|
||||||
|
12,
|
||||||
|
'n',
|
||||||
|
'm',
|
||||||
|
new \DateTime(),
|
||||||
|
'r',
|
||||||
|
'b',
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
$hash
|
||||||
|
),
|
||||||
|
'test', 'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
$steamId = $this->getSteamId('123', 'a');
|
||||||
|
$token = $this->userProvider->store($steamId);
|
||||||
|
|
||||||
|
$this->assertEquals('STV available at: http://example.com/' . $id,
|
||||||
|
$this->uploadProvider->upload($token, 'RED', 'BLU', 'asdasd', $this->tmpDir . '/foo.dem')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveSteamId($steamId, $name) {
|
||||||
|
$steamId = $this->getSteamId($this->parser->convertSteamIdToCommunityId($steamId), $name);
|
||||||
|
$this->userProvider->store($steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpload() {
|
||||||
|
copy(__DIR__ . '/../data/product.dem', $this->tmpDir . '/foo.dem');
|
||||||
|
copy(__DIR__ . '/../data/product-raw.json', $this->tmpDir . '/foo-raw.json');
|
||||||
|
|
||||||
|
$steamId = $this->getSteamId('123', 'a');
|
||||||
|
$token = $this->userProvider->store($steamId);
|
||||||
|
|
||||||
|
// pre-save the names so we dont have to get them from steam
|
||||||
|
$this->saveSteamId('[U:1:64229260]', 'Icewind');
|
||||||
|
$this->saveSteamId('[U:1:115748435]', 'Foz');
|
||||||
|
$this->saveSteamId('[U:1:115754284]', 'Deity');
|
||||||
|
$this->saveSteamId('[U:1:92428736]', 'Kireek');
|
||||||
|
$this->saveSteamId('[U:1:22958903]', 'Vinegar');
|
||||||
|
$this->saveSteamId('[U:1:32061783]', 'Kimo');
|
||||||
|
$this->saveSteamId('[U:1:67502510]', 'magikarp');
|
||||||
|
$this->saveSteamId('[U:1:55128465]', 'Solar');
|
||||||
|
$this->saveSteamId('[U:1:301587080]', 'ztreak');
|
||||||
|
$this->saveSteamId('[U:1:22162172]', 'TheMasterOfDisaster');
|
||||||
|
$this->saveSteamId('[U:1:13559571]', 'Sage');
|
||||||
|
$this->saveSteamId('[U:1:71706948]', 'Sketis');
|
||||||
|
$this->saveSteamId('[U:1:157204170]', 'Pyla');
|
||||||
|
$this->saveSteamId('[U:1:30838206]', 'Heavy');
|
||||||
|
$this->saveSteamId('[U:1:174774002]', 'Soldier');
|
||||||
|
$this->saveSteamId('[U:1:92096346]', 'Fish');
|
||||||
|
$this->saveSteamId('[U:1:143626373]', 'Pendulum');
|
||||||
|
$this->saveSteamId('[U:1:30220936]', 'Jedi');
|
||||||
|
|
||||||
|
|
||||||
|
$result = $this->uploadProvider->upload($token, 'RED', 'BLU', 'foodemo', $this->tmpDir . '/foo.dem');
|
||||||
|
$this->assertStringStartsWith('STV available at: http://example.com/', $result);
|
||||||
|
|
||||||
|
$demoId = (int)substr($result, strlen('STV available at: http://example.com/'));
|
||||||
|
|
||||||
|
$demo = $this->demoProvider->get($demoId, true);
|
||||||
|
|
||||||
|
$this->assertNotNull($demo);
|
||||||
|
|
||||||
|
$this->assertEquals('koth_product_rc8', $demo->getMap());
|
||||||
|
$this->assertEquals(0, $demo->getBlueScore());
|
||||||
|
$this->assertEquals(3, $demo->getRedScore());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,4 +58,21 @@ class UserProviderTest extends TestCase {
|
||||||
|
|
||||||
$this->assertCount(0, $result);
|
$this->assertCount(0, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetIdExisting() {
|
||||||
|
$this->provider->store($this->steamId);
|
||||||
|
|
||||||
|
$user = $this->provider->get($this->steamId->getSteamId64());
|
||||||
|
|
||||||
|
$this->assertEquals($user->getId(), $this->provider->getUserId($this->steamId->getSteamId64()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetIdNew() {
|
||||||
|
$id = $this->provider->getUserId($this->steamId->getSteamId64());
|
||||||
|
|
||||||
|
$user = $this->provider->get($this->steamId->getSteamId64());
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertEquals($user->getId(), $id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue