Fibinger Ádám
2020-08-28 721a8f051aaf368d0453228e27086a434fbf5828
Táblázat renderelés módosítása - R6 Tracker alternatív megoldásának eleje
8 files added
5 files modified
570 ■■■■■ changed files
composer.json 2 ●●●●● patch | view | raw | blame | history
index.html 45 ●●●●● patch | view | raw | blame | history
index.php 89 ●●●●● patch | view | raw | blame | history
src/BattlefyTeamParser.php 2 ●●● patch | view | raw | blame | history
src/R6API/HTTPHelper.php 29 ●●●●● patch | view | raw | blame | history
src/R6API/Login.php 54 ●●●●● patch | view | raw | blame | history
src/R6API/Operations.php 215 ●●●●● patch | view | raw | blame | history
src/R6API/Ticket.php 79 ●●●●● patch | view | raw | blame | history
src/R6API/TicketErrorException.php 10 ●●●●● patch | view | raw | blame | history
src/R6API/TicketExpiredException.php 10 ●●●●● patch | view | raw | blame | history
src/R6API/TicketInvalidException.php 10 ●●●●● patch | view | raw | blame | history
templates/html-base.twig 22 ●●●●● patch | view | raw | blame | history
templates/index.twig 3 ●●●●● patch | view | raw | blame | history
composer.json
@@ -12,6 +12,8 @@
  ],
  "require": {
    "php": ">=7.4.0",
    "ext-curl": "*",
    "ext-json": "*",
    "vfiber/tablegenerator": "*",
    "twig/twig": "^3.0"
  }
index.html
New file
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
          integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>
<body>
<table>
    <tr>
        <td>Belőlem szép nagy táblázat lesz... Egyszer!</td>
    </tr>
</table>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
        crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
        integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
        crossorigin="anonymous"></script>
<script>
    var teams;
    $(document).ready(function () {
            $.getJSON('https://r6admin.unr.hu/workdir/2020-32/parsed.json'
                , function( data ) {
                    teams = data;
                    //ez már csak rizsa, ide lehet írni mondjuk egy táblázatot felépítú részt:
                    console.log("Loaded: ok");
                    console.log(data);
                }
            )
        }
    );
