WIP: Fortnite Support

parent a147b76a
Pipeline #1329 passed with stages
in 3 minutes and 35 seconds
......@@ -2,7 +2,9 @@
namespace Onyx\Fortnite;
use Onyx\Account;
use Onyx\Fortnite\Helpers\Network\Http;
use Onyx\Fortnite\Objects\Stats;
/**
* Class Client.
......@@ -10,22 +12,141 @@ use Onyx\Fortnite\Helpers\Network\Http;
class Client extends Http
{
/**
* @var array
* @param Account $account
* @param string $id
* @return array
* @throws \Exception
*/
private $account_cached = [];
public function getAccountRoyaleStats(string $name, string $platform)
public function getAccountRoyaleStats(Account $account, string $id)
{
$this->getJson('test');
$url = sprintf(Constants::$PvP, $id);
$data = $this->getJson($url);
$platform = $this->getPlatformType($data);
$normalized = $this->statNormalizer($data, $platform);
return $normalized;
}
public function getAccountByTag(string $name, string $platform)
/**
* @param string $name
* @param string $platform
* @return array
*/
public function getAccountByTag(string $name, string $platform): array
{
$this->getJson('test');
$url = sprintf(Constants::$lookup, $name);
$data = $this->getJson($url);
return [$data['id'], new Account()];
}
//---------------------------------------------------------------------------------
// Private Functions
//---------------------------------------------------------------------------------
private function getStatsModel(string $id): Stats
{
// TODO create Stat model
}
/**
* @param array $data
* @return string
* @throws \Exception
*/
private function getPlatformType(array $data): string
{
$types = ['xb1', 'pc', 'psn'];
$activeType = null;
foreach ($data as $item) {
foreach ($types as $type) {
if ($item['name'] === 'br_kills_' . $type . '_m0_p2') {
return $type;
}
}
}
throw new \Exception('Could not identify platform of response.');
}
/**
* @param array $data
* @param string $platform
* @return array
* @throws \Exception
*/
private function statNormalizer(array $data, string $platform): array
{
$stats = [];
foreach ($data as $item) {
$key = $item['name'];
$type = $this->getSquadType($key);
$stat = $this->getStatType($key);
$stats[$platform][$type][$stat][$item['window']] = $item['value'];
}
return $stats;
}
/**
* @param string $type
* @return string
*/
private function getSquadType(string $type): string
{
switch (true) {
case ends_with($type, '_p2'):
return 'solo';
case ends_with($type, '_p10'):
return 'duo';
default:
return 'squad';
}
}
/**
* @param string $type
* @return string
* @throws \Exception
*/
private function getStatType(string $type): string
{
switch (true) {
case str_contains($type, 'kills'):
return 'kills';
case str_contains($type, 'minutesplayed'):
return 'minutesplayed';
case str_contains($type, 'matchesplayed'):
return 'matchesplayed';
case str_contains($type, 'lastmodified'):
return 'lastmodified';
case str_contains($type, 'score'):
return 'score';
case str_contains($type, 'placetop25'):
return 'top25';
case str_contains($type, 'placetop10'):
return 'top10';
case str_contains($type, 'placetop12'):
return 'top12';
case str_contains($type, 'placetop6'):
return 'top6';
case str_contains($type, 'placetop5'):
return 'top5';
case str_contains($type, 'placetop3'):
return 'top3';
case str_contains($type, 'placetop1'):
return 'top1';
default:
throw new \Exception('Unknown new stat - ' . $type);
}
}
}
......@@ -18,5 +18,5 @@ class Constants
public static $PvE = 'https://fortnite-public-service-prod11.ol.epicgames.com/fortnite/api/game/v2/profile/%d/client/QueryProfile?profileId=athena&rvn=-1';
public static $PvP = 'https://fortnite-public-service-prod11.ol.epicgames.com/fortnite/api/stats/accountId/%d/bulk/window/alltime';
public static $PvP = 'https://fortnite-public-service-prod11.ol.epicgames.com/fortnite/api/stats/accountId/%s/bulk/window/alltime';
}
......@@ -2,6 +2,7 @@
namespace Onyx\Fortnite\Helpers\Network;
use Carbon\Carbon;
use GuzzleHttp\Client as Guzzle;
use Onyx\Fortnite\Constants;
......@@ -17,6 +18,12 @@ class Http
*/
protected $config;
protected $accessKeyCacheKey = 'fortniteAccessKey';
protected $refreshKeyCacheKey = 'fortniteRefreshKey';
protected $accessToken = null;
protected $refreshToken = null;
public function __construct()
{
$this->setupGuzzle();
......@@ -28,24 +35,47 @@ class Http
$this->guzzle = new Guzzle();
}
public function getJson($url)
public function getJson($url): ?array
{
$this->oAuthLogin();
$this->determineoAuthStatus();
if (!$this->guzzle instanceof Guzzle) {
$this->setupGuzzle();
}
$response = $this->guzzle->get($url, [
'headers' => [
'Accept' => 'application/json',
],
]);
try {
$response = $this->guzzle->get($url, [
'headers' => [
'Authorization' => 'Bearer ' . $this->accessToken,
'Accept' => 'application/json',
],
]);
if ($response->getStatusCode() != 200) {
throw new FortniteApiNetworkException();
}
return json_decode($response->getBody(), true);
} catch (\Exception $ex) {
return null;
}
}
if ($response->getStatusCode() != 200) {
throw new FortniteApiNetworkException();
private function determineoAuthStatus(): void
{
// 1) We have a valid access token still
if (\Cache::has($this->accessKeyCacheKey)) {
$this->accessToken = \Cache::get($this->accessKeyCacheKey);
return;
}
// 2) We have a valid refresh token
if (\Cache::has($this->refreshKeyCacheKey)) {
$this->oAuthRefresh(\Cache::get($this->refreshKeyCacheKey));
return;
}
return json_decode($response->getBody(), true);
$this->oAuthLogin();
}
private function oAuthLogin()
......@@ -100,7 +130,7 @@ class Http
}
}
private function oAuthEglToken(string $exchangeCode): void
private function oAuthEglToken(string $exchangeCode): array
{
$payload = [
'grant_type' => 'exchange_code',
......@@ -122,8 +152,50 @@ class Http
}
$data = json_decode($response->getBody(), true);
dd($data);
// TODO
$this->parseoAuthIntoCache($data);
return $data;
}
private function oAuthRefresh(string $refreshToken): array
{
$payload = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'includePerms' => true
];
$response = $this->guzzle->post(Constants::$oAuthToken, [
'headers' => [
'Authorization' => 'Basic ' . $this->config['client'],
'Accept' => 'application/json',
],
'form_params' => $payload,
]);
if ($response->getStatusCode() !== 200) {
throw new FortniteApiNetworkException();
}
$data = json_decode($response->getBody(), true);
$this->parseoAuthIntoCache($data);
return $data;
}
/**
* @param array $data
* @throws FortniteApiNetworkException
*/
private function parseoAuthIntoCache(array $data): void
{
if (isset($data['access_token']) && isset($data['refresh_token'])) {
\Cache::put($this->accessKeyCacheKey, $data['access_token'], Carbon::parse($data['expires_at'], 'Z'));
\Cache::put($this->refreshKeyCacheKey, $data['refresh_token'], Carbon::parse($data['refresh_expires_at'], 'Z'));
$this->accessToken = $data['access_token'];
$this->refreshToken = $data['refresh_token'];
} else {
throw new FortniteApiNetworkException();
}
}
}
......
<?php
namespace Onyx\Fortnite\Objects;
use Illuminate\Database\Eloquent\Model;
/**
* Class Stats
* @package Onyx\Fortnite\Objects
*/
class Stats extends Model
{
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'fortnite_stats';
/**
* The attributes that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id'];
public static function boot()
{
parent::boot();
}
//---------------------------------------------------------------------------------
// Accessors & Mutators
//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
// Public Methods
//---------------------------------------------------------------------------------
public function account()
{
return $this->belongsTo('Onyx\Account');
}
}
......@@ -122,7 +122,7 @@ class CustomValidator extends Validator
$client = new FortniteClient();
try {
$account = $client->getAccountByTag($value, $this->data['platform']);
$account = $client->getAccountByTag($value, '');
} catch (FortniteApiNetworkException $ex) {
return false;
}
......
......@@ -93,9 +93,11 @@ class AccountController extends Controller
$client = new FortniteClient();
$gamertag = $request->request->get('gamertag');
$platform = $request->request->get('platform');
dd($gamertag, $platform);
/** @var Account $account */
[$id, $account] = $client->getAccountByTag($gamertag, '');
return \Redirect::action('Fortnite\ProfileController@index', [$id]);
} catch (\Exception $ex) {
return redirect('/account', [
'close' => true,
......
<?php
namespace PandaLove\Http\Controllers\Fortnite;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Onyx\XboxLive\Enums\Console;
use PandaLove\Http\Controllers\Controller;
/**
* Class ProfileController.
*/
class ProfileController extends Controller
{
private $request;
private $inactiveCounter = 10;
private $refreshRateInMinutes = 520;
public function __construct(Request $request)
{
parent::__construct();
$this->request = $request;
}
public function index(string $id)
{
try {
//
} catch (ModelNotFoundException $e) {
\App::abort(404, 'We could not find this Fortnite Profile.');
}
}
public function checkForUpdate($gamertag = '', $platform = Console::Xbox)
{
}
public function manualUpdate($gamertag, $platform = Console::Xbox)
{
}
}
......@@ -31,7 +31,7 @@ class AddFortniteRequest extends Request
return [
'gamertag' => 'required|min:3|fortnite-real',
'platform' => 'required|in:0,1,2',
//'platform' => 'required|in:0,1,2',
];
}
}
......@@ -26,6 +26,7 @@ Route::get('/destiny2/profile/{console}/{gamertag}/{characterId?}', 'Destiny2\Pr
Route::controller('/xbox/api/v1', 'Xbox\ApiV1Controller');
//# Fortnite
Route::get('/fortnite/profile/{id}', 'Fortnite\ProfileController@index');
//# Halo 5
Route::controller('/h5/api/v1', 'Halo5\ApiV1Controller');
......
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddFortniteTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('fortnite_stats', function (Blueprint $table) {
$table->increments('id');
$table->string('epic_id', 128);
$table->integer('account_id', false, true)->nullable();
$this->types($table, 'solo');
$this->places($table, 'solo');
$this->types($table, 'duo');
$this->places($table, 'duo');
$this->types($table, 'squad');
$this->places($table, 'squad');
$table->timestamps();
$table->foreign('account_id')->references('id')->on('accounts');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('fortnite_stats');
}
private function types(Blueprint $table, string $type)
{
$table->integer($type . '_kills', false, true);
$table->integer($type . '_matches', false, true);
$table->integer($type . '_score', false, true);
$table->integer($type . '_minutesplayed', false, true);
}
private function places(Blueprint $table, string $type)
{
$table->integer($type . '_top1', false, true);
$table->integer($type . '_top3', false, true);
$table->integer($type . '_top5', false, true);
$table->integer($type . '_top10', false, true);
$table->integer($type . '_top25', false, true);
}
}
......@@ -4,35 +4,12 @@
@endforeach
<div class="two fields">
<div class="field {{ $errors->fortnite->has('gamertag') ? 'error' : '' }}">
<label>Username</label>
<input type="text" name="gamertag" id="gamertag" placeholder="Username" />
</div>
<div class="field {{ $errors->fortnite->has('platform') ? 'error' : '' }}">
<label>Platform</label>
<div class="ui selection dropdown">
<input type="hidden" name="platform">
<i class="dropdown icon"></i>
<div class="default text">Platform</div>
<div class="menu">
<div class="item" data-value="{{ \Onyx\XboxLive\Enums\Console::Xbox }}">Xbox</div>
<div class="item" data-value="{{ \Onyx\XboxLive\Enums\Console::PSN }}">PSN</div>
<div class="item" data-value="{{ \Onyx\XboxLive\Enums\Console::PC }}">PC</div>
</div>
</div>
<label>Epic Username</label>
<input type="text" name="gamertag" id="gamertag" placeholder="EPIC Username" />
</div>
</div>
<br />
<ul class="actions">
<li><input type="submit" value="Add Fortnite Account" /></li>
</ul>
{!! Form::close() !!}
@section('inline-js')
<script type="text/javascript">
$(document).on('ready', function() {
$('.ui.dropdown')
.dropdown()
;
});
</script>
@append
\ No newline at end of file
{!! Form::close() !!}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment