From ffe69e4fc01db71d2a62b5e0324d77e69166c8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 20:15:28 +0200 Subject: [PATCH 01/21] Implement premium access keys management API API serves for creation, loading and checking of these keys. --- Controllers/Api/ApiController.php | 4 +- .../PremiumAuthenticator/Authenticator.php | 67 +++++++++++++++++++ Models/Api/PremiumAuthenticator/DbInfo.ini | 4 ++ .../PremiumCodeManager.php | 65 ++++++++++++++++++ routes.ini | 3 + 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 Controllers/Api/PremiumAuthenticator/Authenticator.php create mode 100644 Models/Api/PremiumAuthenticator/DbInfo.ini create mode 100644 Models/Api/PremiumAuthenticator/PremiumCodeManager.php diff --git a/Controllers/Api/ApiController.php b/Controllers/Api/ApiController.php index 65d94f7..7c3f867 100644 --- a/Controllers/Api/ApiController.php +++ b/Controllers/Api/ApiController.php @@ -17,8 +17,10 @@ abstract class ApiController extends Controller const UPDATING_API_KEY = 'testing'; //Usage analysis api keys const AGGREGATE_API_KEY = 'testing'; - //Discord Integration Key + //Discord integration key const DISCORD_INTEGRATION_API_KEY = 'testing'; + //Premium authenticator key + const PREMIUM_AUTHENTICATOR_API_KEY = 'testing'; /** * Controller constructor enabling output buffering and setting the Content-Type header diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php new file mode 100644 index 0000000..142b7b0 --- /dev/null +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -0,0 +1,67 @@ +getCodeForUser($discordUserId); + $status = 200; + if (is_null($code)) { + $code = $this->generateCodeForUser($discordUserId); + $status = 201; + } + echo json_encode(["code" => $code]); + return $status; + case 'check-code': + $valid = $this->checkCode(); + if ($valid > 1) { //Higher numbers are HTTP error codes + return $valid; + } + echo json_encode(['valid' => ($valid) ? 'true' : 'false']); + return 200; + default: + return 400; + } + } + + private function getCodeForUser(string $discordUserId) : ?string + { + $manager = new PremiumCodeManager(); + return $manager->getCode($discordUserId); + } + + private function generateCodeForUser(string $discordUserId) : string + { + $manager = new PremiumCodeManager(); + return $manager->createNew($discordUserId); + } + + private function checkCode() : int + { + $code = $_REQUEST['code'] ?? null; + if (is_null($code)) { + return 400; + } + + $manager = new PremiumCodeManager(); + return $manager->verify($code); + } +} + diff --git a/Models/Api/PremiumAuthenticator/DbInfo.ini b/Models/Api/PremiumAuthenticator/DbInfo.ini new file mode 100644 index 0000000..6a52634 --- /dev/null +++ b/Models/Api/PremiumAuthenticator/DbInfo.ini @@ -0,0 +1,4 @@ +host=localhost +database=premium-authenticator +username=root +password= \ No newline at end of file diff --git a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php new file mode 100644 index 0000000..9c4353f --- /dev/null +++ b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php @@ -0,0 +1,65 @@ +executeQuery('INSERT INTO access_codes(code,discord_id) VALUES (?,?);', [$code, $discordUserId]); + } catch (PDOException $ex) { + return false; + } + + return $code; + } + + /** + * Method loading an premium access code for the given Discord user + * @param string $discordUserId Discord user ID of the user whose code should be loaded (numerical string or BIGINT + * in MySQL) + * @return string|null The code (16 characters) or NULL, if no code for the user exists + */ + public function getCode(string $discordUserId): ?string + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + $result = $db->fetchQuery('SELECT code FROM access_codes WHERE discord_id = ? LIMIT 1;', [$discordUserId]); + if ($result === false) { + return null; + } + return $result['code']; + } + + /** + * Method checking whether an premium access code is valid + * @param string $code Access code to check + * @return bool TRUE if the code is valid, FALSE if not + */ + public function verify(string $code): bool + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + $result = $db->fetchQuery('SELECT COUNT(*) AS "cnt" FROM access_codes WHERE code = ? LIMIT 1;', [$code]); + return (bool)$result["cnt"]; + } +} + diff --git a/routes.ini b/routes.ini index 9fce621..f1654cb 100644 --- a/routes.ini +++ b/routes.ini @@ -60,3 +60,6 @@ /api/content/quest-info=Api\Other\Content?quests /api/discord-integration=Api\DiscordIntegration\DiscordIntegration + +/api/premium/load=Api\PremiumAuthenticator\Authenticator?get-code +/api/premium/check=Api\PremiumAuthenticator\Authenticator?check-code From 334cb7f8689fb9746150251aa01224539b013990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 21:05:43 +0200 Subject: [PATCH 02/21] If the access code is valid, server IP is returned as well This IP is where the audio streams are located --- Controllers/Api/PremiumAuthenticator/Authenticator.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php index 142b7b0..e9ff525 100644 --- a/Controllers/Api/PremiumAuthenticator/Authenticator.php +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -7,6 +7,8 @@ class Authenticator extends ApiController { + const STREAM_SERVER_IP = '127.0.0.1'; + public function process(array $args): int { $apiKey = $_REQUEST['apiKey'] ?? null; @@ -34,7 +36,11 @@ public function process(array $args): int if ($valid > 1) { //Higher numbers are HTTP error codes return $valid; } - echo json_encode(['valid' => ($valid) ? 'true' : 'false']); + $result = ['valid' => ($valid) ? 'true' : 'false']; + if ($valid) { + $result['ip'] = self::STREAM_SERVER_IP; + } + echo json_encode($result); return 200; default: return 400; From 15c02b3f2de380b8382612db9a4d6cc1373d0a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 21:21:13 +0200 Subject: [PATCH 03/21] API key is not necessary for check-code requests There are some pre-Db request validation of the access code that is being checked --- .../Api/PremiumAuthenticator/Authenticator.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php index e9ff525..96b0082 100644 --- a/Controllers/Api/PremiumAuthenticator/Authenticator.php +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -11,13 +11,13 @@ class Authenticator extends ApiController public function process(array $args): int { - $apiKey = $_REQUEST['apiKey'] ?? null; - if ($apiKey !== self::PREMIUM_AUTHENTICATOR_API_KEY) { - return 401; - } - switch ($args[0]) { case 'get-code': + $apiKey = $_REQUEST['apiKey'] ?? null; + if ($apiKey !== self::PREMIUM_AUTHENTICATOR_API_KEY) { + return 401; + } + $discordUserId = $_REQUEST['discord'] ?? null; if (is_null($discordUserId)) { return 400; @@ -62,7 +62,7 @@ private function generateCodeForUser(string $discordUserId) : string private function checkCode() : int { $code = $_REQUEST['code'] ?? null; - if (is_null($code)) { + if (is_null($code) || strlen($code) !== 16 || strtoupper($code) !== $code) { return 400; } From 4b2be48dc3fe88705515cbb0ee9775a496d799ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 21:25:06 +0200 Subject: [PATCH 04/21] Premium access codes are no longer case-sensitive --- Controllers/Api/PremiumAuthenticator/Authenticator.php | 2 +- Models/Api/PremiumAuthenticator/PremiumCodeManager.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php index 96b0082..7d9fdc0 100644 --- a/Controllers/Api/PremiumAuthenticator/Authenticator.php +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -62,7 +62,7 @@ private function generateCodeForUser(string $discordUserId) : string private function checkCode() : int { $code = $_REQUEST['code'] ?? null; - if (is_null($code) || strlen($code) !== 16 || strtoupper($code) !== $code) { + if (is_null($code) || strlen($code) !== 16) { return 400; } diff --git a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php index 9c4353f..93b77ac 100644 --- a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php +++ b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php @@ -57,6 +57,7 @@ public function getCode(string $discordUserId): ?string */ public function verify(string $code): bool { + $code = strtoupper($code); $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); $result = $db->fetchQuery('SELECT COUNT(*) AS "cnt" FROM access_codes WHERE code = ? LIMIT 1;', [$code]); return (bool)$result["cnt"]; From a518d3988b5e9dab7c3235e62b659d912cd3919c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 21:37:57 +0200 Subject: [PATCH 05/21] Remove the need for an API key when reporting a new unvoiced line --- Controllers/Api/ApiController.php | 1 - Controllers/Api/LineReporting/LineReporter.php | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Controllers/Api/ApiController.php b/Controllers/Api/ApiController.php index 7c3f867..4e31bfa 100644 --- a/Controllers/Api/ApiController.php +++ b/Controllers/Api/ApiController.php @@ -12,7 +12,6 @@ abstract class ApiController extends Controller /* All API keys go here */ //Line reporting keys - const REPORTING_API_KEY = 'testing'; const COLLECTING_API_KEY = 'testing'; const UPDATING_API_KEY = 'testing'; //Usage analysis api keys diff --git a/Controllers/Api/LineReporting/LineReporter.php b/Controllers/Api/LineReporting/LineReporter.php index 40d0558..13a0a6c 100644 --- a/Controllers/Api/LineReporting/LineReporter.php +++ b/Controllers/Api/LineReporting/LineReporter.php @@ -14,7 +14,7 @@ public function process(array $args): int { parse_str(file_get_contents("php://input"),$_PUT); - if (!isset($_REQUEST['apiKey']) && !isset($_PUT['apiKey'])) { + if (!isset($_REQUEST['apiKey']) && !isset($_PUT['apiKey']) && $args[0] !== 'newUnvoicedLineReport') { return 401; } @@ -45,9 +45,6 @@ private function newReport(): int if ($_SERVER['REQUEST_METHOD'] !== 'POST') { return 405; } - if ($_REQUEST['apiKey'] !== self::REPORTING_API_KEY) { - return 401; - } $reportAdder = new ReportAdder(); return $reportAdder->createReport($_POST['full'], $_POST['npc'], $_POST['player'], $_POST['x'], $_POST['y'], $_POST['z']); } From 852620fbb704924b4001d488cc57f0b016b0b283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 14 Sep 2023 22:13:33 +0200 Subject: [PATCH 06/21] Return value of Controller::process() now projects to HTTP status code --- Controllers/Controller.php | 2 +- index.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Controllers/Controller.php b/Controllers/Controller.php index a9060f2..c57c371 100644 --- a/Controllers/Controller.php +++ b/Controllers/Controller.php @@ -15,7 +15,7 @@ abstract class Controller /** * Public method processing passed data, specific for each controller * @param array $args Arguments to process - * @return int 1 (or TRUE), if everything worked as expected, HTTP error code otherwise + * @return int 1 (or TRUE) or HTTP success status code (2xx) if everything worked as expected, HTTP error code otherwise */ public abstract function process(array $args): int; } diff --git a/index.php b/index.php index a1b01c4..fca8e96 100644 --- a/index.php +++ b/index.php @@ -57,6 +57,9 @@ function autoloader(string $name): void //This is mostly used for AJAX calls } else if ($result < 300) { + if ($result >= 100) { + http_response_code($result); + } $website = $router->getResult(); } From c9fa1914a7681dbab061a0b7bb7f20b44884209d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Sun, 17 Sep 2023 21:31:12 +0200 Subject: [PATCH 07/21] Premium access codes can be deactivated and reactivated Add two new premium API endpoints --- .../PremiumAuthenticator/Authenticator.php | 91 ++++++++++++++----- .../PremiumCodeManager.php | 46 ++++++++-- routes.ini | 2 + 3 files changed, 107 insertions(+), 32 deletions(-) diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php index 7d9fdc0..c77ee14 100644 --- a/Controllers/Api/PremiumAuthenticator/Authenticator.php +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -11,55 +11,88 @@ class Authenticator extends ApiController public function process(array $args): int { + parse_str(file_get_contents("php://input"),$_INPUT); + $_INPUT = array_merge($_REQUEST, $_INPUT); + + $apiKey = $_INPUT['apiKey'] ?? null; + $discordUserId = $_INPUT['discord'] ?? null; + + if (!in_array($args[0], ['check-code'])) { + if ($apiKey !== self::PREMIUM_AUTHENTICATOR_API_KEY) { + return 401; + } + + if (is_null($discordUserId)) { + return 400; + } + } + switch ($args[0]) { - case 'get-code': - $apiKey = $_REQUEST['apiKey'] ?? null; - if ($apiKey !== self::PREMIUM_AUTHENTICATOR_API_KEY) { - return 401; + case 'check-code': + if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + return 405; } - $discordUserId = $_REQUEST['discord'] ?? null; - if (is_null($discordUserId)) { - return 400; + $valid = $this->checkCode(); + $result = ['valid' => ($valid === 200) ? 'true' : 'false']; + if ($valid === 200) { + $result['ip'] = self::STREAM_SERVER_IP; + } else { + $result['reason'] = ($valid === 402) ? 'expired' : 'invalid'; + } + echo json_encode($result); + return 200; + case 'get-code': + if ($_SERVER['REQUEST_METHOD'] !== 'GET') { + return 405; } - $code = $this->getCodeForUser($discordUserId); - $status = 200; - if (is_null($code)) { + $result = $this->getCodeForUser($discordUserId); + $status = $result['status']; + $code = $result['code'] ?? null; + if ($status === 404) { $code = $this->generateCodeForUser($discordUserId); $status = 201; } echo json_encode(["code" => $code]); return $status; - case 'check-code': - $valid = $this->checkCode(); - if ($valid > 1) { //Higher numbers are HTTP error codes - return $valid; + case 'deactivate-user': + if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { + //return 405; } - $result = ['valid' => ($valid) ? 'true' : 'false']; - if ($valid) { - $result['ip'] = self::STREAM_SERVER_IP; + + return ($this->deactivateCodeForUser($discordUserId)) ? 204 : 500; + case 'reactivate-user': + if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { + //return 405; } - echo json_encode($result); - return 200; + + return ($this->activateCodeForUser($discordUserId)) ? 204 : 500; default: return 400; } } - private function getCodeForUser(string $discordUserId) : ?string + private function getCodeForUser(string $discordUserId): array { $manager = new PremiumCodeManager(); - return $manager->getCode($discordUserId); + $codeInfo = $manager->getCode($discordUserId); + if (is_null($codeInfo)) { + return ['status' => 404]; + } else if (!$codeInfo['active']) { + return ['status' => 402, 'code' => $codeInfo['code']]; + } else { + return ['status' => 200, 'code' => $codeInfo['code']]; + } } - private function generateCodeForUser(string $discordUserId) : string + private function generateCodeForUser(string $discordUserId): string { $manager = new PremiumCodeManager(); return $manager->createNew($discordUserId); } - private function checkCode() : int + private function checkCode(): int { $code = $_REQUEST['code'] ?? null; if (is_null($code) || strlen($code) !== 16) { @@ -69,5 +102,17 @@ private function checkCode() : int $manager = new PremiumCodeManager(); return $manager->verify($code); } + + private function deactivateCodeForUser(string $discordUserId): bool + { + $manager = new PremiumCodeManager(); + return $manager->deactivate($discordUserId); + } + + private function activateCodeForUser(string $discordUserId): bool + { + $manager = new PremiumCodeManager(); + return $manager->activate($discordUserId); + } } diff --git a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php index 93b77ac..48accbb 100644 --- a/Models/Api/PremiumAuthenticator/PremiumCodeManager.php +++ b/Models/Api/PremiumAuthenticator/PremiumCodeManager.php @@ -38,29 +38,57 @@ public function createNew(string $discordUserId) * Method loading an premium access code for the given Discord user * @param string $discordUserId Discord user ID of the user whose code should be loaded (numerical string or BIGINT * in MySQL) - * @return string|null The code (16 characters) or NULL, if no code for the user exists + * @return array|null Associative array with elements "code" (the 16-character code) and "active" (boolean) or NULL, + * if no code for the user exists */ - public function getCode(string $discordUserId): ?string + public function getCode(string $discordUserId): ?array { $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); - $result = $db->fetchQuery('SELECT code FROM access_codes WHERE discord_id = ? LIMIT 1;', [$discordUserId]); + $result = $db->fetchQuery('SELECT code,active FROM access_codes WHERE discord_id = ? LIMIT 1;', [$discordUserId]); if ($result === false) { return null; } - return $result['code']; + return $result; } /** - * Method checking whether an premium access code is valid + * Method checking whether a premium access code is valid * @param string $code Access code to check - * @return bool TRUE if the code is valid, FALSE if not + * @return int An HTTP-like code indicator: + * 200 if the code is valid, 402 if the code is disabled, 404 if code does not exist */ - public function verify(string $code): bool + public function verify(string $code): int { $code = strtoupper($code); $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); - $result = $db->fetchQuery('SELECT COUNT(*) AS "cnt" FROM access_codes WHERE code = ? LIMIT 1;', [$code]); - return (bool)$result["cnt"]; + $result = $db->fetchQuery('SELECT active FROM access_codes WHERE code = ? LIMIT 1;', [$code]); + if ($result === false) { + return 404; + } else { + return ($result['active']) ? 200 : 402; + } + } + + /** + * Method marking a certain user's access code as disabled + * @param string $discordUserId Discord ID of the user whose access code should be deactivated + * @return bool TRUE on success, FALSE on failure + */ + public function deactivate(string $discordUserId) + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + return $db->executeQuery('UPDATE access_codes SET active = 0 WHERE discord_id = ?;', [$discordUserId]); + } + + /** + * Method marking a certain user's access code as enabled + * @param string $discordUserId Discord ID of the user whose access code should be activated + * @return bool TRUE on success, FALSE on failure + */ + public function activate(string $discordUserId) + { + $db = new Db('Api/PremiumAuthenticator/DbInfo.ini'); + return $db->executeQuery('UPDATE access_codes SET active = 1 WHERE discord_id = ?;', [$discordUserId]); } } diff --git a/routes.ini b/routes.ini index f1654cb..df51658 100644 --- a/routes.ini +++ b/routes.ini @@ -63,3 +63,5 @@ /api/premium/load=Api\PremiumAuthenticator\Authenticator?get-code /api/premium/check=Api\PremiumAuthenticator\Authenticator?check-code +/api/premium/deactivate=Api\PremiumAuthenticator\Authenticator?deactivate-user +/api/premium/reactivate=Api\PremiumAuthenticator\Authenticator?reactivate-user \ No newline at end of file From cab7770db1131afad0e4e483a4e32cbe27ddb626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Sun, 17 Sep 2023 21:36:39 +0200 Subject: [PATCH 08/21] Error API calls can now return message body. --- index.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.php b/index.php index fca8e96..b3baa8a 100644 --- a/index.php +++ b/index.php @@ -49,6 +49,8 @@ function autoloader(string $name): void if ($router->isWebpageRequest) { $website = $errorController->getResult(); + } else { + $website = $router->getResult(); } } else if ($result === 204) { From 5a74c8d32af5c56dd827e56f282bf6e7c023b7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Sun, 17 Sep 2023 21:37:12 +0200 Subject: [PATCH 09/21] Add error controller and view for the 402 error. It's not used for now, but might be in the future when the premium service is introduced. --- Controllers/Errors/Error402.php | 22 ++++++++++++++++++++++ Views/error402.phtml | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 Controllers/Errors/Error402.php create mode 100644 Views/error402.phtml diff --git a/Controllers/Errors/Error402.php b/Controllers/Errors/Error402.php new file mode 100644 index 0000000..884c7f1 --- /dev/null +++ b/Controllers/Errors/Error402.php @@ -0,0 +1,22 @@ + + + + + + + <?= ${basename(__FILE__, '.phtml').'_title'} ?> + + + + + + + + + + + + + + + + + + +