</script>
</body>
</html>
index.php
@@ -1,6 +1,7 @@
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/Budapest');
include 'vendor/autoload.php';
use TableGenerator\DataObject;
@@ -9,6 +10,29 @@
$twig = \Wargasz\TwigFactory::getEnvironment('templates/');
$messages = [];
$table = false;
$last_access = '1900-01-01 00:00';
$cols = [
    'team'          => 'Csapat Név',
    'name'          => ['Név',
        function ($a, $originalRowData) {
            return '<a href="https://r6.tracker.network/profile/pc/' . $a . '">' . $a . '</a>';
        }
    ],
    'captain'       => ['Kapitány',
        function ($a, $originalRowData) {
            return $a ? "Igen" : "Nem";
        }
    ],
    'valid'         => [
        'Valid',
        function ($a, $originalRowData) {
            return $a ? $originalRowData['level'] >= 100 ? "Igen" : "Nem-LVL" : "Nem-NoUser";
        }
    ],
    'level'         => 'LvL',
    'alias_history' => 'Alias history'
];
if (!empty($_POST['teams_json']))
{
@@ -19,46 +43,43 @@
    $table = $parser->getTeamData($forceDownload);
    $messages = $parser->getMessages();
    $cols = [
        'team'          => 'Csapat Név',
        'name'          => ['Név',
            function ($a, $originalRowData) {
                return '<a href="https://r6.tracker.network/profile/pc/' . $a . '">' . $a . '</a>';
            }
        ],
        'captain'       => ['Kapitány',
            function ($a, $originalRowData) {
                return $a ? "Igen" : "Nem";
            }
        ],
        'valid'         => [
            'Valid',
            function ($a, $originalRowData) {
                return $a ? $originalRowData['level'] >= 100 ? "Igen" : "Nem-LVL" : "Nem-NoUser";
            }
        ],
        'level'         => 'LvL',
        'alias_history' => 'Alias history'
    ];
    file_put_contents(\Wargasz\BattlefyTeamParser::getWorkDir() . '/table.json', json_encode($table));
    file_put_contents(\Wargasz\BattlefyTeamParser::getWorkDir() . '/source-' . time() . '.json', $_POST['teams_json']);
    file_put_contents(\Wargasz\BattlefyTeamParser::getWorkDir() . '/parsed.json', json_encode($parser->getParsedTeamData()));
    //fixme: szétszedni, hogy ez ne a táblázatot írja meg egyből, hanem a nyers adatokat és azt olvassa vissza
    $do = new DataObject($cols, $table);
    $HTMLTable = (new \TableGenerator\Render\HTMLTable(['id' => 'ccup']))->setDataObject($do);
    ob_start();
    $HTMLTable->renderTable();
    $tableData = ob_get_clean();
    file_put_contents('result.html', $tableData);
}
else
{
    $tableData = file_get_contents('result.html');
    $json = @file_get_contents(\Wargasz\BattlefyTeamParser::getWorkDir() . '/table.json');
    if ($json)
    {
        $table = json_decode($json, true);
    }
}
if (!$table)
{
    $tableData = '<h3>Tölts fel egy teams.json -t!</h3>';
}
else
{
    $do = new DataObject($cols, $table);
    $HTMLTable = (new \TableGenerator\Render\HTMLDataTable(['id' => 'ccup']))->setDataObject($do);
    ob_start();
    $HTMLTable->renderTable();
    $tableData = ob_get_clean();
    $fmtime = filemtime(\Wargasz\BattlefyTeamParser::getWorkDir() . '/table.json');
    if ($fmtime)
    {
        $last_access = new DateTime();
        $last_access->setTimestamp($fmtime);
    }
}
echo $twig->render('index.twig', [
    'table'    => $tableData,
    'messages' => $messages
    'last_access' => $last_access,
    'table'       => $tableData,
    'messages'    => $messages
]);
src/BattlefyTeamParser.php
@@ -119,7 +119,7 @@
            }
        }
        $this->parsedTeamData = $csapat;
        $this->parsedTeamData = $csapatok;
        return $table;
    }
src/R6API/HTTPHelper.php
New file
@@ -0,0 +1,29 @@
<?php
namespace Wargasz\R6API;
class HTTPHelper
{
    protected static $ch = null;
    public static function getCurlObject($url, $extraOptions) {
        $options = [
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_URL            => trim($url),
            CURLOPT_USERAGENT      => 'Csibesz Csapat Scraper 0.2',
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_FOLLOWLOCATION => true
        ];
        if (self::$ch == null){
            self::$ch = curl_init();
        }
        curl_setopt_array(self::$ch, $options);
        return self::$ch;
    }
}
src/R6API/Login.php
New file
@@ -0,0 +1,54 @@
<?php
namespace Wargasz\R6API;
class Login
{
    private $b64authcreds;
    public function __construct($username, $password)
    {
    }
    public function login()
    {
        $request_url = "https://public-ubiservices.ubi.com/v3/profiles/sessions";
        #$request_header_ubiappid="314d4fef-e568-454a-ae06-43e3bece12a6";
        $request_header_ubiappid = "39baebad-39e5-4552-8c25-2c9b919064e2";
        $request_header_authbasic = $this->b64authcreds;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, '{"rememberMe":true}');
        $headers = [
            "Content-Type: application/json; charset=utf-8",
            "Accept: */*",
            "Ubi-AppId: " . $request_header_ubiappid,
            "Ubi-RequestedPlatformType: uplay",
            "Authorization: Basic " . $request_header_authbasic,
            "X-Requested-With: XMLHttpRequest",
            "Referer: https://public-ubiservices.ubi.com/Default/Login?appId=" . $request_header_ubiappid . "&lang=en-US&nextUrl=https%3A%2F%2Fclub.ubisoft.com%2Flogged-in.html%3Flocale%3Den-US",
            "Accept-Language: en-US",
            "Accept-Encoding: deflate, br",
            "User-Agent: " . $this->http_useragent,
            "Host: public-ubiservices.ubi.com",
            "Content-Lenght: 19",
            "Cache-Control: no-cache",
        ];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $ubioutput = curl_exec($ch);
        curl_close($ch);
        $previousTicket = $this->loadTicket();
        $this->saveTicket($ubioutput);
        return [
            "error"   => false,
            "content" => ($previousTicket != $this->loadTicket() ? "Ticket updated Successfully" : "Ticket update failed")
        ];
    }
}
src/R6API/Operations.php
New file
@@ -0,0 +1,215 @@
<?php
namespace Wargasz;
use Wargasz\R6API\Ticket;
class Operations
{
    const SEARCH_MODE_BYNICK = 'bynick';
    const SEARCH_MODE_BYID = 'byid';
    public $http_useragent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";
    /**
     * @var Ticket
     */
    protected $ticket;
    function __construct($email = '', $password = '')
    {
        $this->b64authcreds = base64_encode($email . ":" . $password);
    }
    public function getErrorMessage()
    {
        $ticket = $this->loadTicket();
        if (isset($ticket["errorCode"]))
        {
            return $ticket;
        }
        return false;
    }
    public function searchUser($mode, $content, $platform)
    {
        $prefixUrl = "https://api-ubiservices.ubi.com/v2/profiles?";
        if ($mode == self::SEARCH_MODE_BYNICK)
        {
            $content = urlencode($content);
            $request_url = $prefixUrl . "nameOnPlatform=" . $content . "&platformType=$platform";
        }
        if ($mode == self::SEARCH_MODE_BYID)
        {
            $request_url = $prefixUrl . "profileId=" . $content;
        }
        $request_header_ubiappid = "314d4fef-e568-454a-ae06-43e3bece12a6";
        $request_header_ubisessionid = "a651a618-bead-4732-b929-4a9488a21d27";
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $headers = [
            "Accept: */*",
            "ubi-appid: " . $request_header_ubiappid,
            "ubi-sessionid: " . $request_header_ubisessionid,
            "authorization: " . $this->uplayticket(),
            "Referer: https://club.ubisoft.com/en-US/friends",
            "Accept-Language: en-US",
            "Origin: https://club.ubisoft.com",
            "Accept-Encoding: deflate, br",
            "User-Agent: " . $this->http_useragent,
            "Host: api-ubiservices.ubi.com",
            "Cache-Control: no-cache"
        ];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $ubioutput = curl_exec($ch);
        curl_close($ch);
        $jsonoutput = json_decode($ubioutput, true);
        if (!isset($jsonoutput['profiles']) || count($jsonoutput["profiles"]) == 0)
        {
            return ["error"   => true,
                    "content" => "Ubi Response is empty!"];
        }
        return [
            "error" => false,
            "raw"   => $ubioutput,
            "json"  => $jsonoutput,
            "nick"  => $jsonoutput['profiles'][0]['nameOnPlatform'],
            "pid"   => $jsonoutput['profiles'][0]['profileId'],
            "uid"   => $jsonoutput['profiles'][0]['userId']
        ];
    }
    public function getStats($users, $stats, $platform)
    {
        $array = array_chunk(explode(",", $stats), 19, true);
        $final = [];
        foreach ($array as $row)
        {
            $stats = implode(",", $row);
            $stats = $this->getStatsRaw($users, $stats, $platform);
            $stats = json_decode($stats, true);
            $final[] = $stats;
        }
        $result = [];
        foreach ($final as $key => $val)
        {
            if ((!is_null($val)) && (array_key_exists("results", $val)))
            {
                foreach ($val["results"] as $user => $value)
                {
                    if (isset($result[$user]))
                    {
                        $result[$user] = array_merge($result[$user], $value);
                        continue;
                    }
                    $result[$user] = $value;
                }
            }
        }
        return json_encode(["results" => $result]);
    }
    private function getStatsRaw($users, $stats, $platform)
    {
        $user = explode(",", $users)[0];
        $request_urls = ["uplay" =>
                             "https://public-ubiservices.ubi.com/v1/spaces/5172a557-50b5-4665-b7db-e3f2e8c5041d/sandboxes/OSBOR_PC_LNCH_A/playerstats2/statistics"
            , "xbl"              =>
                             "https://public-ubiservices.ubi.com/v1/spaces/98a601e5-ca91-4440-b1c5-753f601a2c90/sandboxes/OSBOR_XBOXONE_LNCH_A/playerstats2/statistics"
            , "psn"              =>
                             "https://public-ubiservices.ubi.com/v1/spaces/05bfb3f7-6c21-4c42-be1f-97a33fb5cf66/sandboxes/OSBOR_PS4_LNCH_A/playerstats2/statistics"
        ];
        $request_url = $request_urls[$platform] . "?populations=$users&statistics=$stats";
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $headers = [
            "Authorization: " . $this->uplayticket(),
            "Origin: https://game-rainbow6.ubi.com",
            "Accept-Encoding: deflate, br",
            "Host: public-ubiservices.ubi.com",
            "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 OPR/52.0.2871.99",
            "Accept: application/json, text/plain, */*",
            "Ubi-AppId: 39baebad-39e5-4552-8c25-2c9b919064e2",
            "Ubi-SessionId: a4df2e5c-7fee-41ff-afe5-9d79e68e8048",
            "Referer: https://game-rainbow6.ubi.com/de-de/uplay/player-statistics/$user/multiplayer",
            "Connection: keep-alive"];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $ubioutput = curl_exec($ch);
        curl_close($ch);
        return str_replace(":infinite", "", $ubioutput);
    }
    public function getRanking($users, $season, $region, $platform)
    {
        $user = explode(",", $users)[0];
        $request_urls = [
            "uplay" =>
                "https://public-ubiservices.ubi.com/v1/spaces/5172a557-50b5-4665-b7db-e3f2e8c5041d/sandboxes/OSBOR_PC_LNCH_A/r6karma/players"
            , "xbl" =>
                "https://public-ubiservices.ubi.com/v1/spaces/98a601e5-ca91-4440-b1c5-753f601a2c90/sandboxes/OSBOR_XBOXONE_LNCH_A/r6karma/players"
            , "psn" =>
                "https://public-ubiservices.ubi.com/v1/spaces/05bfb3f7-6c21-4c42-be1f-97a33fb5cf66/sandboxes/OSBOR_PS4_LNCH_A/r6karma/players"
        ];
        $request_url = $request_urls[$platform] . "?board_id=pvp_ranked&profile_ids=$users&region_id=$region&season_id=$season";
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $headers = [
            "Authorization: " . $this->ticket->getAuthorizationString(),
            "Origin: https://game-rainbow6.ubi.com",
            "Accept-Encoding: gzip, deflate, br",
            "Host: public-ubiservices.ubi.com",
            "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 OPR/52.0.2871.99",
            "Accept: application/json, text/plain, */*",
            "Ubi-AppId: 39baebad-39e5-4552-8c25-2c9b919064e2",
            "Ubi-SessionId: a4df2e5c-7fee-41ff-afe5-9d79e68e8048",
            "Referer: https://game-rainbow6.ubi.com/de-de/uplay/player-statistics/$user/multiplayer",
            "Connection: keep-alive"];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $ubioutput = curl_exec($ch);
        curl_close($ch);
        return $ubioutput;
    }
    public function getProgression($users, $platform)
    {
        $user = explode(",", $users)[0];
        $request_urls = ["uplay" =>
                             "https://public-ubiservices.ubi.com/v1/spaces/5172a557-50b5-4665-b7db-e3f2e8c5041d/sandboxes/OSBOR_PC_LNCH_A/r6playerprofile/playerprofile/progressions"
            , "xbl"              =>
                             "https://public-ubiservices.ubi.com/v1/spaces/98a601e5-ca91-4440-b1c5-753f601a2c90/sandboxes/OSBOR_XBOXONE_LNCH_A/r6playerprofile/playerprofile/progressions"
            , "psn"              =>
                             "https://public-ubiservices.ubi.com/v1/spaces/05bfb3f7-6c21-4c42-be1f-97a33fb5cf66/sandboxes/OSBOR_PS4_LNCH_A/r6playerprofile/playerprofile/progressions"
        ];
        $request_url = $request_urls[$platform] . "?profile_ids=$users";
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $request_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $headers = [
            "Authorization: " . $this->uplayticket(),
            "Origin: https://game-rainbow6.ubi.com",
            "Accept-Encoding: deflate, br",
            "Host: public-ubiservices.ubi.com",
            "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 OPR/52.0.2871.99",
            "Accept: application/json, text/plain, */*",
            "Ubi-AppId: 39baebad-39e5-4552-8c25-2c9b919064e2",
            "Ubi-SessionId: a4df2e5c-7fee-41ff-afe5-9d79e68e8048",
            "Referer: https://game-rainbow6.ubi.com/de-de/uplay/player-statistics/$user/multiplayer",
            "Connection: keep-alive"];
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $ubioutput = curl_exec($ch);
        curl_close($ch);
        return $ubioutput;
    }
}
src/R6API/Ticket.php
New file
@@ -0,0 +1,79 @@
<?php
namespace Wargasz\R6API;
class Ticket
{
    const TICKET_FILE_NAME = 'api_ticket';
    private $ticketData = [];
    private function loadTicket()
    {
        $ticket = file_get_contents(self::TICKET_FILE_NAME);
        if ($ticket === false)
        {
            throw new TicketExpiredException("Cannot read ticket file");
        }
        return json_decode($ticket, true);
    }
    private function saveTicket($ticketContent)
    {
        if (!is_writeable(self::TICKET_FILE_NAME))
        {
            die("Can't open ticket file");
        }
        return (bool)file_put_contents(self::TICKET_FILE_NAME, $ticketContent);
    }
    public function getAuthorizationString($check = true)
    {
        $ticket = $this->loadTicket();
        if (isset($ticket["error"]) && $ticket["error"] == true)
        {
            if ($ticket["error"])
            {
                throw new TicketErrorException("Ticket returned an error: " . $ticket["error"]);
            }
        }
        if (isset($ticket["errorCode"]))
        {
            throw new TicketErrorException("Ticket has an error code: " . $ticket["errorCode"]);
        }
        if (!isset($ticket["expiration"]))
        {
            throw new TicketInvalidException("Ticket has expired or invalid: has no 'expiration' field");
        }
        $time = strtotime($ticket["expiration"]);
        if (!$time)
        {
            throw new TicketErrorException("Ticket time cannot be parsed as a valid time: " . $ticket["expiration"]);
        }
        if ($time < time())
        {
            throw new TicketExpiredException("Ticket has expired or invalid.");
        }
        if (!isset($ticket["ticket"]))
        {
            return "";
        }
        $ticket = $ticket["ticket"];
        $prefix = "Ubi_v1 t=";
        return $prefix . $ticket;
    }
}
src/R6API/TicketErrorException.php
New file
@@ -0,0 +1,10 @@
<?php
namespace Wargasz\R6API;
class TicketErrorException extends \Exception
{
}
src/R6API/TicketExpiredException.php
New file
@@ -0,0 +1,10 @@
<?php
namespace Wargasz\R6API;
class TicketExpiredException extends \Exception
{
}
src/R6API/TicketInvalidException.php
New file
@@ -0,0 +1,10 @@
<?php
namespace Wargasz\R6API;
class TicketInvalidException extends \Exception
{
}
templates/html-base.twig
@@ -7,21 +7,29 @@
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
              integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
              crossorigin="anonymous">
        <script src="https://code.jquery.com/jquery-3.5.1.min.js" crossorigin="anonymous"></script>
    {% endblock %}
</head>
<body>
{% block navigation %}
{% endblock %}
{% block body %}
    <h1>Hello, world!</h1>
{% endblock %}
<div class="container">
    {% block body %}
        <h1>Hello, world!</h1>
    {% endblock %}
</div>
{% block lazyload %}
    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
            integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
            integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
            crossorigin="anonymous"></script>
{% endblock %}
</body>
</html>
templates/index.twig
@@ -1,5 +1,8 @@
{% extends 'html-base.twig' %}
{% block body %}
    <div class="page-header">
        <h3>Utoljára frissítve: {{ last_access|date('Y-m-d H:i:s') }}</h3>
    </div>
    <form method="post">
        <div class="form-group">
            <label for="exampleFormControlTextarea1">Teams JSON</label>