src/Controller/SKOverlayController.php line 94

  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\CoreConfigData;
  4. use App\Kernel;
  5. use App\Service\CardImageService;
  6. use Doctrine\ORM\EntityManagerInterface;
  7. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  8. use Symfony\Component\DependencyInjection\Attribute\Autowire;
  9. use Symfony\Component\Filesystem\Filesystem;
  10. use Symfony\Component\Filesystem\Path;
  11. use Symfony\Component\Finder\Finder;
  12. use Symfony\Component\HttpFoundation\JsonResponse;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use App\Service\MercurePublisher;
  16. use Symfony\Component\Routing\Attribute\Route;
  17. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  18. use Symfony\Component\Serializer\Encoder\CsvEncoder;
  19. use Symfony\Component\Serializer\Encoder\JsonEncoder;
  20. use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
  21. use Symfony\Component\Serializer\Serializer;
  22. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  23. use Symfony\Contracts\HttpClient\HttpClientInterface;
  24. #[Route('/skoverlay')]
  25. class SKOverlayController extends AbstractController
  26. {
  27. #[Route('/', name: 'app_skoverlay_index')]
  28. public function app_skoverlay_index (
  29. ): Response {
  30. return $this->render("sk_overlay/index.html.twig");
  31. }
  32. #[Route('/set/{id<\w+>}/{context}', name: 'app_skoverlay_set')]
  33. public function app_skoverlay_set (
  34. EntityManagerInterface $entityManager,
  35. Request $request,
  36. string $id,
  37. string $context = ""
  38. ): Response {
  39. $core_config_data_repository = $entityManager->getRepository(CoreConfigData::class);
  40. $config_key = "app_coverage_engine_overlay";
  41. if ($id) $config_key .= "_{$id}";
  42. if ($context) $config_key .= "_{$context}";
  43. $config_key .= "_data";
  44. $processed = $core_config_data_repository->findOneBy([
  45. "config_key" => $config_key
  46. ]);
  47. if (!$processed) $processed = new CoreConfigData();
  48. $processed->setConfigKey("app_coverage_engine_overlay_{$id}_data")->setConfigValue($request->getContent());
  49. $entityManager->persist($processed);
  50. $entityManager->flush();
  51. return new JsonResponse(
  52. [
  53. "success" => true,
  54. ]
  55. );
  56. }
  57. #[Route('/push/{topic<\w+>}', name: 'app_skoverlay_push')]
  58. public function app_skoverlay_push (
  59. Request $request,
  60. MercurePublisher $mercurePublisher,
  61. $topic = "ANZID",
  62. ): Response {
  63. $mercurePublisher->publishRaw($topic, $request->getContent());
  64. return new JsonResponse(
  65. [
  66. "success" => true,
  67. "data" => $request->getContent(),
  68. ]
  69. );
  70. }
  71. #[Route('/get/{id<^\w+$>}/{context}', name: 'app_skoverlay_get')]
  72. public function app_skoverlay_get (
  73. EntityManagerInterface $entityManager,
  74. Request $request,
  75. string $id,
  76. string $context = ""
  77. ): Response {
  78. $core_config_data_repository = $entityManager->getRepository(CoreConfigData::class);
  79. $config_key = "app_coverage_engine_overlay";
  80. if ($id) $config_key .= "_{$id}";
  81. if ($context) $config_key .= "_{$context}";
  82. $config_key .= "_data";
  83. $processed = $core_config_data_repository->findOneBy([
  84. "config_key" => $config_key
  85. ]);
  86. if (!$processed) {
  87. return new JsonResponse(
  88. [
  89. "success" => false,
  90. "message" => "No configuration set for given string."
  91. ]
  92. );
  93. }
  94. return new JsonResponse(
  95. [
  96. "success" => true,
  97. "data" => json_decode($processed->getConfigValue())
  98. ]
  99. );
  100. }
  101. #[Route('/edit/{engine<^\w+$>}/{id<^\w+$>}/{delay<\d+>}', name: 'app_skoverlay_edit')]
  102. public function app_skoverlay_edit (
  103. $engine = "anzid",
  104. $id = "ANZID",
  105. $delay = 10000
  106. ): Response {
  107. $cardsearch = [];
  108. if ($engine == "cardoverlay") {
  109. $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
  110. $cardsearch = json_encode($serializer->decode(file_get_contents("library/fab_cardlist.csv"), "csv"));
  111. }
  112. return $this->render("sk_overlay/edit/{$engine}.html.twig", [
  113. "console" => $id,
  114. "delay" => $delay,
  115. "cardsearch" => $cardsearch,
  116. ]);
  117. }
  118. #[Route('/view/{engine<^\w+$>}/{id<^\w+$>}/{delay<\d+>}', name: 'app_skoverlay_view')]
  119. public function app_skoverlay_view (
  120. Request $request,
  121. $engine = "anzid",
  122. $id = "ANZID",
  123. $delay = 10000
  124. ): Response {
  125. $safari = $request->query->get("safari") ?: 0;
  126. return $this->render("sk_overlay/view/{$engine}.html.twig", [
  127. "console" => $id,
  128. "delay" => $delay,
  129. "safari" => $safari,
  130. "get" => $request->query->all()
  131. ]);
  132. }
  133. #[Route('/cardsearch/{game}', name: 'app_skoverlay_cardsearch')]
  134. public function app_skoverlay_cardsearch (
  135. HttpClientInterface $client,
  136. Kernel $kernel,
  137. Request $request,
  138. CardImageService $cardImageService,
  139. #[Autowire('%env(PLAYERLINK_AC_TOURNAMENT_FORMAT)%')] string $tournamentFormat,
  140. $game = "fab"
  141. ): Response {
  142. $query = $request->query->get("q");
  143. if (strlen($query) < 2) return new JsonResponse([]);
  144. if ($game === "lorcana") {
  145. $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
  146. $cardsearch = ($serializer->decode(file_get_contents("library/lorcana_cardlist.csv"), "csv"));
  147. return new JsonResponse($cardsearch);
  148. } else if ($game === "fab") {
  149. $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
  150. $cardsearch = ($serializer->decode(file_get_contents("library/fab_cardlist.csv"), "csv"));
  151. return new JsonResponse($cardsearch);
  152. } else {
  153. $req = null;
  154. if ($game === "mtgarena") {
  155. $req = "https://api.scryfall.com/cards/search?q=$query+(f:$tournamentFormat)";
  156. } else {
  157. $req = "https://api.scryfall.com/cards/search?include_extras=true&unique=prints&q=$query";
  158. }
  159. try {
  160. $response = $client->request('GET', $req, [
  161. // 'headers' => [
  162. // 'Accept' => 'application/json',
  163. // ],
  164. 'timeout' => 5
  165. ]);
  166. } catch (TransportExceptionInterface $e) {
  167. return new JsonResponse([]);
  168. }
  169. $json = json_decode($response->getContent());
  170. $response_json = [];
  171. if ($game === "mtgarena") {
  172. foreach ($json->data as $element) {
  173. $cardNameTemp = $element->name;
  174. $setCodeTemp = strtoupper($element->set);
  175. $cardNameArray = [];
  176. if (str_contains($cardNameTemp, "//")) {
  177. $cardNameArray = explode("//", $cardNameTemp);
  178. } else {
  179. $cardNameArray[] = $cardNameTemp;
  180. }
  181. $time = time();
  182. foreach ($cardNameArray as $cardName) {
  183. // Use CardImageService to get the card image URL (tries CDN first, falls back to local)
  184. $cardImageRelativePath = $cardImageService->getCardImageUrl(trim($cardName), $setCodeTemp);
  185. if (!property_exists($element, "image_uris") && !property_exists($element, "card_faces")) continue;
  186. $response_json[] = [
  187. "cardname" => explode("//", $cardName, 2)[0] . " (" . $element->set_name . ")",
  188. "url" => $cardImageRelativePath
  189. ];
  190. }
  191. }
  192. } else {
  193. foreach ($json->data as $element) {
  194. if (!property_exists($element, "image_uris")) continue;
  195. $cardImageRelativePath = $element->image_uris->png;
  196. $response_json[] = [
  197. "cardname" => $element->name . " (" . $element->set_name . ")",
  198. "url" => $cardImageRelativePath
  199. ];
  200. }
  201. }
  202. return new JsonResponse($response_json);
  203. }
  204. }
  205. #[Route('/playerview/{id}', name: 'app_skoverlay_playerview')]
  206. public function app_skoverlay_playerview (
  207. $id = 1943
  208. ): Response {
  209. $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
  210. $demographics = ($serializer->decode(file_get_contents("library/fab_ptdemographics.csv"), "csv"));
  211. foreach ($demographics as $demographic) {
  212. if ($demographic["player_id"] == $id) {
  213. return $this->render("sk_overlay/view/playerview.html.twig", [
  214. "player" => $demographic
  215. ]);
  216. }
  217. }
  218. var_dump($demographics);
  219. return new Response();
  220. }
  221. #[Route('/playerhistory/{id}', name: 'app_skoverlay_playerhistory')]
  222. public function app_skoverlay_playerhistory (
  223. $id = 1943
  224. ): Response {
  225. $serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
  226. $demographics = ($serializer->decode(file_get_contents("library/fab_ptdemographics.csv"), "csv"));
  227. foreach ($demographics as $demographic) {
  228. if ($demographic["player_id"] == $id) {
  229. $history = [];
  230. for ($i = 1; $i <= 14; $i++) {
  231. if (array_key_exists("m" . $i . "_opponent", $demographic)) {
  232. $history[$i] = [
  233. "round" => $i,
  234. "opponent" => $demographic["m" . $i . "_opponent"],
  235. "hero" => $demographic["m" . $i . "_opp_hero"],
  236. "result" => $demographic["m" . $i . "_result"],
  237. ];
  238. }
  239. }
  240. return $this->render("sk_overlay/view/playerhistory.html.twig", [
  241. "player" => $demographic,
  242. "history" => $history,
  243. ]);
  244. }
  245. }
  246. var_dump($demographics);
  247. return new Response();
  248. }
  249. #[Route('/standings/{page}', name: 'app_skoverlay_standings')]
  250. public function app_skoverlay_standings (
  251. Kernel $kernel,
  252. HttpClientInterface $httpClient,
  253. Request $request,
  254. EntityManagerInterface $entityManager,
  255. Int $page,
  256. ): Response {
  257. $core_config_data_repository = $entityManager->getRepository(CoreConfigData::class);
  258. $config_key = "app_coverlay_eventdata";
  259. $value = $core_config_data_repository->findOneBy([
  260. "config_key" => $config_key
  261. ]);
  262. $event_json = json_decode($value->getConfigValue());
  263. $round_id = ($event_json->standings_round);
  264. $standings_response = $httpClient->request(
  265. 'GET',
  266. $this->generateUrl("app_coverlay_fn_cachestandings", array("id" => $round_id),UrlGeneratorInterface::ABSOLUTE_URL
  267. )
  268. );
  269. $json = json_decode($standings_response->getContent());
  270. return $this->render('c_overlay/view/magicanzid_standings.html.twig', [
  271. "standings" => array_slice($json, ($page - 1) * 16, 16),
  272. "page" => $page - 1,
  273. ]);
  274. }
  275. #[Route('/standingsnyse/{round}/{page}', name: 'app_skoverlay_standingsnyse')]
  276. public function app_skoverlay_standingsnyse (
  277. Kernel $kernel,
  278. HttpClientInterface $httpClient,
  279. Request $request,
  280. EntityManagerInterface $entityManager,
  281. Int $round,
  282. Int $page,
  283. ): Response {
  284. $standings_response = $httpClient->request(
  285. 'GET',
  286. $this->generateUrl("app_coverlay_fn_cachestandings", array("id" => $round),UrlGeneratorInterface::ABSOLUTE_URL
  287. )
  288. );
  289. $json = json_decode($standings_response->getContent());
  290. return $this->render('c_overlay/view/magicnyse_standings.html.twig', [
  291. "standings" => array_slice($json, ($page - 1) * 16, 16),
  292. "page" => $page - 1,
  293. ]);
  294. }
  295. #[Route('/newstandings/{page}/{round}', name: 'app_skoverlay_newstandings')]
  296. public function app_skoverlay_newstandings (
  297. Kernel $kernel,
  298. HttpClientInterface $httpClient,
  299. Request $request,
  300. EntityManagerInterface $entityManager,
  301. Int $page,
  302. Int $round = -1
  303. ): Response {
  304. $standings_export_path = $this->getParameter('kernel.project_dir') . '/private/lorcana_standings.csv';
  305. $filesystem = new Filesystem();
  306. $standings = [];
  307. if ($filesystem->exists($standings_export_path)) {
  308. $standings_file = file_get_contents($standings_export_path);
  309. $encoder = new CsvEncoder();
  310. $standings = $encoder->decode($standings_file, "csv");
  311. }
  312. // $standings = [];
  313. //
  314. // for ($i = 1; $i < 100; $i++) {
  315. //
  316. // $standings[] = (object) [
  317. // "FirstName" => "Player",
  318. // "LastName" => "Player",
  319. // "Points" => 10
  320. // ];
  321. //
  322. // }
  323. return $this->render('c_overlay/view/lorcana_standings.html.twig', [
  324. "standings" => array_slice($standings, ($page - 1) * 16, 16),
  325. "page" => $page - 1,
  326. ]);
  327. }
  328. private function usort_helper($a, $b) {
  329. if ($a < $b) { return 1; }
  330. if ($a > $b) { return -1; }
  331. return 0;
  332. }
  333. #[Route('/playerstowatch/{page}/{round}', name: 'app_skoverlay_playerstowatch')]
  334. public function app_skoverlay_playerstowatch (
  335. Kernel $kernel,
  336. HttpClientInterface $httpClient,
  337. Request $request,
  338. EntityManagerInterface $entityManager,
  339. Int $page,
  340. Int $round = -1
  341. ): Response {
  342. $playerdata = $this->_getDecklistPlayerData();
  343. $core_config_data_repository = $entityManager->getRepository(CoreConfigData::class);
  344. $config_key = "app_coverlay_eventdata";
  345. $value = $core_config_data_repository->findOneBy([
  346. "config_key" => $config_key
  347. ]);
  348. $event_json = json_decode($value->getConfigValue());
  349. $current_round = substr($event_json->broadcast_round,1);
  350. if ($round > -1) $current_round = $round;
  351. $current_round = max(1, $current_round - 1);
  352. $standings_api_path = $this->getParameter('kernel.project_dir') . '/private/tournament_standings_'.$current_round.'_api.json';
  353. $standings_export_path = $this->getParameter('kernel.project_dir') . '/private/tournament_standings_'.$current_round.'_export.csv';
  354. $filesystem = new Filesystem();
  355. $standings = [];
  356. if ($filesystem->exists($standings_export_path)) {
  357. $standings_file = file_get_contents($standings_export_path);
  358. $encoder = new CsvEncoder();
  359. $standings = $encoder->decode($standings_file, "csv");
  360. } else if ($filesystem->exists($standings_api_path)) {
  361. $standings_file = file_get_contents($standings_api_path);
  362. $encoder = new JsonEncoder();
  363. $standings = $encoder->decode($standings_file, "json");
  364. } else {
  365. }
  366. return $this->render('c_overlay/view/magicanzid_playerstowatch.html.twig', [
  367. "standings" => $standings,
  368. "playerlist" => [
  369. "Aintrazi, Ali",
  370. "Anderson, Todd",
  371. "Baeckstrom, Andrew",
  372. "Brodie, Daniel",
  373. // "Bursavich, Austin",
  374. "Clark, Mason",
  375. "Corrigan, Michael",
  376. "Kiihne, Zachary",
  377. "Krueger, Will",
  378. "Loveman, Eli",
  379. "Malka, Sol",
  380. "mcvety, max",
  381. "Merriam, Ross",
  382. "Nettles, Logan",
  383. "Pardee, Carolyn",
  384. "Pardee, Samuel",
  385. "Rabin, Noah",
  386. "Snook, Adam",
  387. "Stein, Abraham",
  388. "Steuer, Nathan",
  389. "Syed, Zan",
  390. "Taylor, Robert",
  391. "Warfield, Skyler",
  392. "Yu, Jarvis",
  393. ]
  394. ]);
  395. }
  396. #[Route('/archetypes/{page}/{format}', name: 'app_skoverlay_archetypes')]
  397. public function app_skoverlay_archetypes (
  398. Kernel $kernel,
  399. HttpClientInterface $httpClient,
  400. Request $request,
  401. EntityManagerInterface $entityManager,
  402. Int $page,
  403. $format = "Pioneer"
  404. ): Response {
  405. $archetypes = [];
  406. $total = 0;
  407. $playerdata = $this->_getDecklistPlayerData();
  408. foreach ($playerdata as $player) {
  409. foreach ($player->Decklists as $decklist) {
  410. if ($decklist->Format == $format) {
  411. if (!array_key_exists($decklist->Name, $archetypes)) {
  412. $archetypes[$decklist->Name] = [
  413. "archetype" => $decklist->Name,
  414. "count" => 0,
  415. "percent" => 0,
  416. "keycards" => []
  417. ];
  418. }
  419. $archetypes[$decklist->Name]["count"]++;
  420. $total++;
  421. }
  422. }
  423. }
  424. foreach ($archetypes as &$archetype) {
  425. $archetype["percent"] = $archetype["count"] / $total;
  426. }
  427. usort($archetypes, function($a, $b) {
  428. return $b["count"] - $a["count"];
  429. });
  430. return $this->render('c_overlay/view/magicanzid_archetypes.html.twig', [
  431. "archetypes" => array_slice($archetypes, ($page - 1) * 12, 12),
  432. "format" => $format
  433. ]);
  434. }
  435. private function _getDecklistPlayerData() {
  436. $player_path = $this->getParameter('kernel.project_dir') . '/private/tournament_players.json';
  437. $player_file = file_get_contents($player_path);
  438. $player_json = json_decode($player_file);
  439. $players = [];
  440. foreach ($player_json as $element) {
  441. $players[$element->ID] = $element;
  442. }
  443. $decklist_path = $this->getParameter('kernel.project_dir') . '/private/tournament_decklists.json';
  444. $decklist_file = file_get_contents($decklist_path);
  445. $decklist_json = json_decode($decklist_file);
  446. foreach ($decklist_json as $element) {
  447. if (!array_key_exists($element->PlayerId, $players)) { continue; }
  448. foreach ($players[$element->PlayerId]->Decklists as $decklist) {
  449. if ($decklist->ID === $element->ID) {
  450. if ($element->Format == "Draft") {
  451. $colors = [
  452. "W" => false,
  453. "U" => false,
  454. "B" => false,
  455. "R" => false,
  456. "G" => false,
  457. ];
  458. foreach ($element->Cards as $card) {
  459. if (!$card->IsSideboard) {
  460. foreach (["W", "U", "B", "R", "G"] as $color) {
  461. if (str_contains($card->CardManaCost, $color)) {
  462. $colors[$color] = true;
  463. }
  464. }
  465. }
  466. }
  467. $element->IsWhite = $colors["W"];
  468. $element->IsBlue = $colors["U"];
  469. $element->IsBlack = $colors["B"];
  470. $element->IsRed = $colors["R"];
  471. $element->IsGreen = $colors["G"];
  472. $element->White = $colors["W"] ? 1 : 0;
  473. $element->Blue = $colors["U"] ? 1 : 0;
  474. $element->Black = $colors["B"] ? 1 : 0;
  475. $element->Red = $colors["R"] ? 1 : 0;
  476. $element->Green = $colors["G"] ? 1 : 0;
  477. }
  478. $decklist->Decklist = $element;
  479. }
  480. }
  481. }
  482. ksort($players);
  483. return $players;
  484. }
  485. private function _getPlayerData() {
  486. $player_path = $this->getParameter('kernel.project_dir') . '/private/tournament_players.json';
  487. $player_file = file_get_contents($player_path);
  488. $player_json = json_decode($player_file);
  489. $players = [];
  490. foreach ($player_json as $element) {
  491. $players[$element->ID] = $element;
  492. }
  493. $decklist_path = $this->getParameter('kernel.project_dir') . '/private/tournament_decklists.json';
  494. $decklist_file = file_get_contents($decklist_path);
  495. $decklist_json = json_decode($decklist_file);
  496. if (!$decklist_json->Error) {
  497. foreach ($decklist_json as $element) {
  498. foreach ($players[$element->PlayerId]->Decklists as $decklist) {
  499. if ($decklist->ID === $element->ID) {
  500. $decklist->Decklist = $element;
  501. }
  502. }
  503. }
  504. }
  505. return $players;
  506. }
  507. #[Route('/anziddecklist/{match}/{player}', name: 'app_coverlay_anziddecklist')]
  508. public function app_coverlay_decksideboard (
  509. Kernel $kernel,
  510. HttpClientInterface $httpClient,
  511. Request $request,
  512. EntityManagerInterface $entityManager,
  513. String $match,
  514. String $player
  515. ): Response {
  516. $core_config_data_repository = $entityManager->getRepository(CoreConfigData::class);
  517. $config_key = "app_coverlay_eventdata";
  518. $value = $core_config_data_repository->findOneBy([
  519. "config_key" => $config_key
  520. ]);
  521. $event_json = json_decode($value->getConfigValue());
  522. $config_key = "app_coverlay_matchdata_" . $event_json->broadcast_round . "_m" .$match;
  523. $value = $core_config_data_repository->findOneBy([
  524. "config_key" => $config_key
  525. ]);
  526. $match_json = json_decode($value->getConfigValue());
  527. $playerdata = $this->_getDecklistPlayerData();
  528. $deck = [
  529. "main" => [],
  530. "side" => []
  531. ];
  532. $decklist_player = null;
  533. $colors = [
  534. "W" => false,
  535. "U" => false,
  536. "B" => false,
  537. "R" => false,
  538. "G" => false,
  539. ];
  540. foreach ($playerdata[$match_json->{"p".$player."_name"}]->Decklists as $decklist) {
  541. if ($decklist->Format == $match_json->format) {
  542. $decklist_player = $decklist->Decklist;
  543. foreach (array_merge($decklist->Decklist->Cards, $decklist->Decklist->Sideboard) as $card) {
  544. if ($card->IsSideboard) {
  545. $deck["side"][] = [
  546. "cardname" => explode(" // ", $card->CardName)[0],
  547. "quantity" => $card->Quantity,
  548. ];
  549. } else {
  550. $deck["main"][] = [
  551. "cardname" => explode(" // ", $card->CardName)[0],
  552. "quantity" => $card->Quantity,
  553. ];
  554. foreach (["W", "U", "B", "R", "G"] as $color) {
  555. if (str_contains($card->CardManaCost, $color)) {
  556. $colors[$color] = true;
  557. }
  558. }
  559. }
  560. }
  561. }
  562. }
  563. if (strtoupper($decklist_player->Archetype) == "ALL OTHER DECKLISTS") {
  564. $decklist_player->Archetype = "";
  565. }
  566. return $this->render("c_overlay/view/magicanzid_decklist.html.twig", [
  567. "player" => $decklist_player,
  568. "decklist" => $deck,
  569. "colors" => $colors
  570. ]);
  571. }
  572. }