402 – Payment Required

+

That'll be nine emerald blocks.?

+

You need to be our patron to be able to access this content.

+

Check out our Patreon and make a pledge that will grant you access.

+An illustration of two Wynncraft characters looking for something.
+Art by Gleeokenspiel
+ + From ea02955d80e08e8a9082642b64869a1fdb42a770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Sun, 17 Sep 2023 22:05:59 +0200 Subject: [PATCH 10/21] Uncommented things commented for testing purposes --- Controllers/Api/PremiumAuthenticator/Authenticator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Controllers/Api/PremiumAuthenticator/Authenticator.php b/Controllers/Api/PremiumAuthenticator/Authenticator.php index c77ee14..c57e8fe 100644 --- a/Controllers/Api/PremiumAuthenticator/Authenticator.php +++ b/Controllers/Api/PremiumAuthenticator/Authenticator.php @@ -58,13 +58,13 @@ public function process(array $args): int return $status; case 'deactivate-user': if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { - //return 405; + return 405; } return ($this->deactivateCodeForUser($discordUserId)) ? 204 : 500; case 'reactivate-user': if ($_SERVER['REQUEST_METHOD'] !== 'PUT') { - //return 405; + return 405; } return ($this->activateCodeForUser($discordUserId)) ? 204 : 500; From dcccd63ac3ef7afcbd4746d91aa690155f95be9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 28 Sep 2023 12:49:29 +0200 Subject: [PATCH 11/21] Fix bug when the last NPC/Quest ID was used if the current one wasn't found --- Models/Website/RecordingUploader.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Models/Website/RecordingUploader.php b/Models/Website/RecordingUploader.php index c18ba07..abd9fb8 100644 --- a/Models/Website/RecordingUploader.php +++ b/Models/Website/RecordingUploader.php @@ -19,14 +19,17 @@ class RecordingUploader * Use the provided getters for these properties after calling this method to retrieve the status of the operation. * @param array $uploadedFiles Array from $_FILES['recording'] with unchanged structure, which contains the files to process * @param bool $overwrite TRUE, if files with the same name should be overwritten, FALSE, if they should be renamed instead, default FALSE - * @param int|null $questId ID of the quest to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording - * @param int|null $npcId ID of the NPC to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording + * @param int|null $overrideQuestId ID of the quest to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording + * @param int|null $overrideNpcId ID of the NPC to assign new recordings to, if left as null, the program will try to decide the ID based on the filename of every single recording * @return int Number of successfully uploaded recordings */ - public function upload(array $uploadedFiles, bool $overwrite = false, ?int $questId = null, ?int $npcId = null) : int + public function upload(array $uploadedFiles, bool $overwrite = false, ?int $overrideQuestId = null, ?int $overrideNpcId = null): int { $recordingsCount = count($uploadedFiles['name']); for ($i = 0; $i < $recordingsCount; $i++) { + $questId = $overrideQuestId; + $npcId = $overrideNpcId; + $filename = $uploadedFiles['name'][$i]; $tempName = $uploadedFiles['tmp_name'][$i]; $type = $uploadedFiles['type'][$i]; From d5f96e794eb4881c593585b1b6e6cbb80702667e Mon Sep 17 00:00:00 2001 From: Just5MoreMinutes Date: Sun, 22 Oct 2023 14:47:43 +0200 Subject: [PATCH 12/21] fix // comment profile pciture size fixed --- Views/comments.phtml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Views/comments.phtml b/Views/comments.phtml index 233bf5e..dcc35de 100644 --- a/Views/comments.phtml +++ b/Views/comments.phtml @@ -77,10 +77,11 @@ - - - Avatar - + + + + Avatar + From 8469116e54c2ec7850a5efc6e4d5bf5d1e5d937b Mon Sep 17 00:00:00 2001 From: Just5MoreMinutes Date: Sun, 22 Oct 2023 15:05:11 +0200 Subject: [PATCH 13/21] fix // profile picture size fixed in comment section --- Views/comments.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Views/comments.phtml b/Views/comments.phtml index dcc35de..e7db9aa 100644 --- a/Views/comments.phtml +++ b/Views/comments.phtml @@ -80,7 +80,7 @@ - Avatar + Avatar From dbfb04ab35af1db2b7e9af9d517e13a951f96a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 15:39:31 +0100 Subject: [PATCH 14/21] Update composer dependencies --- composer.lock | 69 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/composer.lock b/composer.lock index 368a657..4a8e52e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,43 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a6c5cc896a5450cb8c267cda6e6409ff", + "content-hash": "79a15d1cad08678193b283a1af287d88", "packages": [ { "name": "ezyang/htmlpurifier", - "version": "v4.14.0", + "version": "v4.17.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", - "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", "shasum": "" }, "require": { - "php": ">=5.2" + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" }, "type": "library", "autoload": { - "psr-0": { - "HTMLPurifier": "library/" - }, "files": [ "library/HTMLPurifier.composer.php" ], + "psr-0": { + "HTMLPurifier": "library/" + }, "exclude-from-classmap": [ "/library/HTMLPurifier/Language/" ] @@ -53,22 +63,22 @@ ], "support": { "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" }, - "time": "2021-12-25T01:21:49+00:00" + "time": "2023-11-17T15:01:25+00:00" }, { "name": "phpmailer/phpmailer", - "version": "v6.5.3", + "version": "v6.9.1", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "baeb7cde6b60b1286912690ab0693c7789a31e71" + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/baeb7cde6b60b1286912690ab0693c7789a31e71", - "reference": "baeb7cde6b60b1286912690ab0693c7789a31e71", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", "shasum": "" }, "require": { @@ -78,22 +88,25 @@ "php": ">=5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", - "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.0", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, "type": "library", "autoload": { @@ -125,7 +138,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.5.3" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1" }, "funding": [ { @@ -133,7 +146,7 @@ "type": "github" } ], - "time": "2021-11-25T16:34:11+00:00" + "time": "2023-11-25T22:23:28+00:00" } ], "packages-dev": [], @@ -144,8 +157,10 @@ "prefer-lowest": false, "platform": { "ext-pdo": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-curl": "*", + "ext-json": "*" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From e385d2e8d96e692b78a1caaa34fb3a1ac9c92c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 16:55:05 +0100 Subject: [PATCH 15/21] Remove pictures from the manage NPCs page They were making the loading of the webpage too long. Add a recordings count in their place into new column. --- Controllers/Website/WebpageController.php | 1 + Models/Website/ContentManager.php | 19 ++++++++++--------- Models/Website/Npc.php | 14 ++++++++++++++ Views/npcs.phtml | 16 +++++++--------- css/administration.css | 17 +++++++++++++++++ 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Controllers/Website/WebpageController.php b/Controllers/Website/WebpageController.php index 8657684..814b5ee 100644 --- a/Controllers/Website/WebpageController.php +++ b/Controllers/Website/WebpageController.php @@ -154,6 +154,7 @@ private function sanitize($value) $attr['id'] = $this->sanitize($value->getId()); $attr['name'] = $this->sanitize($value->getName()); $attr['archived'] = $this->sanitize($value->isArchived()); + $attr['recordings_count'] = $this->sanitize($value->getRecordingsCount()); $voiceActor = $this->sanitize($value->getVoiceActor()); $npc = new Npc($attr); if ($voiceActor !== null) { diff --git a/Models/Website/ContentManager.php b/Models/Website/ContentManager.php index 44dbdbe..38491f4 100644 --- a/Models/Website/ContentManager.php +++ b/Models/Website/ContentManager.php @@ -9,13 +9,15 @@ class ContentManager public function getQuests(?int $questId = null): array { $query = ' - SELECT quest.quest_id, quest.name AS "qname", npc.npc_id, npc.name AS "nname", npc.voice_actor_id, npc.archived, user.user_id, user.display_name, user.picture - FROM quest - JOIN npc_quest ON npc_quest.quest_id = quest.quest_id - JOIN npc ON npc.npc_id = npc_quest.npc_id - LEFT JOIN user ON npc.voice_actor_id = user.user_id - '.(is_null($questId) ? '' : 'WHERE quest.quest_id = ?').' - ORDER BY quest.quest_id, npc_quest.sorting_order; + SELECT quest.quest_id, quest.name AS "qname", npc.npc_id, npc.name AS "nname", npc.voice_actor_id, npc.archived, user.user_id, user.display_name, COUNT(DISTINCT recording.recording_id) AS "recordings_count" + FROM quest + JOIN npc_quest ON npc_quest.quest_id = quest.quest_id + JOIN npc ON npc.npc_id = npc_quest.npc_id + JOIN recording ON recording.npc_id = npc.npc_id AND recording.quest_id = quest.quest_id + LEFT JOIN user ON npc.voice_actor_id = user.user_id + '.(is_null($questId) ? '' : 'WHERE quest.quest_id = ?').' + GROUP BY quest.quest_id, npc.npc_id, npc_quest.sorting_order + ORDER BY quest.quest_id, npc_quest.sorting_order; '; $result = (new Db('Website/DbInfo.ini'))->fetchQuery($query, (is_null($questId) ? array() : array($questId)), true); @@ -29,9 +31,8 @@ public function getQuests(?int $questId = null): array } $currentQuest = new Quest($npc); } - $npcObj = new Npc($npc); - if ($npc['user_id'] !== null) { + if ($npc['user_id'] !== null) { $voiceActor = new User(); $voiceActor->setData($npc); $npcObj->setVoiceActor($voiceActor); diff --git a/Models/Website/Npc.php b/Models/Website/Npc.php index 6136641..64efc08 100644 --- a/Models/Website/Npc.php +++ b/Models/Website/Npc.php @@ -15,6 +15,8 @@ class Npc implements JsonSerializable private array $recordings = array(); + private int $recordingsCount; + /** * @param array $data Data returned from database, invalid items are skipped, multiple key names are supported for * each attribute @@ -39,6 +41,9 @@ public function __construct(array $data) case 'archived': case 'hidden': $this->archived = $value; + break; + case 'recordings_count': + $this->recordingsCount = $value; break; } } @@ -214,5 +219,14 @@ public function getRecordings() : array { return $this->recordings; } + + /** + * Recordings count getter + * @return int + */ + public function getRecordingsCount(): int + { + return $this->recordingsCount; + } } diff --git a/Views/npcs.phtml b/Views/npcs.phtml index 092ab9a..5555ebd 100644 --- a/Views/npcs.phtml +++ b/Views/npcs.phtml @@ -1,9 +1,9 @@ -
+

getName() ?>

- + getNpcs() as $npc) : ?> @@ -12,16 +12,14 @@
- - + getVoiceActor() === null) : ?> - - + - - + - + +
RearrangeNPCVoice actorRearrangeNPCVoice actorRecordingsAction
NPC picturegetName() ?>getName() ?> Profile pictureNobodyNobody Profile picturegetVoiceActor()->getName() ?>getVoiceActor()->getName() ?> ManagegetRecordingsCount() ?>Manage

diff --git a/css/administration.css b/css/administration.css index 09701b3..aef9f0a 100644 --- a/css/administration.css +++ b/css/administration.css @@ -69,6 +69,23 @@ td th { .deletebtn { background: rgb(255, 0, 0);} .deletebtn:hover { border-color: rgb(221, 55, 55);} +.table-npc-management { + width: fit-content; + margin: auto; +} + +.table-npc-management td { + text-align: center; +} + +.va-name { + color: #117743; +} + +.npc-name { + color: purple; +} + #filename { width: 26rem; } From e6b9d3c1adfbe54d4cbdd7c6c03683ec2543578e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 17:18:41 +0100 Subject: [PATCH 16/21] Downloads webpage now displays Fabric versions by default Forge versions are hidden under a small link for archival purposes only, as they're now deprecated. --- Controllers/Website/Downloads.php | 4 --- Views/downloads-list.phtml | 16 ++++++++-- Views/downloads.phtml | 53 ------------------------------- routes.ini | 3 +- 4 files changed, 14 insertions(+), 62 deletions(-) delete mode 100644 Views/downloads.phtml diff --git a/Controllers/Website/Downloads.php b/Controllers/Website/Downloads.php index d562e92..fac7832 100644 --- a/Controllers/Website/Downloads.php +++ b/Controllers/Website/Downloads.php @@ -13,10 +13,6 @@ class Downloads extends WebpageController public function process(array $args): int { switch (array_shift($args)) { - case 'junction': - self::$views[] = 'downloads'; - self::$cssFiles[] = 'downloads'; - break; case 'forge': $downloadsManager = new DownloadsManager(); self::$data['downloadslist_versions'] = $downloadsManager->listDownloads(DownloadsManager::FORGE_VERSIONS); diff --git a/Views/downloads-list.phtml b/Views/downloads-list.phtml index b4c6a58..ebe9142 100644 --- a/Views/downloads-list.phtml +++ b/Views/downloads-list.phtml @@ -1,15 +1,25 @@

Download Voices of Wynn

For

- +

+ +

Looking for versions for Forge 1.12.2? Click here.

+ +
+

Support for Voices of Wynn Forge has officially been dropped and version 1.5 was the last.

+

We recommend switching to Fabric, as most popular Wynncraft mods are already using newer versions of Minecraft and connecting to Wynncraft on 1.12.2 is deprecated.

+
+ +

Older Versions of the Mod

Want an older version of the mod? They are linked below! The oldest are at the bottom and the newest are at the top.

- + +

- +

diff --git a/Views/downloads.phtml b/Views/downloads.phtml deleted file mode 100644 index 8261de9..0000000 --- a/Views/downloads.phtml +++ /dev/null @@ -1,53 +0,0 @@ -

Download Voices of Wynn

- -

Choose your platform

-

- Voices of Wynn is supported for Forge client and Fabric client. -

-

- Forge version is meant for Minecraft version 1.12.2. -

- -

- Fabric version is usually meant for the latest Minecraft release version. - Check the exact version in the download details. -

- - - diff --git a/routes.ini b/routes.ini index df51658..49ea09a 100644 --- a/routes.ini +++ b/routes.ini @@ -14,9 +14,8 @@ /contents/npc/<0>/comments/<1>/new=Website\Rating?<1>?c /contents/npc/<0>/comments/<1>/delete/<2>=Website\Comments?<2> /cast/<0>=Website\Cast?<0> -/downloads=Website\Downloads?junction +/downloads=Website\Downloads?fabric /downloads/forge=Website\Downloads?forge -/downloads/fabric=Website\Downloads?fabric /downloads/<0>=Website\Download?view?<0> /download/<0>/jar=Website\Download?get?<0>?direct /download/<0>/exe=Website\Download?get?<0>?installer From df188cde87654e342f8a14bebc4ceb6a6b046462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 17:55:14 +0100 Subject: [PATCH 17/21] When archiving NPC, its degenerated name is copied as well --- Models/Website/Npc.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Models/Website/Npc.php b/Models/Website/Npc.php index 64efc08..1a2dc96 100644 --- a/Models/Website/Npc.php +++ b/Models/Website/Npc.php @@ -15,7 +15,7 @@ class Npc implements JsonSerializable private array $recordings = array(); - private int $recordingsCount; + private int $recordingsCount = 0; /** * @param array $data Data returned from database, invalid items are skipped, multiple key names are supported for @@ -95,7 +95,7 @@ public function archive() : int //Create new NPC $this->loadName(); //Needed to be copied - $replacementId = $db->executeQuery('INSERT INTO npc(name) VALUES (?);', array($this->name), true); + $replacementId = $db->executeQuery('INSERT INTO npc(name, degenerated_name) VALUES (?,?);', array($this->name, $this->degeneratedName), true); $replacementNpc = new Npc(array('id' => $replacementId, 'name' => $this->name)); unset($replacementId); @@ -150,7 +150,7 @@ public function getId() : ?int } /** - * Method loading the name of this NPC from the database. + * Method loading the name and degenerated name of this NPC from the database. * The ID property must be filled for this method to work * @return bool TRUE on success, FALSE on failure (ID is not known or no database results) */ @@ -161,10 +161,11 @@ private function loadName() : bool } $db = new Db('Website/DbInfo.ini'); - $result = $db->fetchQuery('SELECT name FROM npc WHERE npc_id = ?;', array($this->id)); + $result = $db->fetchQuery('SELECT name, degenerated_name FROM npc WHERE npc_id = ?;', array($this->id)); if (!empty($result)) { $this->name = $result['name']; + $this->degeneratedName = $result['degenerated_name']; return true; } return false; From 86797260b535779f7f92aaac0bff5c74d9723e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 18:26:48 +0100 Subject: [PATCH 18/21] FAQ update, typo fix on error402.phtml --- Views/error402.phtml | 2 +- Views/faq.phtml | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Views/error402.phtml b/Views/error402.phtml index 7bcb4cc..cff8d5b 100644 --- a/Views/error402.phtml +++ b/Views/error402.phtml @@ -24,7 +24,7 @@

402 – Payment Required

-

That'll be nine emerald blocks.?

+

That'll be nine emerald blocks.

You need to be our patron to be able to access this content.

Check out our Patreon and make a pledge that will grant you access.

An illustration of two Wynncraft characters looking for something.
diff --git a/Views/faq.phtml b/Views/faq.phtml index 5536826..af16f84 100644 --- a/Views/faq.phtml +++ b/Views/faq.phtml @@ -1,18 +1,19 @@

FAQ

How many characters are voiced in the mod?
-

Currently, all quest NPCs are voiced. We’re hard at work keeping all quest dialogues up to date while adding dialogues to non-quest NPCs.

-

In addition to quest NPCs, we’ve also added full voice acting for the Seaskipper Captain and the Talking Mushroom. Other significant NPCs will be added soon!

+

Currently, all quest NPCs and dungeon NPCs are voiced. We’re working hard to keep all quest and dungeon dialogues up to date while adding voicelines to other NPCs as well, but sometimes, there is is slight change in dialogue that breaks a voiceline here and there.

+

In addition to quest and dungeon NPCs, we’ve also added full voice acting for the Seaskipper Captain and the Talking Mushroom. Other significant NPCs will be added soon!


Do you plan to voice all NPCs in Wynncraft?
-

We’re currently adding voices to Secret Discoveries. The next thing on our agenda is boss altars and dungeons. After that, it is our hope that we will voice all NPCs with white name tags that players are most likely to talk to. It’s not likely we’ll do every single one.

+

We’re currently adding voices to Secret Discoveries and Boss Altars. After that, it is our hope that we will voice all NPCs with white name tags that players are most likely to talk to. It’s not likely we’ll manage to do every single one because of dialogue changes.


What Minecraft version is needed for the mod to work?
-

The mod was first developed for Forge (Minecraft version 1.12.2). At the current time, support for Fabric (Minecraft version 1.19.4) is also available. Pay attention to the "Minecraft version" information when downloading the mod to make sure you get the correct one.

+

The mod is currently developed for Fabric (Minecraft version 1.19.4).

+

In the past, it was first developed for Forge (Minecraft version 1.12.2), but support for Forge was dropped recently. You can still download the old versions on our Downloads page.

Voices of Wynn is also compatible with Wynntils.


@@ -44,7 +45,8 @@
Some dialogue isn’t playing. Where can I report it?
-

Be aware that some dialogue, such as pre-and-post-quest dialogue is not voiced. However, all dialogue during a quest should be voiced, and if not you should report it. Join our Discord server and navigate down to #🚨bugs-and-suggestions💡.

+

Be aware that some dialogue, especially dialogue said by most of the white-named NPCs (non-quest NPCs) is not voiced. However, all dialogue related to quests and dungeons should be voiced. If you enabled line reporting after installing Voices of Wynn, you don't need to do anything, because the reports will be sent automatically.

+

If you run into a different issue, join our Discord server and navigate down to #🙋issues-and-support.


I saw a login button. How can I create an account?
@@ -54,12 +56,12 @@
I have an account, but forgot my password. There’s no “forgot password” button, so what do I do?
-

If you forgot your password, contact Shady#2948 on Discord and explain. It’ll be easier if you contact him from the same account you used to apply to the project. Shady will reset your password.

+

If you forgot your password, contact @shady_medic on Discord and explain. It’ll be easier if you contact him from the same account you used to apply to the project. Shady will reset your password.


I found offensive, insulting, or inappropriate content on this webpage. Where do I report it?
-

Currently, there is no report option. Contact Shady#2948 on Discord to report inappropriate content.

+

Currently, there is no report option. Contact @shady_medic on Discord to report inappropriate content.


I need to contact an administrator, but I don’t have nor want to create a Discord account. What should I do?
From 6d4bd9232c14d294170a2d891b45ecea57c2378c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 18:47:05 +0100 Subject: [PATCH 19/21] Mass-upload now works when NPC/quest ID is set and filename components are incorrect The preset NPC/quest ID will override the components of the filename, as intended. --- Controllers/Website/Administration/Upload.php | 6 +++-- Controllers/Website/Npc.php | 23 ------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/Controllers/Website/Administration/Upload.php b/Controllers/Website/Administration/Upload.php index a19ec7a..1efb529 100644 --- a/Controllers/Website/Administration/Upload.php +++ b/Controllers/Website/Administration/Upload.php @@ -52,8 +52,10 @@ private function get(array $args): int private function post(array $args): int { $uploader = new RecordingUploader(); - - $uploader->upload($_FILES['recordings']); + $questId = $_POST['questId']; + $npcId = $_POST['npcId']; + $overwriteFiles = isset($_POST['overwrite']) && $_POST['overwrite'] === 'on'; + $uploader->upload($_FILES['recordings'], $overwriteFiles, $questId, $npcId); self::$data['upload_uploadErrors'] = $uploader->getErrors(); self::$data['upload_uploadSuccesses'] = $uploader->getSuccesses(); diff --git a/Controllers/Website/Npc.php b/Controllers/Website/Npc.php index 1a41e90..faadba6 100644 --- a/Controllers/Website/Npc.php +++ b/Controllers/Website/Npc.php @@ -89,29 +89,6 @@ private function get(array $args): int return true; } - /** - * Processing method for POST requests to this controller (new recordings were uploaded) - * @param array $args - * @return int|bool - */ - private function post(array $args): int - { - if ($this->disallowAdministration) { - return 403; - } - - $uploader = new RecordingUploader(); - $questId = $_POST['questId']; - $npcId = $_POST['npcId']; - $overwriteFiles = isset($_POST['overwrite']) && $_POST['overwrite'] === 'on'; - - $uploader->upload($_FILES['recordings'], $overwriteFiles, $questId, $npcId); - self::$data['npc_uploadErrors'][$questId] = $uploader->getErrors(); - self::$data['npc_uploadSuccesses'][$questId] = $uploader->getSuccesses(); - - return $this->get($args); - } - /** * Processing method for PUT requests to this controller (recast, archivation of NPC or archivation of recordings) * @param array $args Voice actor id as the first element From f3deff346f52c878ef59794b8f59911248b5259e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 18:51:59 +0100 Subject: [PATCH 20/21] Fix current MC version for Fabric (1.19.4 --> 1.20.2) --- Models/Website/DownloadsManager.php | 2 +- Views/faq.phtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Models/Website/DownloadsManager.php b/Models/Website/DownloadsManager.php index d7d34f8..adae5c2 100644 --- a/Models/Website/DownloadsManager.php +++ b/Models/Website/DownloadsManager.php @@ -14,7 +14,7 @@ class DownloadsManager private const FILE_NAME_FORMATS = 'VoicesOfWynn-MC{mcVersion}-v{version}.jar'; public const FORGE_VERSIONS = ['1.12.2']; - public const FABRIC_VERSIONS = ['1.19.4', '1.19.3', '1.18.2']; + public const FABRIC_VERSIONS = ['1.20.2', '1.19.4', '1.19.3', '1.18.2']; /** diff --git a/Views/faq.phtml b/Views/faq.phtml index af16f84..9d6cfe5 100644 --- a/Views/faq.phtml +++ b/Views/faq.phtml @@ -12,7 +12,7 @@
What Minecraft version is needed for the mod to work?
-

The mod is currently developed for Fabric (Minecraft version 1.19.4).

+

The mod is currently developed for Fabric (Minecraft version 1.20.2).

In the past, it was first developed for Forge (Minecraft version 1.12.2), but support for Forge was dropped recently. You can still download the old versions on our Downloads page.

Voices of Wynn is also compatible with Wynntils.

From 050a6eb230c7f331424e416a0b3834394471f163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C5=A0t=C4=9Bch?= Date: Thu, 1 Feb 2024 20:04:30 +0100 Subject: [PATCH 21/21] Re-add the overwrite checkbox to the mass-upload form --- Controllers/Website/Administration/Upload.php | 4 ++-- Views/upload.phtml | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Controllers/Website/Administration/Upload.php b/Controllers/Website/Administration/Upload.php index 1efb529..ce805bf 100644 --- a/Controllers/Website/Administration/Upload.php +++ b/Controllers/Website/Administration/Upload.php @@ -52,8 +52,8 @@ private function get(array $args): int private function post(array $args): int { $uploader = new RecordingUploader(); - $questId = $_POST['questId']; - $npcId = $_POST['npcId']; + $questId = (empty($_POST['questId']) ? null : $_POST['questId']); + $npcId = (empty($_POST['npcId']) ? null : $_POST['npcId']); $overwriteFiles = isset($_POST['overwrite']) && $_POST['overwrite'] === 'on'; $uploader->upload($_FILES['recordings'], $overwriteFiles, $questId, $npcId); self::$data['upload_uploadErrors'] = $uploader->getErrors(); diff --git a/Views/upload.phtml b/Views/upload.phtml index f6a52e9..5844b15 100644 --- a/Views/upload.phtml +++ b/Views/upload.phtml @@ -63,6 +63,16 @@

These will be applied to all recordings you upload this time and will override the names in their filenames!

+ Allow overwriting: + +

+ Overwriting files allows you to seamlessly replace old recording files with new versions of them. + However, you need to be absolutely sure that the file names are exatly the same! + The system sometimes renames the audio files (uploding with overwriting disabled or archiving), + so make sure you check the current name of the files on the server. To do this, see the src + attribute of the <source> elements within the <audio> + elements on the NPCs' webpages. +